style: kanban dropdowns, github integration loaders

This commit is contained in:
Aaryan Khandelwal 2023-02-23 22:34:36 +05:30
parent 69e8b504de
commit b4c4271f66
12 changed files with 91 additions and 60 deletions

View File

@ -15,7 +15,7 @@ const Breadcrumbs = ({ children }: BreadcrumbsProps) => {
<> <>
<div className="flex items-center"> <div className="flex items-center">
<div <div
className="grid h-8 w-8 cursor-pointer place-items-center rounded border border-gray-300 text-center text-sm hover:bg-gray-100" className="grid h-8 w-8 cursor-pointer place-items-center flex-shrink-0 rounded border border-gray-300 text-center text-sm hover:bg-gray-100"
onClick={() => router.back()} onClick={() => router.back()}
> >
<ArrowLeftIcon className="h-3 w-3" /> <ArrowLeftIcon className="h-3 w-3" />

View File

@ -226,12 +226,13 @@ export const SingleBoardIssue: React.FC<Props> = ({
</h5> </h5>
</a> </a>
</Link> </Link>
<div className="flex flex-wrap items-center gap-x-1 gap-y-2 text-xs"> <div className="relative flex flex-wrap items-center gap-x-1 gap-y-2 text-xs">
{properties.priority && selectedGroup !== "priority" && ( {properties.priority && selectedGroup !== "priority" && (
<ViewPrioritySelect <ViewPrioritySelect
issue={issue} issue={issue}
partialUpdateIssue={partialUpdateIssue} partialUpdateIssue={partialUpdateIssue}
isNotAllowed={isNotAllowed} isNotAllowed={isNotAllowed}
selfPositioned
/> />
)} )}
{properties.state && selectedGroup !== "state_detail.name" && ( {properties.state && selectedGroup !== "state_detail.name" && (
@ -239,6 +240,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
issue={issue} issue={issue}
partialUpdateIssue={partialUpdateIssue} partialUpdateIssue={partialUpdateIssue}
isNotAllowed={isNotAllowed} isNotAllowed={isNotAllowed}
selfPositioned
/> />
)} )}
{properties.due_date && ( {properties.due_date && (
@ -276,6 +278,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
issue={issue} issue={issue}
partialUpdateIssue={partialUpdateIssue} partialUpdateIssue={partialUpdateIssue}
isNotAllowed={isNotAllowed} isNotAllowed={isNotAllowed}
selfPositioned
/> />
)} )}
</div> </div>

View File

@ -29,7 +29,7 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
{links.map((link) => ( {links.map((link) => (
<div key={link.id} className="relative"> <div key={link.id} className="relative">
{!isNotAllowed && ( {!isNotAllowed && (
<div className="absolute top-1.5 right-1.5 z-10 flex items-center gap-1"> <div className="absolute top-1.5 right-1.5 z-[1] flex items-center gap-1">
<Link href={link.url}> <Link href={link.url}>
<a <a
className="grid h-7 w-7 place-items-center rounded bg-gray-100 p-1 outline-none" className="grid h-7 w-7 place-items-center rounded bg-gray-100 p-1 outline-none"
@ -56,8 +56,8 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
<h5 className="w-4/5">{link.title}</h5> <h5 className="w-4/5">{link.title}</h5>
<p className="mt-0.5 text-gray-500"> <p className="mt-0.5 text-gray-500">
Added {timeAgo(link.created_at)} Added {timeAgo(link.created_at)}
<br /> {/* <br />
by {link.created_by_detail.email} by {link.created_by_detail.email} */}
</p> </p>
</div> </div>
</a> </a>

View File

@ -18,14 +18,14 @@ import { PROJECT_MEMBERS } from "constants/fetch-keys";
type Props = { type Props = {
issue: IIssue; issue: IIssue;
partialUpdateIssue: (formData: Partial<IIssue>) => void; partialUpdateIssue: (formData: Partial<IIssue>) => void;
position?: "left" | "right"; selfPositioned?: boolean;
isNotAllowed: boolean; isNotAllowed: boolean;
}; };
export const ViewAssigneeSelect: React.FC<Props> = ({ export const ViewAssigneeSelect: React.FC<Props> = ({
issue, issue,
partialUpdateIssue, partialUpdateIssue,
position = "right", selfPositioned = false,
isNotAllowed, isNotAllowed,
}) => { }) => {
const router = useRouter(); const router = useRouter();
@ -50,7 +50,7 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
partialUpdateIssue({ assignees_list: newData }); partialUpdateIssue({ assignees_list: newData });
}} }}
className="group relative flex-shrink-0" className={`group ${!selfPositioned ? "relative" : ""} flex-shrink-0`}
disabled={isNotAllowed} disabled={isNotAllowed}
> >
{({ open }) => ( {({ open }) => (
@ -86,11 +86,7 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options <Listbox.Options className="absolute right-0 z-10 mt-1 max-h-48 overflow-auto rounded-md bg-white py-1 text-xs shadow-lg min-w-full ring-1 ring-black ring-opacity-5 focus:outline-none">
className={`absolute z-10 mt-1 max-h-48 overflow-auto rounded-md bg-white py-1 text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
position === "left" ? "left-0" : "right-0"
}`}
>
{members?.map((member) => ( {members?.map((member) => (
<Listbox.Option <Listbox.Option
key={member.member.id} key={member.member.id}

View File

@ -12,14 +12,14 @@ import { PRIORITIES } from "constants/project";
type Props = { type Props = {
issue: IIssue; issue: IIssue;
partialUpdateIssue: (formData: Partial<IIssue>) => void; partialUpdateIssue: (formData: Partial<IIssue>) => void;
position?: "left" | "right"; selfPositioned?: boolean;
isNotAllowed: boolean; isNotAllowed: boolean;
}; };
export const ViewPrioritySelect: React.FC<Props> = ({ export const ViewPrioritySelect: React.FC<Props> = ({
issue, issue,
partialUpdateIssue, partialUpdateIssue,
position = "right", selfPositioned = false,
isNotAllowed, isNotAllowed,
}) => ( }) => (
<CustomSelect <CustomSelect
@ -53,6 +53,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
} border-none`} } border-none`}
noChevron noChevron
disabled={isNotAllowed} disabled={isNotAllowed}
selfPositioned={selfPositioned}
> >
{PRIORITIES?.map((priority) => ( {PRIORITIES?.map((priority) => (
<CustomSelect.Option key={priority} value={priority} className="capitalize"> <CustomSelect.Option key={priority} value={priority} className="capitalize">

View File

@ -17,14 +17,14 @@ import { STATE_LIST } from "constants/fetch-keys";
type Props = { type Props = {
issue: IIssue; issue: IIssue;
partialUpdateIssue: (formData: Partial<IIssue>) => void; partialUpdateIssue: (formData: Partial<IIssue>) => void;
position?: "left" | "right"; selfPositioned?: boolean;
isNotAllowed: boolean; isNotAllowed: boolean;
}; };
export const ViewStateSelect: React.FC<Props> = ({ export const ViewStateSelect: React.FC<Props> = ({
issue, issue,
partialUpdateIssue, partialUpdateIssue,
position = "right", selfPositioned = false,
isNotAllowed, isNotAllowed,
}) => { }) => {
const router = useRouter(); const router = useRouter();
@ -67,6 +67,7 @@ export const ViewStateSelect: React.FC<Props> = ({
maxHeight="md" maxHeight="md"
noChevron noChevron
disabled={isNotAllowed} disabled={isNotAllowed}
selfPositioned={selfPositioned}
> >
{states?.map((state) => ( {states?.map((state) => (
<CustomSelect.Option key={state.id} value={state.id}> <CustomSelect.Option key={state.id} value={state.id}>

View File

@ -8,7 +8,7 @@ import workspaceService from "services/workspace.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { Button } from "components/ui"; import { Button, Loader } from "components/ui";
// icons // icons
import GithubLogo from "public/logos/github-black.png"; import GithubLogo from "public/logos/github-black.png";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
@ -105,38 +105,49 @@ const OAuthPopUp = ({ integration }: any) => {
<div> <div>
<h3 className="flex items-center gap-4 font-semibold text-xl"> <h3 className="flex items-center gap-4 font-semibold text-xl">
{integration.title} {integration.title}
{isInstalled ? ( {workspaceIntegrations ? (
<span className="flex items-center text-green-500 font-normal text-sm gap-1"> isInstalled ? (
<span className="h-1.5 w-1.5 bg-green-500 flex-shrink-0 rounded-full" /> Installed <span className="flex items-center text-green-500 font-normal text-sm gap-1">
</span> <span className="h-1.5 w-1.5 bg-green-500 flex-shrink-0 rounded-full" /> Installed
) : ( </span>
<span className="flex items-center text-gray-400 font-normal text-sm gap-1"> ) : (
<span className="h-1.5 w-1.5 bg-gray-400 flex-shrink-0 rounded-full" /> Not <span className="flex items-center text-gray-400 font-normal text-sm gap-1">
Installed <span className="h-1.5 w-1.5 bg-gray-400 flex-shrink-0 rounded-full" /> Not
</span> Installed
)} </span>
)
) : null}
</h3> </h3>
<p className="text-gray-400 text-sm"> <p className="text-gray-400 text-sm">
{isInstalled {workspaceIntegrations
? "Activate GitHub integrations on individual projects to sync with specific repositories." ? isInstalled
: "Connect with GitHub with your Plane workspace to sync project issues."} ? "Activate GitHub integrations on individual projects to sync with specific repositories."
: "Connect with GitHub with your Plane workspace to sync project issues."
: "Loading..."}
</p> </p>
</div> </div>
</div> </div>
{isInstalled ? (
<Button {workspaceIntegrations ? (
theme="danger" isInstalled ? (
size="rg" <Button
className="text-xs" theme="danger"
onClick={handleRemoveIntegration} size="rg"
disabled={deletingIntegration} className="text-xs"
> onClick={handleRemoveIntegration}
{deletingIntegration ? "Removing..." : "Remove installation"} disabled={deletingIntegration}
</Button> >
{deletingIntegration ? "Removing..." : "Remove installation"}
</Button>
) : (
<Button theme="secondary" size="rg" className="text-xs" onClick={startAuth}>
Add installation
</Button>
)
) : ( ) : (
<Button theme="secondary" size="rg" className="text-xs" onClick={startAuth}> <Loader>
Add installation <Loader.Item height="35px" width="150px" />
</Button> </Loader>
)} )}
</div> </div>
); );

View File

@ -15,7 +15,9 @@ type CustomSelectProps = {
input?: boolean; input?: boolean;
noChevron?: boolean; noChevron?: boolean;
buttonClassName?: string; buttonClassName?: string;
optionsClassName?: string;
disabled?: boolean; disabled?: boolean;
selfPositioned?: boolean;
}; };
const CustomSelect = ({ const CustomSelect = ({
@ -29,13 +31,15 @@ const CustomSelect = ({
input = false, input = false,
noChevron = false, noChevron = false,
buttonClassName = "", buttonClassName = "",
optionsClassName = "",
disabled = false, disabled = false,
selfPositioned = false,
}: CustomSelectProps) => ( }: CustomSelectProps) => (
<Listbox <Listbox
as="div" as="div"
value={value} value={value}
onChange={onChange} onChange={onChange}
className="relative flex-shrink-0 text-left" className={`${!selfPositioned ? "relative" : ""} flex-shrink-0 text-left`}
disabled={disabled} disabled={disabled}
> >
<div> <div>
@ -67,8 +71,8 @@ const CustomSelect = ({
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Listbox.Options <Listbox.Options
className={`absolute right-0 z-10 mt-1 origin-top-right overflow-y-auto rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${ className={`${optionsClassName} absolute right-0 z-10 mt-1 origin-top-right overflow-y-auto rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
width === "auto" ? "min-w-full whitespace-nowrap" : "w-56" width === "auto" ? "min-w-full whitespace-nowrap" : width
} ${input ? "max-h-48" : ""} ${ } ${input ? "max-h-48" : ""} ${
maxHeight === "lg" maxHeight === "lg"
? "max-h-60" ? "max-h-60"
@ -97,9 +101,9 @@ const Option: React.FC<OptionProps> = ({ children, value, className }) => (
<Listbox.Option <Listbox.Option
value={value} value={value}
className={({ active, selected }) => className={({ active, selected }) =>
`${active || selected ? "bg-indigo-50" : ""} ${ `${className} ${active || selected ? "bg-indigo-50" : ""} ${
selected ? "font-medium" : "" selected ? "font-medium" : ""
} relative flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900 ${className}` } relative flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900`
} }
> >
{children} {children}

View File

@ -4,6 +4,9 @@ export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, "
export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
export const truncateText = (str: string, length: number) =>
str.length > length ? `${str.substring(0, length)}...` : str;
export const createSimilarString = (str: string) => { export const createSimilarString = (str: string) => {
const shuffled = str const shuffled = str
.split("") .split("")

View File

@ -23,6 +23,8 @@ import projectService from "services/project.service";
// ui // ui
import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui"; import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// helpers
import { truncateText } from "helpers/string.helper";
// types // types
import { CycleIssueResponse, UserAuth } from "types"; import { CycleIssueResponse, UserAuth } from "types";
// fetch-keys // fetch-keys
@ -135,7 +137,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
label={ label={
<> <>
<CyclesIcon className="h-3 w-3" /> <CyclesIcon className="h-3 w-3" />
{cycleDetails?.name} {cycleDetails?.name && truncateText(cycleDetails.name, 40)}
</> </>
} }
className="ml-1.5" className="ml-1.5"
@ -147,7 +149,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
renderAs="a" renderAs="a"
href={`/${workspaceSlug}/projects/${activeProject?.id}/cycles/${cycle.id}`} href={`/${workspaceSlug}/projects/${activeProject?.id}/cycles/${cycle.id}`}
> >
{cycle.name} {truncateText(cycle.name, 40)}
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
))} ))}
</CustomMenu> </CustomMenu>

View File

@ -27,6 +27,8 @@ import { ModuleDetailsSidebar } from "components/modules";
// ui // ui
import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui"; import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// helpers
import { truncateText } from "helpers/string.helper";
// types // types
import { IModule, ModuleIssueResponse, UserAuth } from "types"; import { IModule, ModuleIssueResponse, UserAuth } from "types";
@ -130,7 +132,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
label={ label={
<> <>
<RectangleGroupIcon className="h-3 w-3" /> <RectangleGroupIcon className="h-3 w-3" />
{moduleDetails?.name} {moduleDetails?.name && truncateText(moduleDetails.name, 40)}
</> </>
} }
className="ml-1.5" className="ml-1.5"
@ -142,7 +144,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
renderAs="a" renderAs="a"
href={`/${workspaceSlug}/projects/${projectId}/modules/${module.id}`} href={`/${workspaceSlug}/projects/${projectId}/modules/${module.id}`}
> >
{module.name} {truncateText(module.name, 40)}
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
))} ))}
</CustomMenu> </CustomMenu>

View File

@ -13,6 +13,7 @@ import AppLayout from "layouts/app-layout";
// componentss // componentss
import OAuthPopUp from "components/popup"; import OAuthPopUp from "components/popup";
// ui // ui
import { Loader } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// types // types
import type { NextPage, GetServerSideProps } from "next"; import type { NextPage, GetServerSideProps } from "next";
@ -54,13 +55,20 @@ const WorkspaceIntegrations: NextPage<UserAuth> = (props) => {
<p className="mt-4 text-sm text-gray-500">Manage the workspace integrations.</p> <p className="mt-4 text-sm text-gray-500">Manage the workspace integrations.</p>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
{integrations?.map((integration) => ( {integrations ? (
<OAuthPopUp integrations.map((integration) => (
key={integration.id} <OAuthPopUp
workspaceSlug={workspaceSlug} key={integration.id}
integration={integration} workspaceSlug={workspaceSlug}
/> integration={integration}
))} />
))
) : (
<Loader className="space-y-5">
<Loader.Item height="60px" />
<Loader.Item height="60px" />
</Loader>
)}
</div> </div>
</section> </section>
</AppLayout> </AppLayout>