feat: extend skeleton loaders

This commit is contained in:
Henry
2024-05-05 15:12:20 +01:00
parent 8da27d3484
commit 23faa66d48
9 changed files with 334 additions and 269 deletions

View File

@@ -15,6 +15,7 @@ import WorkspaceMenu from "~/components/WorkspaceMenu";
interface SideNavigationProps { interface SideNavigationProps {
user: UserType; user: UserType;
isLoading: boolean;
} }
interface UserType { interface UserType {
@@ -22,7 +23,10 @@ interface UserType {
image?: string | null | undefined; image?: string | null | undefined;
} }
export default function SideNavigation({ user }: SideNavigationProps) { export default function SideNavigation({
user,
isLoading,
}: SideNavigationProps) {
const pathname = usePathname(); const pathname = usePathname();
const { theme } = useTheme(); const { theme } = useTheme();
@@ -65,6 +69,7 @@ export default function SideNavigation({ user }: SideNavigationProps) {
<UserMenu <UserMenu
email={user?.email ?? ""} email={user?.email ?? ""}
imageUrl={user?.image ?? undefined} imageUrl={user?.image ?? undefined}
isLoading={isLoading}
/> />
</nav> </nav>
</> </>

View File

@@ -7,13 +7,18 @@ import createClient from "~/utils/supabase/client";
interface UserMenuProps { interface UserMenuProps {
imageUrl: string | undefined; imageUrl: string | undefined;
email: string; email: string;
isLoading: boolean;
} }
function classNames(...classes: string[]): string { function classNames(...classes: string[]): string {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(" ");
} }
export default function UserMenu({ imageUrl, email }: UserMenuProps) { export default function UserMenu({
imageUrl,
email,
isLoading,
}: UserMenuProps) {
const router = useRouter(); const router = useRouter();
const { theme, switchTheme } = useTheme(); const { theme, switchTheme } = useTheme();
@@ -28,28 +33,35 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
return ( return (
<Menu as="div" className="relative inline-block w-full text-left"> <Menu as="div" className="relative inline-block w-full text-left">
<div> <div>
<Menu.Button className="flex w-full items-center rounded-md p-1.5 text-neutral-900 hover:bg-light-200 dark:text-dark-900 dark:hover:bg-dark-200 dark:hover:text-dark-1000"> {isLoading ? (
{imageUrl ? ( <div className="flex">
<Image <div className="h-[30px] w-[30px] animate-pulse rounded-full bg-light-200 dark:bg-dark-200" />
src={imageUrl ?? ""} <div className="mx-2 h-[30px] w-[175px] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
className="h-8 w-8 rounded-full bg-gray-50" </div>
width={30} ) : (
height={30} <Menu.Button className="flex w-full items-center rounded-md p-1.5 text-neutral-900 hover:bg-light-200 dark:text-dark-900 dark:hover:bg-dark-200 dark:hover:text-dark-1000">
alt="" {imageUrl ? (
/> <Image
) : ( src={imageUrl ?? ""}
<span className="inline-block h-6 w-6 overflow-hidden rounded-full bg-light-400 dark:bg-dark-400"> className="h-8 w-8 rounded-full bg-gray-50"
<svg width={30}
className="h-full w-full text-dark-700" height={30}
fill="currentColor" alt=""
viewBox="0 0 24 24" />
> ) : (
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" /> <span className="inline-block h-6 w-6 overflow-hidden rounded-full bg-light-400 dark:bg-dark-400">
</svg> <svg
</span> className="h-full w-full text-dark-700"
)} fill="currentColor"
<span className="mx-2 truncate text-sm">{email}</span> viewBox="0 0 24 24"
</Menu.Button> >
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
)}
<span className="mx-2 truncate text-sm">{email}</span>
</Menu.Button>
)}
</div> </div>
<Transition <Transition

View File

@@ -7,22 +7,30 @@ import { useWorkspace } from "~/providers/workspace";
import { HiCheck } from "react-icons/hi2"; import { HiCheck } from "react-icons/hi2";
export default function WorkspaceMenu() { export default function WorkspaceMenu() {
const { workspace, availableWorkspaces, switchWorkspace } = useWorkspace(); const { workspace, isLoading, availableWorkspaces, switchWorkspace } =
useWorkspace();
const { openModal } = useModal(); const { openModal } = useModal();
return ( return (
<Menu as="div" className="relative inline-block w-full pb-3 text-left"> <Menu as="div" className="relative inline-block w-full pb-3 text-left">
<div> <div>
<Menu.Button className="mb-1 flex w-full items-center rounded-[5px] p-1.5 hover:bg-light-200 dark:hover:bg-dark-200"> {isLoading ? (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-[5px] bg-indigo-700"> <div className="mb-1 flex p-1.5">
<span className="text-xs font-bold leading-none text-white"> <div className="h-6 w-6 animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-200" />
{workspace?.name.charAt(0).toUpperCase()} <div className="ml-2 h-6 w-[150px] animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-200" />
</div>
) : (
<Menu.Button className="mb-1 flex w-full items-center rounded-[5px] p-1.5 hover:bg-light-200 dark:hover:bg-dark-200">
<span className="inline-flex h-6 w-6 items-center justify-center rounded-[5px] bg-indigo-700">
<span className="text-xs font-bold leading-none text-white">
{workspace?.name.charAt(0).toUpperCase()}
</span>
</span> </span>
</span> <span className="ml-2 text-sm font-bold text-neutral-900 dark:text-dark-1000">
<span className="ml-2 text-sm font-bold text-neutral-900 dark:text-dark-1000"> {workspace?.name}
{workspace?.name} </span>
</span> </Menu.Button>
</Menu.Button> )}
</div> </div>
<Transition <Transition

View File

@@ -3,7 +3,7 @@ import SideNavigation from "./SideNavigation";
import FeedbackButton from "./FeedbackButton"; import FeedbackButton from "./FeedbackButton";
export default function Dashboard(props: { children: React.ReactNode }) { export default function Dashboard(props: { children: React.ReactNode }) {
const { data } = api.auth.getUser.useQuery(); const { data, isLoading } = api.auth.getUser.useQuery();
return ( return (
<div className="flex h-screen flex-col items-center bg-light-100 dark:bg-dark-50"> <div className="flex h-screen flex-col items-center bg-light-100 dark:bg-dark-50">
@@ -17,7 +17,7 @@ export default function Dashboard(props: { children: React.ReactNode }) {
</div> </div>
<div className="flex h-full w-full"> <div className="flex h-full w-full">
<SideNavigation user={{ email: data?.email }} /> <SideNavigation user={{ email: data?.email }} isLoading={isLoading} />
<div className="w-full overflow-hidden">{props.children}</div> <div className="w-full overflow-hidden">{props.children}</div>
</div> </div>
</div> </div>

View File

@@ -12,6 +12,7 @@ import { useRouter } from "next/navigation";
interface WorkspaceContextProps { interface WorkspaceContextProps {
workspace: Workspace; workspace: Workspace;
isLoading: boolean;
switchWorkspace: (_workspace: Workspace) => void; switchWorkspace: (_workspace: Workspace) => void;
availableWorkspaces: Workspace[]; availableWorkspaces: Workspace[];
} }
@@ -41,7 +42,7 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
initialAvailableWorkspaces, initialAvailableWorkspaces,
); );
const { data } = api.workspace.all.useQuery(); const { data, isLoading } = api.workspace.all.useQuery();
const switchWorkspace = (_workspace: Workspace) => { const switchWorkspace = (_workspace: Workspace) => {
localStorage.setItem("workspacePublicId", _workspace.publicId); localStorage.setItem("workspacePublicId", _workspace.publicId);
@@ -97,7 +98,7 @@ export const WorkspaceProvider: React.FC<{ children: ReactNode }> = ({
return ( return (
<WorkspaceContext.Provider <WorkspaceContext.Provider
value={{ workspace, availableWorkspaces, switchWorkspace }} value={{ workspace, isLoading, availableWorkspaces, switchWorkspace }}
> >
{children} {children}
</WorkspaceContext.Provider> </WorkspaceContext.Provider>

View File

@@ -14,11 +14,13 @@ interface LabelSelectorProps {
selected: boolean; selected: boolean;
colourCode: string; colourCode: string;
}[]; }[];
isLoading: boolean;
} }
export default function LabelSelector({ export default function LabelSelector({
cardPublicId, cardPublicId,
labels, labels,
isLoading,
}: LabelSelectorProps) { }: LabelSelectorProps) {
const { openModal } = useModal(); const { openModal } = useModal();
const utils = api.useUtils(); const utils = api.useUtils();
@@ -47,104 +49,110 @@ export default function LabelSelector({
return ( return (
<> <>
<Menu {isLoading ? (
as="div" <div className="flex w-full">
className="relative flex w-full flex-wrap items-center text-left" <div className="h-full w-[175px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
> </div>
{selectedLabels.length ? ( ) : (
<> <Menu
{selectedLabels.map((label) => ( as="div"
<Menu.Button className="relative flex w-full flex-wrap items-center text-left"
key={label.publicId} >
className="my-1 mr-2 inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[12px] font-medium text-light-800 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800" {selectedLabels.length ? (
> <>
<svg {selectedLabels.map((label) => (
fill={label.colourCode} <Menu.Button
className="h-2 w-2" key={label.publicId}
viewBox="0 0 6 6" className="my-1 mr-2 inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[12px] font-medium text-light-800 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800"
aria-hidden="true"
> >
<circle cx={3} cy={3} r={3} /> <svg
</svg> fill={label.colourCode}
<div>{label.name}</div> className="h-2 w-2"
viewBox="0 0 6 6"
aria-hidden="true"
>
<circle cx={3} cy={3} r={3} />
</svg>
<div>{label.name}</div>
</Menu.Button>
))}
<Menu.Button className="my-1 inline-flex w-fit items-center gap-x-1.5 rounded-full py-1 pl-2 pr-4 text-[12px] font-medium text-dark-800 ring-inset ring-dark-800 hover:bg-light-400 dark:hover:bg-dark-200">
<HiMiniPlus size={16} />
Add label
</Menu.Button> </Menu.Button>
))} </>
<Menu.Button className="my-1 inline-flex w-fit items-center gap-x-1.5 rounded-full py-1 pl-2 pr-4 text-[12px] font-medium text-dark-800 ring-inset ring-dark-800 hover:bg-light-400 dark:hover:bg-dark-200"> ) : (
<HiMiniPlus size={16} /> <Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
<HiMiniPlus size={22} className="pr-2" />
Add label Add label
</Menu.Button> </Menu.Button>
</> )}
) : (
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
<HiMiniPlus size={22} className="pr-2" />
Add label
</Menu.Button>
)}
<Transition <Transition
as={Fragment} as={Fragment}
enter="transition ease-out duration-100" enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95" enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100" enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75" leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="p-2"> <div className="p-2">
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
{labels?.map((label) => ( {labels?.map((label) => (
<Menu.Item key={label.publicId}> <Menu.Item key={label.publicId}>
{() => ( {() => (
<div <div
key={label.publicId} key={label.publicId}
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300" className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
await formik.setFieldValue( await formik.setFieldValue(
label.publicId, label.publicId,
!formik.values[label.publicId], !formik.values[label.publicId],
); );
addOrRemoveLabel.mutate({ addOrRemoveLabel.mutate({
cardPublicId, cardPublicId,
labelPublicId: label.publicId, labelPublicId: label.publicId,
}); });
await formik.submitForm(); await formik.submitForm();
}} }}
>
<input
id={label.publicId}
name={label.publicId}
type="checkbox"
className="h-[14px] w-[14px] rounded bg-transparent"
onClick={(event) => event.stopPropagation()}
onChange={formik.handleChange}
checked={formik.values[label.publicId]}
/>
<label
htmlFor={label.publicId}
className="ml-3 text-sm"
> >
{label.name} <input
</label> id={label.publicId}
</div> name={label.publicId}
)} type="checkbox"
</Menu.Item> className="h-[14px] w-[14px] rounded bg-transparent"
))} onClick={(event) => event.stopPropagation()}
<button onChange={formik.handleChange}
onClick={() => openModal("NEW_LABEL")} checked={formik.values[label.publicId]}
className="flex w-full items-center rounded-[5px] p-1.5 px-2 text-sm hover:bg-light-200 dark:hover:bg-dark-300" />
> <label
<HiMiniPlus size={22} className="pr-2" /> htmlFor={label.publicId}
Create new label className="ml-3 text-sm"
</button> >
</form> {label.name}
</div> </label>
</Menu.Items> </div>
</Transition> )}
</Menu> </Menu.Item>
))}
<button
onClick={() => openModal("NEW_LABEL")}
className="flex w-full items-center rounded-[5px] p-1.5 px-2 text-sm hover:bg-light-200 dark:hover:bg-dark-300"
>
<HiMiniPlus size={22} className="pr-2" />
Create new label
</button>
</form>
</div>
</Menu.Items>
</Transition>
</Menu>
)}
</> </>
); );
} }

View File

@@ -10,11 +10,13 @@ interface ListSelectorProps {
name: string; name: string;
selected: boolean; selected: boolean;
}[]; }[];
isLoading: boolean;
} }
export default function ListSelector({ export default function ListSelector({
cardPublicId, cardPublicId,
lists, lists,
isLoading,
}: ListSelectorProps) { }: ListSelectorProps) {
const utils = api.useUtils(); const utils = api.useUtils();
@@ -42,66 +44,72 @@ export default function ListSelector({
return ( return (
<> <>
<Menu {isLoading ? (
as="div" <div className="flex w-full">
className="relative flex w-full flex-wrap items-center text-left" <div className="h-full w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
> </div>
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:border-light-300 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200"> ) : (
{selectedList?.name} <Menu
</Menu.Button> as="div"
className="relative flex w-full flex-wrap items-center text-left"
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:border-light-300 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
<div className="p-2"> {selectedList?.name}
<form onSubmit={formik.handleSubmit}> </Menu.Button>
{lists?.map((list) => (
<Menu.Item key={list.publicId}>
<div
key={list.publicId}
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={async (e) => {
e.preventDefault();
await formik.setFieldValue(
list.publicId,
!formik.values[list.publicId],
);
updateCardList.mutate({ <Transition
cardId: cardPublicId, as={Fragment}
newListId: list.publicId, enter="transition ease-out duration-100"
}); enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="p-2">
<form onSubmit={formik.handleSubmit}>
{lists?.map((list) => (
<Menu.Item key={list.publicId}>
<div
key={list.publicId}
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={async (e) => {
e.preventDefault();
await formik.setFieldValue(
list.publicId,
!formik.values[list.publicId],
);
await formik.submitForm(); updateCardList.mutate({
}} cardId: cardPublicId,
> newListId: list.publicId,
<input });
id={list.publicId}
name={list.publicId} await formik.submitForm();
type="checkbox" }}
className="h-[14px] w-[14px] rounded bg-transparent" >
onClick={(event) => event.stopPropagation()} <input
onChange={formik.handleChange} id={list.publicId}
checked={formik.values[list.publicId]} name={list.publicId}
/> type="checkbox"
<label htmlFor={list.publicId} className="ml-3 text-sm"> className="h-[14px] w-[14px] rounded bg-transparent"
{list.name} onClick={(event) => event.stopPropagation()}
</label> onChange={formik.handleChange}
</div> checked={formik.values[list.publicId]}
</Menu.Item> />
))} <label htmlFor={list.publicId} className="ml-3 text-sm">
</form> {list.name}
</div> </label>
</Menu.Items> </div>
</Transition> </Menu.Item>
</Menu> ))}
</form>
</div>
</Menu.Items>
</Transition>
</Menu>
)}
</> </>
); );
} }

View File

@@ -14,11 +14,13 @@ interface MemberSelectorProps {
}; };
selected: boolean; selected: boolean;
}[]; }[];
isLoading: boolean;
} }
export default function LabelSelector({ export default function LabelSelector({
cardPublicId, cardPublicId,
members, members,
isLoading,
}: MemberSelectorProps) { }: MemberSelectorProps) {
const utils = api.useUtils(); const utils = api.useUtils();
@@ -46,89 +48,98 @@ export default function LabelSelector({
return ( return (
<> <>
<Menu {isLoading ? (
as="div" <div className="flex w-full">
className="relative flex w-full flex-wrap items-center text-left" <div className="h-full w-[125px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
> </div>
<Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200"> ) : (
{selectedMembers.length ? ( <Menu
<div className="isolate flex -space-x-1 overflow-hidden"> as="div"
{selectedMembers.map((member) => ( className="relative flex w-full flex-wrap items-center text-left"
<span
key={member.publicId}
className="relative z-30 inline-flex h-6 w-6 items-center justify-center rounded-full bg-gray-500 ring-1 ring-light-200 dark:ring-dark-100"
>
<span className="text-[10px] font-medium leading-none text-white">
{member.user?.name
? member.user?.name
.split(" ")
.map((namePart) => namePart.charAt(0).toUpperCase())
.join("")
: null}
</span>
</span>
))}
</div>
) : (
<>
<HiMiniPlus size={22} className="pr-2" />
{"Add member"}
</>
)}
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200"> <Menu.Button className="flex h-full w-full items-center rounded-[5px] border-[1px] border-light-200 pl-2 text-left text-sm text-neutral-900 hover:bg-light-300 dark:border-dark-100 dark:text-dark-1000 dark:hover:border-dark-300 dark:hover:bg-dark-200">
<div className="p-2"> {selectedMembers.length ? (
<form onSubmit={formik.handleSubmit}> <div className="isolate flex -space-x-1 overflow-hidden">
{members?.map((member) => ( {selectedMembers.map((member) => (
<Menu.Item key={member.publicId}> <span
<div key={member.publicId}
key={member.publicId} className="relative z-30 inline-flex h-6 w-6 items-center justify-center rounded-full bg-gray-500 ring-1 ring-light-200 dark:ring-dark-100"
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300" >
onClick={async (e) => { <span className="text-[10px] font-medium leading-none text-white">
e.preventDefault(); {member.user?.name
await formik.setFieldValue( ? member.user?.name
member.publicId, .split(" ")
!formik.values[member.publicId], .map((namePart) => namePart.charAt(0).toUpperCase())
); .join("")
: null}
addOrRemoveMember.mutate({ </span>
cardPublicId, </span>
workspaceMemberPublicId: member.publicId,
});
await formik.submitForm();
}}
>
<input
id={member.publicId}
name={member.publicId}
type="checkbox"
className="h-[14px] w-[14px] rounded bg-transparent"
onClick={(event) => event.stopPropagation()}
onChange={formik.handleChange}
checked={formik.values[member.publicId]}
/>
<label htmlFor={member.publicId} className="ml-3 text-sm">
{member?.user?.name}
</label>
</div>
</Menu.Item>
))} ))}
</form> </div>
</div> ) : (
</Menu.Items> <>
</Transition> <HiMiniPlus size={22} className="pr-2" />
</Menu> {"Add member"}
</>
)}
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-[200px] top-[30px] z-10 mt-2 w-56 origin-top-right rounded-md border-[1px] border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="p-2">
<form onSubmit={formik.handleSubmit}>
{members?.map((member) => (
<Menu.Item key={member.publicId}>
<div
key={member.publicId}
className="flex items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={async (e) => {
e.preventDefault();
await formik.setFieldValue(
member.publicId,
!formik.values[member.publicId],
);
addOrRemoveMember.mutate({
cardPublicId,
workspaceMemberPublicId: member.publicId,
});
await formik.submitForm();
}}
>
<input
id={member.publicId}
name={member.publicId}
type="checkbox"
className="h-[14px] w-[14px] rounded bg-transparent"
onClick={(event) => event.stopPropagation()}
onChange={formik.handleChange}
checked={formik.values[member.publicId]}
/>
<label
htmlFor={member.publicId}
className="ml-3 text-sm"
>
{member?.user?.name}
</label>
</div>
</Menu.Item>
))}
</form>
</div>
</Menu.Items>
</Transition>
</Menu>
)}
</> </>
); );
} }

View File

@@ -152,15 +152,27 @@ export default function CardPage() {
<div className="min-w-[325px] border-l-[1px] border-light-600 bg-light-200 p-8 text-light-900 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900"> <div className="min-w-[325px] border-l-[1px] border-light-600 bg-light-200 p-8 text-light-900 dark:border-dark-400 dark:bg-dark-100 dark:text-dark-900">
<div className="mb-4 flex w-full"> <div className="mb-4 flex w-full">
<p className="my-2 w-[100px] text-sm">List</p> <p className="my-2 w-[100px] text-sm">List</p>
<ListSelector cardPublicId={cardId} lists={formattedLists} /> <ListSelector
cardPublicId={cardId}
lists={formattedLists}
isLoading={isLoading}
/>
</div> </div>
<div className="mb-4 flex w-full"> <div className="mb-4 flex w-full">
<p className="my-2 w-[100px] text-sm">Labels</p> <p className="my-2 w-[100px] text-sm">Labels</p>
<LabelSelector cardPublicId={cardId} labels={formattedLabels} /> <LabelSelector
cardPublicId={cardId}
labels={formattedLabels}
isLoading={isLoading}
/>
</div> </div>
<div className="flex w-full"> <div className="flex w-full">
<p className="my-2 w-[100px] text-sm">Members</p> <p className="my-2 w-[100px] text-sm">Members</p>
<MemberSelector cardPublicId={cardId} members={formattedMembers} /> <MemberSelector
cardPublicId={cardId}
members={formattedMembers}
isLoading={isLoading}
/>
</div> </div>
</div> </div>