fix: bugs fixed

This commit is contained in:
Aaryan Khandelwal 2022-12-19 20:30:09 +05:30
parent c4079c4e0c
commit a2db04f9ff
35 changed files with 719 additions and 569 deletions

View File

@ -0,0 +1,5 @@
const SingleBoard = () => {
return <></>;
};
export default SingleBoard;

View File

@ -117,7 +117,7 @@ const SingleIssue: React.FC<Props> = ({
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none"> <Listbox.Options className="absolute z-20 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
{PRIORITIES?.map((priority) => ( {PRIORITIES?.map((priority) => (
<Listbox.Option <Listbox.Option
key={priority} key={priority}
@ -186,18 +186,24 @@ const SingleIssue: React.FC<Props> = ({
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none"> <Listbox.Options className="absolute z-20 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
{states?.map((state) => ( {states?.map((state) => (
<Listbox.Option <Listbox.Option
key={state.id} key={state.id}
className={({ active }) => className={({ active }) =>
classNames( classNames(
active ? "bg-indigo-50" : "bg-white", active ? "bg-indigo-50" : "bg-white",
"cursor-pointer select-none px-3 py-2" "flex items-center gap-2 cursor-pointer select-none px-3 py-2"
) )
} }
value={state.id} value={state.id}
> >
<span
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: state.color,
}}
></span>
{addSpaceIfCamelCase(state.name)} {addSpaceIfCamelCase(state.name)}
</Listbox.Option> </Listbox.Option>
))} ))}
@ -316,7 +322,7 @@ const SingleIssue: React.FC<Props> = ({
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute left-0 z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none"> <Listbox.Options className="absolute left-0 z-20 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
{people?.map((person) => ( {people?.map((person) => (
<Listbox.Option <Listbox.Option
key={person.id} key={person.id}

View File

@ -18,6 +18,14 @@ type Props = {
removeIssueFromCycle: (bridgeId: string) => void; removeIssueFromCycle: (bridgeId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void; partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| undefined
>
>;
}; };
const CyclesBoardView: React.FC<Props> = ({ const CyclesBoardView: React.FC<Props> = ({
@ -30,6 +38,7 @@ const CyclesBoardView: React.FC<Props> = ({
removeIssueFromCycle, removeIssueFromCycle,
partialUpdateIssue, partialUpdateIssue,
handleDeleteIssue, handleDeleteIssue,
setPreloadedData,
}) => { }) => {
const { states } = useUser(); const { states } = useUser();
@ -63,6 +72,12 @@ const CyclesBoardView: React.FC<Props> = ({
openCreateIssueModal={openCreateIssueModal} openCreateIssueModal={openCreateIssueModal}
partialUpdateIssue={partialUpdateIssue} partialUpdateIssue={partialUpdateIssue}
handleDeleteIssue={handleDeleteIssue} handleDeleteIssue={handleDeleteIssue}
setPreloadedData={setPreloadedData}
stateId={
selectedGroup === "state_detail.name"
? states?.find((s) => s.name === singleGroup)?.id ?? null
: null
}
/> />
))} ))}
</div> </div>

View File

@ -7,7 +7,7 @@ import workspaceService from "lib/services/workspace.service";
// hooks // hooks
import useUser from "lib/hooks/useUser"; import useUser from "lib/hooks/useUser";
// components // components
import SingleIssue from "components/project/common/board-view/single-issue"; import SingleIssue from "components/common/board-view/single-issue";
// headless ui // headless ui
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
// ui // ui
@ -35,6 +35,15 @@ type Props = {
removeIssueFromCycle: (bridgeId: string) => void; removeIssueFromCycle: (bridgeId: string) => void;
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void; partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>; handleDeleteIssue: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| undefined
>
>;
stateId: string | null;
}; };
const SingleCycleBoard: React.FC<Props> = ({ const SingleCycleBoard: React.FC<Props> = ({
@ -49,6 +58,8 @@ const SingleCycleBoard: React.FC<Props> = ({
removeIssueFromCycle, removeIssueFromCycle,
partialUpdateIssue, partialUpdateIssue,
handleDeleteIssue, handleDeleteIssue,
setPreloadedData,
stateId,
}) => { }) => {
// Collapse/Expand // Collapse/Expand
const [show, setState] = useState(true); const [show, setState] = useState(true);
@ -109,7 +120,18 @@ const SingleCycleBoard: React.FC<Props> = ({
</div> </div>
<CustomMenu width="auto" ellipsis> <CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={() => openCreateIssueModal()}> <CustomMenu.MenuItem
onClick={() => {
openCreateIssueModal();
if (selectedGroup !== null) {
setPreloadedData({
state: stateId !== null ? stateId : undefined,
[selectedGroup]: groupTitle,
actionType: "createIssue",
});
}
}}
>
Create new Create new
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}> <CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
@ -150,55 +172,35 @@ const SingleCycleBoard: React.FC<Props> = ({
); );
})} })}
<Menu as="div" className="relative text-left"> <CustomMenu
<Menu.Button className="flex items-center gap-1 px-2 py-1 rounded hover:bg-gray-100 text-xs font-medium"> label={
<PlusIcon className="h-3 w-3" /> <span className="flex items-center gap-1">
Add issue <PlusIcon className="h-3 w-3" />
</Menu.Button> Add issue
</span>
<Transition }
as={React.Fragment} className="mt-1"
enter="transition ease-out duration-100" optionsPosition="left"
enterFrom="transform opacity-0 scale-95" withoutBorder
enterTo="transform opacity-100 scale-100" >
leave="transition ease-in duration-75" <CustomMenu.MenuItem
leaveFrom="transform opacity-100 scale-100" onClick={() => {
leaveTo="transform opacity-0 scale-95" openCreateIssueModal();
if (selectedGroup !== null) {
setPreloadedData({
state: stateId !== null ? stateId : undefined,
[selectedGroup]: groupTitle,
actionType: "createIssue",
});
}
}}
> >
<Menu.Items className="absolute left-0 z-10 mt-1 rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none whitespace-nowrap"> Create new
<div className="py-1"> </CustomMenu.MenuItem>
<Menu.Item> <CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
{({ active }) => ( Add an existing issue
<button </CustomMenu.MenuItem>
type="button" </CustomMenu>
className={classNames(
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
"block w-full p-2 text-left"
)}
onClick={() => openCreateIssueModal()}
>
Create new
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
type="button"
className={classNames(
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
"block w-full p-2 text-left"
)}
onClick={() => openIssuesListModal()}
>
Add an existing issue
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div> </div>
</div> </div>
</div> </div>

View File

@ -28,13 +28,21 @@ import workspaceService from "lib/services/workspace.service";
type Props = { type Props = {
groupedByIssues: { groupedByIssues: {
[key: string]: IIssue[]; [key: string]: (IIssue & { bridge?: string })[];
}; };
properties: Properties; properties: Properties;
selectedGroup: NestedKeyOf<IIssue> | null; selectedGroup: NestedKeyOf<IIssue> | null;
openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void; openCreateIssueModal: (issue?: IIssue, actionType?: "create" | "edit" | "delete") => void;
openIssuesListModal: () => void; openIssuesListModal: () => void;
removeIssueFromCycle: (bridgeId: string) => void; removeIssueFromCycle: (bridgeId: string) => void;
setPreloadedData: React.Dispatch<
React.SetStateAction<
| (Partial<IIssue> & {
actionType: "createIssue" | "edit" | "delete";
})
| undefined
>
>;
}; };
const CyclesListView: React.FC<Props> = ({ const CyclesListView: React.FC<Props> = ({
@ -44,8 +52,9 @@ const CyclesListView: React.FC<Props> = ({
openIssuesListModal, openIssuesListModal,
properties, properties,
removeIssueFromCycle, removeIssueFromCycle,
setPreloadedData,
}) => { }) => {
const { activeWorkspace, activeProject } = useUser(); const { activeWorkspace, activeProject, states } = useUser();
const { data: people } = useSWR<IWorkspaceMember[]>( const { data: people } = useSWR<IWorkspaceMember[]>(
activeWorkspace ? WORKSPACE_MEMBERS : null, activeWorkspace ? WORKSPACE_MEMBERS : null,
@ -54,273 +63,261 @@ const CyclesListView: React.FC<Props> = ({
return ( return (
<div className="flex flex-col space-y-5"> <div className="flex flex-col space-y-5">
{Object.keys(groupedByIssues).map((singleGroup) => ( {Object.keys(groupedByIssues).map((singleGroup) => {
<Disclosure key={singleGroup} as="div" defaultOpen> const stateId =
{({ open }) => ( selectedGroup === "state_detail.name"
<div className="bg-white rounded-lg"> ? states?.find((s) => s.name === singleGroup)?.id ?? null
<div className="bg-gray-100 px-4 py-3 rounded-t-lg"> : null;
<Disclosure.Button>
<div className="flex items-center gap-x-2">
<span>
<ChevronDownIcon
className={`h-4 w-4 text-gray-500 ${!open ? "transform -rotate-90" : ""}`}
/>
</span>
{selectedGroup !== null ? (
<h2 className="font-medium leading-5 capitalize">
{singleGroup === null || singleGroup === "null"
? selectedGroup === "priority" && "No priority"
: addSpaceIfCamelCase(singleGroup)}
</h2>
) : (
<h2 className="font-medium leading-5">All Issues</h2>
)}
<p className="text-gray-500 text-sm">
{groupedByIssues[singleGroup as keyof IIssue].length}
</p>
</div>
</Disclosure.Button>
</div>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
<div className="divide-y-2">
{groupedByIssues[singleGroup] ? (
groupedByIssues[singleGroup].length > 0 ? (
groupedByIssues[singleGroup].map((issue: IIssue) => {
const assignees = [
...(issue?.assignees_list ?? []),
...(issue?.assignees ?? []),
]?.map((assignee) => {
const tempPerson = people?.find(
(p) => p.member.id === assignee
)?.member;
return { return (
avatar: tempPerson?.avatar, <Disclosure key={singleGroup} as="div" defaultOpen>
first_name: tempPerson?.first_name, {({ open }) => (
email: tempPerson?.email, <div className="bg-white rounded-lg">
}; <div className="bg-gray-100 px-4 py-3 rounded-t-lg">
}); <Disclosure.Button>
<div className="flex items-center gap-x-2">
return ( <span>
<div <ChevronDownIcon
key={issue.id} className={`h-4 w-4 text-gray-500 ${!open ? "transform -rotate-90" : ""}`}
className="px-4 py-3 text-sm rounded flex justify-between items-center gap-2" />
> </span>
<div className="flex items-center gap-2"> {selectedGroup !== null ? (
<span <h2 className="font-medium leading-5 capitalize">
className={`flex-shrink-0 h-1.5 w-1.5 block rounded-full`} {singleGroup === null || singleGroup === "null"
style={{ ? selectedGroup === "priority" && "No priority"
backgroundColor: issue.state_detail.color, : addSpaceIfCamelCase(singleGroup)}
}} </h2>
/>
<Link href={`/projects/${activeProject?.id}/issues/${issue.id}`}>
<a className="group relative flex items-center gap-2">
{properties.key && (
<span className="flex-shrink-0 text-xs text-gray-500">
{activeProject?.identifier}-{issue.sequence_id}
</span>
)}
<span className="">{issue.name}</span>
<div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap">
<h5 className="font-medium mb-1">Name</h5>
<div>{issue.name}</div>
</div>
</a>
</Link>
</div>
<div className="flex-shrink-0 flex items-center gap-x-1 gap-y-2 text-xs flex-wrap">
{properties.priority && (
<div
className={`group relative flex-shrink-0 flex items-center gap-1 text-xs rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 capitalize ${
issue.priority === "urgent"
? "bg-red-100 text-red-600"
: issue.priority === "high"
? "bg-orange-100 text-orange-500"
: issue.priority === "medium"
? "bg-yellow-100 text-yellow-500"
: issue.priority === "low"
? "bg-green-100 text-green-500"
: "bg-gray-100"
}`}
>
{/* {getPriorityIcon(issue.priority ?? "")} */}
{issue.priority ?? "None"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1 text-gray-900">Priority</h5>
<div
className={`capitalize ${
issue.priority === "urgent"
? "text-red-600"
: issue.priority === "high"
? "text-orange-500"
: issue.priority === "medium"
? "text-yellow-500"
: issue.priority === "low"
? "text-green-500"
: ""
}`}
>
{issue.priority ?? "None"}
</div>
</div>
</div>
)}
{properties.state && (
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
<span
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: issue?.state_detail?.color,
}}
></span>
{addSpaceIfCamelCase(issue?.state_detail.name)}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1">State</h5>
<div>{issue?.state_detail.name}</div>
</div>
</div>
)}
{properties.start_date && (
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
<CalendarDaysIcon className="h-4 w-4" />
{issue.start_date
? renderShortNumericDateFormat(issue.start_date)
: "N/A"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1">Started at</h5>
<div>
{renderShortNumericDateFormat(issue.start_date ?? "")}
</div>
</div>
</div>
)}
{properties.target_date && (
<div
className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
issue.target_date === null
? ""
: issue.target_date < new Date().toISOString()
? "text-red-600"
: findHowManyDaysLeft(issue.target_date) <= 3 &&
"text-orange-400"
}`}
>
<CalendarDaysIcon className="h-4 w-4" />
{issue.target_date
? renderShortNumericDateFormat(issue.target_date)
: "N/A"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1 text-gray-900">
Target date
</h5>
<div>
{renderShortNumericDateFormat(issue.target_date ?? "")}
</div>
<div>
{issue.target_date &&
(issue.target_date < new Date().toISOString()
? `Target date has passed by ${findHowManyDaysLeft(
issue.target_date
)} days`
: findHowManyDaysLeft(issue.target_date) <= 3
? `Target date is in ${findHowManyDaysLeft(
issue.target_date
)} days`
: "Target date")}
</div>
</div>
</div>
)}
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem
onClick={() => openCreateIssueModal(issue, "edit")}
>
Edit
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={() => removeIssueFromCycle(issue.bridge ?? "")}
>
Remove from cycle
</CustomMenu.MenuItem>
<CustomMenu.MenuItem>Delete permanently</CustomMenu.MenuItem>
</CustomMenu>
</div>
</div>
);
})
) : ( ) : (
<p className="text-sm px-4 py-3 text-gray-500">No issues.</p> <h2 className="font-medium leading-5">All Issues</h2>
) )}
) : ( <p className="text-gray-500 text-sm">
<div className="h-full w-full flex items-center justify-center"> {groupedByIssues[singleGroup as keyof IIssue].length}
<Spinner /> </p>
</div>
)}
</div>
</Disclosure.Panel>
</Transition>
<Menu as="div" className="relative text-left p-3">
<Menu.Button className="flex items-center gap-1 px-2 py-1 rounded hover:bg-gray-100 text-xs font-medium">
<PlusIcon className="h-3 w-3" />
Add issue
</Menu.Button>
<Transition
as={React.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 left-0 z-10 mt-1 rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none whitespace-nowrap">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<button
type="button"
className={classNames(
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
"block w-full p-2 text-left"
)}
onClick={() => openCreateIssueModal()}
>
Create new
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
type="button"
className={classNames(
active ? "bg-indigo-50 text-gray-900" : "text-gray-700",
"block w-full p-2 text-left"
)}
onClick={() => openIssuesListModal()}
>
Add an existing issue
</button>
)}
</Menu.Item>
</div> </div>
</Menu.Items> </Disclosure.Button>
</div>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
<div className="divide-y-2">
{groupedByIssues[singleGroup] ? (
groupedByIssues[singleGroup].length > 0 ? (
groupedByIssues[singleGroup].map((issue) => {
const assignees = [
...(issue?.assignees_list ?? []),
...(issue?.assignees ?? []),
]?.map((assignee) => {
const tempPerson = people?.find(
(p) => p.member.id === assignee
)?.member;
return {
avatar: tempPerson?.avatar,
first_name: tempPerson?.first_name,
email: tempPerson?.email,
};
});
return (
<div
key={issue.id}
className="px-4 py-3 text-sm rounded flex justify-between items-center gap-2"
>
<div className="flex items-center gap-2">
<span
className={`flex-shrink-0 h-1.5 w-1.5 block rounded-full`}
style={{
backgroundColor: issue.state_detail.color,
}}
/>
<Link href={`/projects/${activeProject?.id}/issues/${issue.id}`}>
<a className="group relative flex items-center gap-2">
{properties.key && (
<span className="flex-shrink-0 text-xs text-gray-500">
{activeProject?.identifier}-{issue.sequence_id}
</span>
)}
<span>{issue.name}</span>
{/* <div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap">
<h5 className="font-medium mb-1">Name</h5>
<div>{issue.name}</div>
</div> */}
</a>
</Link>
</div>
<div className="flex-shrink-0 flex items-center gap-x-1 gap-y-2 text-xs flex-wrap">
{properties.priority && (
<div
className={`group relative flex-shrink-0 flex items-center gap-1 text-xs rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 capitalize ${
issue.priority === "urgent"
? "bg-red-100 text-red-600"
: issue.priority === "high"
? "bg-orange-100 text-orange-500"
: issue.priority === "medium"
? "bg-yellow-100 text-yellow-500"
: issue.priority === "low"
? "bg-green-100 text-green-500"
: "bg-gray-100"
}`}
>
{/* {getPriorityIcon(issue.priority ?? "")} */}
{issue.priority ?? "None"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1 text-gray-900">Priority</h5>
<div
className={`capitalize ${
issue.priority === "urgent"
? "text-red-600"
: issue.priority === "high"
? "text-orange-500"
: issue.priority === "medium"
? "text-yellow-500"
: issue.priority === "low"
? "text-green-500"
: ""
}`}
>
{issue.priority ?? "None"}
</div>
</div>
</div>
)}
{properties.state && (
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
<span
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: issue?.state_detail?.color,
}}
></span>
{addSpaceIfCamelCase(issue?.state_detail.name)}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1">State</h5>
<div>{issue?.state_detail.name}</div>
</div>
</div>
)}
{properties.start_date && (
<div className="group relative flex-shrink-0 flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300">
<CalendarDaysIcon className="h-4 w-4" />
{issue.start_date
? renderShortNumericDateFormat(issue.start_date)
: "N/A"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1">Started at</h5>
<div>
{renderShortNumericDateFormat(issue.start_date ?? "")}
</div>
</div>
</div>
)}
{properties.target_date && (
<div
className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
issue.target_date === null
? ""
: issue.target_date < new Date().toISOString()
? "text-red-600"
: findHowManyDaysLeft(issue.target_date) <= 3 &&
"text-orange-400"
}`}
>
<CalendarDaysIcon className="h-4 w-4" />
{issue.target_date
? renderShortNumericDateFormat(issue.target_date)
: "N/A"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1 text-gray-900">
Target date
</h5>
<div>
{renderShortNumericDateFormat(issue.target_date ?? "")}
</div>
<div>
{issue.target_date &&
(issue.target_date < new Date().toISOString()
? `Target date has passed by ${findHowManyDaysLeft(
issue.target_date
)} days`
: findHowManyDaysLeft(issue.target_date) <= 3
? `Target date is in ${findHowManyDaysLeft(
issue.target_date
)} days`
: "Target date")}
</div>
</div>
</div>
)}
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem
onClick={() => openCreateIssueModal(issue, "edit")}
>
Edit
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={() => removeIssueFromCycle(issue.bridge ?? "")}
>
Remove from cycle
</CustomMenu.MenuItem>
<CustomMenu.MenuItem>Delete permanently</CustomMenu.MenuItem>
</CustomMenu>
</div>
</div>
);
})
) : (
<p className="text-sm px-4 py-3 text-gray-500">No issues.</p>
)
) : (
<div className="h-full w-full flex items-center justify-center">
<Spinner />
</div>
)}
</div>
</Disclosure.Panel>
</Transition> </Transition>
</Menu> <div className="p-3">
</div> <CustomMenu
)} label={
</Disclosure> <span className="flex items-center gap-1">
))} <PlusIcon className="h-3 w-3" />
Add issue
</span>
}
optionsPosition="left"
withoutBorder
>
<CustomMenu.MenuItem
onClick={() => {
openCreateIssueModal();
if (selectedGroup !== null) {
setPreloadedData({
state: stateId !== null ? stateId : undefined,
[selectedGroup]: singleGroup,
actionType: "createIssue",
});
}
}}
>
Create new
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => openIssuesListModal()}>
Add an existing issue
</CustomMenu.MenuItem>
</CustomMenu>
</div>
</div>
)}
</Disclosure>
);
})}
</div> </div>
); );
}; };

View File

@ -20,7 +20,7 @@ import { addSpaceIfCamelCase } from "constants/common";
import { WORKSPACE_MEMBERS } from "constants/fetch-keys"; import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
// types // types
import { IIssue, Properties, NestedKeyOf, IWorkspaceMember } from "types"; import { IIssue, Properties, NestedKeyOf, IWorkspaceMember } from "types";
import SingleIssue from "components/project/common/board-view/single-issue"; import SingleIssue from "components/common/board-view/single-issue";
type Props = { type Props = {
selectedGroup: NestedKeyOf<IIssue> | null; selectedGroup: NestedKeyOf<IIssue> | null;

View File

@ -122,7 +122,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
return ( return (
<div className="flex items-center gap-x-2 p-2 bg-gray-50"> <div className="flex items-center gap-x-2 p-2 bg-gray-50">
<div className="w-8 h-8 shrink-0"> <div className="flex-shrink-0 h-8 w-8">
<Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl"> <Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl">
{({ open }) => ( {({ open }) => (
<> <>
@ -150,7 +150,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
leaveFrom="opacity-100 translate-y-0" leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1" leaveTo="opacity-0 translate-y-1"
> >
<Popover.Panel className="absolute top-full z-50 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0"> <Popover.Panel className="absolute top-full z-20 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0">
<Controller <Controller
name="color" name="color"
control={control} control={control}

View File

@ -55,7 +55,7 @@ const ConfirmIssueDeletion: React.FC<Props> = ({ isOpen, handleClose, data }) =>
}, },
false false
); );
mutate(CYCLE_ISSUES(data.issue_cycle.id)); mutate(CYCLE_ISSUES(data.issue_cycle?.id ?? ""));
setToastAlert({ setToastAlert({
title: "Success", title: "Success",
type: "success", type: "success",

View File

@ -29,7 +29,7 @@ import CreateUpdateStateModal from "components/project/issues/BoardView/state/cr
import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal"; import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
// types // types
import type { IIssue, IssueResponse } from "types"; import type { IIssue, IssueResponse } from "types";
// fetching keys // fetch keys
import { import {
PROJECT_ISSUES_DETAILS, PROJECT_ISSUES_DETAILS,
PROJECT_ISSUES_LIST, PROJECT_ISSUES_LIST,

View File

@ -47,14 +47,14 @@ const SelectSprint: React.FC<Props> = ({ control, setIsOpen }) => {
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none"> <Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
<div className="p-1"> <div className="py-1">
{cycles?.map((cycle) => ( {cycles?.map((cycle) => (
<Listbox.Option <Listbox.Option
key={cycle.id} key={cycle.id}
value={cycle.id} value={cycle.id}
className={({ active }) => className={({ active }) =>
`relative cursor-pointer select-none p-2 rounded-md ${ `text-gray-900 cursor-pointer select-none p-2 ${
active ? "bg-theme text-white" : "text-gray-900" active ? "bg-indigo-50" : ""
}` }`
} }
> >

View File

@ -99,14 +99,14 @@ const SelectLabels: React.FC<Props> = ({ control }) => {
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none"> <Listbox.Options className="absolute z-10 mt-1 bg-white shadow-lg max-h-28 rounded-md text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
<div className="p-1"> <div className="py-1">
{issueLabels?.map((label) => ( {issueLabels?.map((label) => (
<Listbox.Option <Listbox.Option
key={label.id} key={label.id}
className={({ active }) => className={({ active }) =>
`${ `${
active ? "text-white bg-theme" : "text-gray-900" active ? "bg-indigo-50" : ""
} flex items-center gap-2 cursor-pointer select-none w-full p-2 rounded-md` } flex items-center gap-2 text-gray-900 cursor-pointer select-none w-full p-2`
} }
value={label.id} value={label.id}
> >

View File

@ -42,14 +42,14 @@ const SelectPriority: React.FC<Props> = ({ control }) => {
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute z-10 mt-1 w-full w-[5rem] bg-white shadow-lg max-h-28 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-xs"> <Listbox.Options className="absolute z-10 mt-1 w-full w-[5rem] bg-white shadow-lg max-h-28 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none text-xs">
<div className="p-1"> <div className="py-1">
{PRIORITIES.map((priority) => ( {PRIORITIES.map((priority) => (
<Listbox.Option <Listbox.Option
key={priority} key={priority}
className={({ active }) => className={({ active }) =>
`${ `${
active ? "text-white bg-theme" : "text-gray-900" active ? "bg-indigo-50" : ""
} cursor-pointer select-none relative p-2 rounded-md` } text-gray-900 cursor-pointer select-none p-2`
} }
value={priority} value={priority}
> >

View File

@ -28,7 +28,7 @@ const SelectState: React.FC<Props> = ({ control, setIsOpen }) => {
<CustomListbox <CustomListbox
title="State" title="State"
options={states?.map((state) => { options={states?.map((state) => {
return { value: state.id, display: state.name }; return { value: state.id, display: state.name, color: state.color };
})} })}
value={value} value={value}
optionsFontsize="sm" optionsFontsize="sm"

View File

@ -93,6 +93,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
console.log(res); console.log(res);
reset(defaultValues); reset(defaultValues);
issueLabelMutate((prevData) => [...(prevData ?? []), res], false); issueLabelMutate((prevData) => [...(prevData ?? []), res], false);
submitChanges({ labels_list: [res.id] });
}); });
}; };
@ -103,7 +104,7 @@ const IssueDetailSidebar: React.FC<Props> = ({
}); });
}; };
console.log(issueDetail); console.log(issueDetail?.labels, issueDetail?.labels_list);
return ( return (
<> <>
@ -248,25 +249,32 @@ const IssueDetailSidebar: React.FC<Props> = ({
</div> </div>
<div className="basis-1/2"> <div className="basis-1/2">
<div className="flex gap-1 flex-wrap"> <div className="flex gap-1 flex-wrap">
{issueDetail?.label_details.map((label) => ( {issueDetail?.labels.map((label) => {
<span const singleLabel = issueLabels?.find((l) => l.id === label);
key={label.id}
className="group flex items-center gap-1 border rounded-2xl text-xs px-1 py-0.5 hover:bg-red-50 hover:border-red-500 cursor-pointer" if (!singleLabel) return null;
onClick={() => {
const updatedLabels = issueDetail?.labels.filter((l) => l !== label.id); return (
submitChanges({
labels_list: updatedLabels,
});
}}
>
<span <span
className="h-2 w-2 rounded-full flex-shrink-0" key={singleLabel.id}
style={{ backgroundColor: label.colour ?? "green" }} className="group flex items-center gap-1 border rounded-2xl text-xs px-1 py-0.5 hover:bg-red-50 hover:border-red-500 cursor-pointer"
></span> onClick={() => {
{label.name} const updatedLabels = issueDetail?.labels.filter((l) => l !== label);
<XMarkIcon className="h-2 w-2 group-hover:text-red-500" /> // submitChanges({
</span> // labels_list: updatedLabels,
))} // });
console.log(updatedLabels);
}}
>
<span
className="h-2 w-2 rounded-full flex-shrink-0"
style={{ backgroundColor: singleLabel.colour ?? "green" }}
></span>
{singleLabel.name}
<XMarkIcon className="h-2 w-2 group-hover:text-red-500" />
</span>
);
})}
<Controller <Controller
control={control} control={control}
name="labels_list" name="labels_list"
@ -274,9 +282,9 @@ const IssueDetailSidebar: React.FC<Props> = ({
<Listbox <Listbox
as="div" as="div"
value={value} value={value}
multiple
onChange={(val: any) => submitChanges({ labels_list: val })} onChange={(val: any) => submitChanges({ labels_list: val })}
className="flex-shrink-0" className="flex-shrink-0"
multiple
> >
{({ open }) => ( {({ open }) => (
<> <>

View File

@ -58,7 +58,7 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
> >
{({ open }) => ( {({ open }) => (
<div className="relative"> <div className="relative">
<Listbox.Button className="w-full flex justify-end items-center gap-1 text-xs cursor-pointer"> <Listbox.Button className="w-full flex items-center gap-1 text-xs cursor-pointer">
<span <span
className={classNames( className={classNames(
value ? "" : "text-gray-900", value ? "" : "text-gray-900",
@ -121,9 +121,12 @@ const SelectAssignee: React.FC<Props> = ({ control, submitChanges }) => {
<Transition <Transition
show={open} show={open}
as={React.Fragment} as={React.Fragment}
leave="transition ease-in duration-100" enter="transition ease-out duration-100"
leaveFrom="opacity-100" enterFrom="transform opacity-0 scale-95"
leaveTo="opacity-0" enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
> >
<Listbox.Options className="absolute z-10 right-0 mt-1 w-auto bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none"> <Listbox.Options className="absolute z-10 right-0 mt-1 w-auto bg-white shadow-lg max-h-28 rounded-md py-1 text-xs ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none">
<div className="py-1"> <div className="py-1">

View File

@ -1,16 +1,17 @@
// react
import React from "react";
// react-hook-form // react-hook-form
import { Control, Controller } from "react-hook-form"; import { Control, Controller } from "react-hook-form";
// hooks // hooks
import useUser from "lib/hooks/useUser"; import useUser from "lib/hooks/useUser";
// headless ui // ui
import { Listbox, Transition } from "@headlessui/react"; import { Spinner, CustomSelect } from "ui";
// icons
import { ArrowPathIcon } from "@heroicons/react/24/outline";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
// common
import { classNames } from "constants/common"; import { classNames } from "constants/common";
import { Spinner } from "ui";
import React from "react";
import { ArrowPathIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
import CustomSelect from "ui/custom-select";
type Props = { type Props = {
control: Control<IIssue, any>; control: Control<IIssue, any>;
@ -29,7 +30,7 @@ const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
<div className="sm:basis-1/2"> <div className="sm:basis-1/2">
<Controller <Controller
control={control} control={control}
name="cycle" name="issue_cycle"
render={({ field: { value } }) => ( render={({ field: { value } }) => (
<> <>
<CustomSelect <CustomSelect
@ -40,7 +41,7 @@ const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
"hidden truncate sm:block text-left" "hidden truncate sm:block text-left"
)} )}
> >
{value ? cycles?.find((c) => c.id === value)?.name : "None"} {value ? cycles?.find((c) => c.id === value.cycle_detail.id)?.name : "None"}
</span> </span>
} }
value={value} value={value}

View File

@ -155,11 +155,11 @@ const ListView: React.FC<Props> = ({
{activeProject?.identifier}-{issue.sequence_id} {activeProject?.identifier}-{issue.sequence_id}
</span> </span>
)} )}
<span className="">{issue.name}</span> <span>{issue.name}</span>
<div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap"> {/* <div className="absolute bottom-full left-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md max-w-sm whitespace-nowrap">
<h5 className="font-medium mb-1">Name</h5> <h5 className="font-medium mb-1">Name</h5>
<div>{issue.name}</div> <div>{issue.name}</div>
</div> </div> */}
</a> </a>
</Link> </Link>
</div> </div>

View File

@ -13,7 +13,7 @@ import useUser from "lib/hooks/useUser";
// headless ui // headless ui
import { Popover, Transition, Menu } from "@headlessui/react"; import { Popover, Transition, Menu } from "@headlessui/react";
// ui // ui
import { Button, Input, Spinner } from "ui"; import { Button, CustomMenu, Input, Spinner } from "ui";
// icons // icons
import { import {
ChevronDownIcon, ChevronDownIcon,
@ -26,6 +26,7 @@ import {
import { IIssueLabels } from "types"; import { IIssueLabels } from "types";
// fetch-keys // fetch-keys
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
import SingleLabel from "./single-label";
const defaultValues: Partial<IIssueLabels> = { const defaultValues: Partial<IIssueLabels> = {
name: "", name: "",
@ -68,6 +69,14 @@ const LabelsSettings: React.FC = () => {
}); });
}; };
const editLabel = (label: IIssueLabels) => {
setNewLabelForm(true);
setValue("colour", label.colour);
setValue("name", label.name);
setIsUpdating(true);
setLabelidForUpdate(label.id);
};
const handleLabelUpdate: SubmitHandler<IIssueLabels> = (formData) => { const handleLabelUpdate: SubmitHandler<IIssueLabels> = (formData) => {
if (!activeWorkspace || !activeProject || isSubmitting) return; if (!activeWorkspace || !activeProject || isSubmitting) return;
issuesServices issuesServices
@ -98,10 +107,6 @@ const LabelsSettings: React.FC = () => {
} }
}; };
const getLabelChildren = (labelId: string) => {
return issueLabels?.filter((l) => l.parent === labelId);
};
return ( return (
<> <>
<section className="space-y-5"> <section className="space-y-5">
@ -110,31 +115,38 @@ const LabelsSettings: React.FC = () => {
<h3 className="text-lg font-medium leading-6 text-gray-900">Labels</h3> <h3 className="text-lg font-medium leading-6 text-gray-900">Labels</h3>
<p className="mt-1 text-sm text-gray-500">Manage the labels of this project.</p> <p className="mt-1 text-sm text-gray-500">Manage the labels of this project.</p>
</div> </div>
<Button className="flex items-center gap-x-1" onClick={() => setNewLabelForm(true)}> <Button
theme="secondary"
className="flex items-center gap-x-1"
onClick={() => setNewLabelForm(true)}
>
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
New label New label
</Button> </Button>
</div> </div>
<div className="space-y-5"> <div className="space-y-5">
<div <div
className={`bg-white px-4 py-2 flex items-center gap-2 ${newLabelForm ? "" : "hidden"}`} className={`md:w-2/3 border p-3 rounded-md flex items-center gap-2 ${
newLabelForm ? "" : "hidden"
}`}
> >
<div> <div className="flex-shrink-0 h-8 w-8">
<Popover className="relative"> <Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl">
{({ open }) => ( {({ open }) => (
<> <>
<Popover.Button <Popover.Button
className={`bg-white flex items-center gap-1 rounded-md p-1 outline-none focus:ring-2 focus:ring-indigo-500`} className={`group inline-flex items-center text-base font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
open ? "text-gray-900" : "text-gray-500"
}`}
> >
{watch("colour") && watch("colour") !== "" && ( {watch("colour") && watch("colour") !== "" && (
<span <span
className="w-6 h-6 rounded" className="w-4 h-4 rounded"
style={{ style={{
backgroundColor: watch("colour") ?? "green", backgroundColor: watch("colour") ?? "green",
}} }}
></span> ></span>
)} )}
<ChevronDownIcon className="h-4 w-4" />
</Popover.Button> </Popover.Button>
<Transition <Transition
@ -146,7 +158,7 @@ const LabelsSettings: React.FC = () => {
leaveFrom="opacity-100 translate-y-0" leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1" leaveTo="opacity-0 translate-y-1"
> >
<Popover.Panel className="absolute z-20 transform left-0 mt-1 px-2 max-w-xs sm:px-0"> <Popover.Panel className="absolute top-full z-20 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0">
<Controller <Controller
name="colour" name="colour"
control={control} control={control}
@ -193,89 +205,23 @@ const LabelsSettings: React.FC = () => {
</Button> </Button>
)} )}
</div> </div>
{issueLabels ? ( <>
issueLabels.map((label) => { {issueLabels ? (
const children = getLabelChildren(label.id); issueLabels.map((label) => (
<SingleLabel
return ( key={label.id}
<React.Fragment key={label.id}> label={label}
{children && children.length === 0 ? ( issueLabels={issueLabels}
<div className="bg-white p-2 flex items-center justify-between text-gray-900 rounded-md"> editLabel={editLabel}
<div className="flex items-center gap-2"> handleLabelDelete={handleLabelDelete}
<span />
className="flex-shrink-0 h-1.5 w-1.5 rounded-full" ))
style={{ ) : (
backgroundColor: label.colour, <div className="flex justify-center py-4">
}} <Spinner />
/> </div>
<p className="text-sm">{label.name}</p> )}
</div> </>
<div>
<Menu as="div" className="relative">
<Menu.Button
as="button"
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-100 duration-300 outline-none`}
>
<EllipsisHorizontalIcon className="h-4 w-4" />
</Menu.Button>
<Menu.Items className="absolute origin-top-right right-0.5 mt-1 p-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10">
<Menu.Item>
<button
type="button"
className="text-left p-2 text-gray-900 hover:bg-theme hover:text-white rounded-md text-xs whitespace-nowrap w-full"
onClick={() => {
setNewLabelForm(true);
setValue("colour", label.colour);
setValue("name", label.name);
setIsUpdating(true);
setLabelidForUpdate(label.id);
}}
>
Edit
</button>
</Menu.Item>
<Menu.Item>
<div className="hover:bg-gray-100 border-b last:border-0">
<button
type="button"
className="text-left p-2 text-gray-900 hover:bg-theme hover:text-white rounded-md text-xs whitespace-nowrap w-full"
onClick={() => handleLabelDelete(label.id)}
>
Delete
</button>
</div>
</Menu.Item>
</Menu.Items>
</Menu>
</div>
</div>
) : (
<div className="bg-white p-4 text-gray-900 rounded-md">
<h3 className="font-medium leading-5 flex items-center gap-2">
<RectangleGroupIcon className="h-5 w-5" />
This is the label group title
</h3>
<div className="pl-5 mt-4">
<div className="group text-sm flex justify-between items-center p-2 hover:bg-gray-100 rounded">
<h5 className="flex items-center gap-2">
<div className="w-2 h-2 bg-red-600 rounded-full"></div>
This is the label title
</h5>
<button type="button" className="hidden group-hover:block">
<PencilIcon className="h-3 w-3" />
</button>
</div>
</div>
</div>
)}
</React.Fragment>
);
})
) : (
<div className="flex justify-center py-4">
<Spinner />
</div>
)}
</div> </div>
</section> </section>
</> </>

View File

@ -0,0 +1,160 @@
// react
import React, { useState } from "react";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// headless ui
import { Popover, Transition } from "@headlessui/react";
// ui
import { Button, CustomMenu, Input } from "ui";
// icons
import { PencilIcon, RectangleGroupIcon } from "@heroicons/react/24/outline";
// types
import { IIssueLabels } from "types";
import { TwitterPicker } from "react-color";
type Props = {
label: IIssueLabels;
issueLabels: IIssueLabels[];
editLabel: (label: IIssueLabels) => void;
handleLabelDelete: (labelId: string) => void;
};
const defaultValues: Partial<IIssueLabels> = {
name: "",
colour: "#ff0000",
};
const SingleLabel: React.FC<Props> = ({ label, issueLabels, editLabel, handleLabelDelete }) => {
const [newLabelForm, setNewLabelForm] = useState(true);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
setError,
watch,
reset,
control,
} = useForm<IIssueLabels>({ defaultValues });
const children = issueLabels?.filter((l) => l.parent === label.id);
return (
<>
{children && children.length === 0 ? (
<div className="md:w-2/3 gap-2 border p-3 rounded-md divide-y space-y-3">
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<span
className="flex-shrink-0 h-3 w-3 rounded-full"
style={{
backgroundColor: label.colour,
}}
/>
<h6 className="text-sm">{label.name}</h6>
</div>
<CustomMenu ellipsis>
<CustomMenu.MenuItem>Convert to group</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={() => {
editLabel(label);
}}
>
Edit
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => handleLabelDelete(label.id)}>
Delete
</CustomMenu.MenuItem>
</CustomMenu>
</div>
<div className={`flex items-center gap-2 ${newLabelForm ? "" : "hidden"}`}>
<div className="flex-shrink-0 h-8 w-8">
<Popover className="relative w-full h-full flex justify-center items-center bg-gray-200 rounded-xl">
{({ open }) => (
<>
<Popover.Button
className={`group inline-flex items-center text-base font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
open ? "text-gray-900" : "text-gray-500"
}`}
>
{watch("colour") && watch("colour") !== "" && (
<span
className="w-4 h-4 rounded"
style={{
backgroundColor: watch("colour") ?? "green",
}}
></span>
)}
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute top-full z-20 left-0 mt-3 px-2 w-screen max-w-xs sm:px-0">
<Controller
name="colour"
control={control}
render={({ field: { value, onChange } }) => (
<TwitterPicker
color={value}
onChange={(value) => onChange(value.hex)}
/>
)}
/>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</div>
<div className="w-full flex flex-col justify-center">
<Input
type="text"
id="labelName"
name="name"
register={register}
placeholder="Lable title"
validations={{
required: "Label title is required",
}}
error={errors.name}
/>
</div>
<Button type="button" theme="secondary" onClick={() => setNewLabelForm(false)}>
Cancel
</Button>
<Button type="button" disabled={isSubmitting}>
{isSubmitting ? "Adding" : "Add"}
</Button>
</div>
</div>
) : (
<div className="bg-white p-4 text-gray-900 rounded-md">
<h3 className="font-medium leading-5 flex items-center gap-2">
<RectangleGroupIcon className="h-5 w-5" />
This is the label group title
</h3>
<div className="pl-5 mt-4">
<div className="group text-sm flex justify-between items-center p-2 hover:bg-gray-100 rounded">
<h5 className="flex items-center gap-2">
<div className="w-2 h-2 bg-red-600 rounded-full"></div>
This is the label title
</h5>
<button type="button" className="hidden group-hover:block">
<PencilIcon className="h-3 w-3" />
</button>
</div>
</div>
</div>
)}
</>
);
};
export default SingleLabel;

View File

@ -56,50 +56,7 @@ const StatesSettings: React.FC<Props> = ({ projectId }) => {
<span>Add</span> <span>Add</span>
</button> </button>
</div> </div>
<div className="w-full md:w-2/3 space-y-1 border p-1 rounded-xl bg-gray-50"> <div className="md:w-2/3 space-y-1 border p-1 rounded-xl">
<div className="w-full">
{groupedStates[key]?.map((state) =>
state.id !== selectedState ? (
<div
key={state.id}
className={`bg-gray-50 px-5 py-4 flex justify-between items-center border-b ${
Boolean(activeGroup !== key) ? "last:border-0" : ""
}`}
>
<div className="flex items-center gap-x-8">
<div
className="w-6 h-6 rounded-full"
style={{
backgroundColor: state.color,
}}
></div>
<h4>{addSpaceIfCamelCase(state.name)}</h4>
</div>
<div className="flex gap-x-2">
<button type="button" onClick={() => setSelectDeleteState(state.id)}>
<TrashIcon className="h-5 w-5 text-red-400" />
</button>
<button type="button" onClick={() => setSelectedState(state.id)}>
<PencilSquareIcon className="h-5 w-5 text-gray-400" />
</button>
</div>
</div>
) : (
<div className={`border-b last:border-b-0`} key={state.id}>
<CreateUpdateStateInline
projectId={projectId as string}
onClose={() => {
setActiveGroup(null);
setSelectedState(null);
}}
workspaceSlug={activeWorkspace?.slug}
data={states?.find((state) => state.id === selectedState) ?? null}
selectedGroup={key as keyof StateGroup}
/>
</div>
)
)}
</div>
{key === activeGroup && ( {key === activeGroup && (
<CreateUpdateStateInline <CreateUpdateStateInline
projectId={projectId as string} projectId={projectId as string}
@ -112,6 +69,47 @@ const StatesSettings: React.FC<Props> = ({ projectId }) => {
selectedGroup={key as keyof StateGroup} selectedGroup={key as keyof StateGroup}
/> />
)} )}
{groupedStates[key]?.map((state) =>
state.id !== selectedState ? (
<div
key={state.id}
className={`bg-gray-50 p-3 flex justify-between items-center gap-2 border-b ${
Boolean(activeGroup !== key) ? "last:border-0" : ""
}`}
>
<div className="flex items-center gap-2">
<div
className="flex-shrink-0 h-3 w-3 rounded-full"
style={{
backgroundColor: state.color,
}}
></div>
<h6 className="text-sm">{addSpaceIfCamelCase(state.name)}</h6>
</div>
<div className="flex items-center gap-2">
<button type="button" onClick={() => setSelectDeleteState(state.id)}>
<TrashIcon className="h-4 w-4 text-red-400" />
</button>
<button type="button" onClick={() => setSelectedState(state.id)}>
<PencilSquareIcon className="h-4 w-4 text-gray-400" />
</button>
</div>
</div>
) : (
<div className={`border-b last:border-b-0`} key={state.id}>
<CreateUpdateStateInline
projectId={projectId as string}
onClose={() => {
setActiveGroup(null);
setSelectedState(null);
}}
workspaceSlug={activeWorkspace?.slug}
data={states?.find((state) => state.id === selectedState) ?? null}
selectedGroup={key as keyof StateGroup}
/>
</div>
)
)}
</div> </div>
</React.Fragment> </React.Fragment>
))} ))}

View File

@ -1,7 +1,6 @@
// react // react
import React, { useState } from "react"; import React, { useState } from "react";
// next // next
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// swr // swr
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
@ -12,6 +11,9 @@ import AppLayout from "layouts/app-layout";
// components // components
import CyclesListView from "components/project/cycles/list-view"; import CyclesListView from "components/project/cycles/list-view";
import CyclesBoardView from "components/project/cycles/board-view"; import CyclesBoardView from "components/project/cycles/board-view";
import CreateUpdateIssuesModal from "components/project/issues/create-update-issue-modal";
import CycleIssuesListModal from "components/project/cycles/cycle-issues-list-modal";
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
// services // services
import issuesServices from "lib/services/issues.service"; import issuesServices from "lib/services/issues.service";
import cycleServices from "lib/services/cycles.service"; import cycleServices from "lib/services/cycles.service";
@ -28,23 +30,11 @@ import { BreadcrumbItem, Breadcrumbs, CustomMenu } from "ui";
import { Squares2X2Icon } from "@heroicons/react/20/solid"; import { Squares2X2Icon } from "@heroicons/react/20/solid";
import { ArrowPathIcon, ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline"; import { ArrowPathIcon, ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline";
// types // types
import { import { CycleIssueResponse, IIssue, NestedKeyOf, Properties, SelectIssue } from "types";
CycleIssueResponse,
IIssue,
IssueResponse,
NestedKeyOf,
Properties,
SelectIssue,
SelectSprintType,
} from "types";
// fetch-keys // fetch-keys
import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, PROJECT_MEMBERS } from "constants/fetch-keys"; import { CYCLE_ISSUES, PROJECT_MEMBERS } from "constants/fetch-keys";
// constants // common
import { classNames, replaceUnderscoreIfSnakeCase } from "constants/common"; import { classNames, replaceUnderscoreIfSnakeCase } from "constants/common";
import CreateUpdateIssuesModal from "components/project/issues/create-update-issue-modal";
import CycleIssuesListModal from "components/project/cycles/cycle-issues-list-modal";
import ConfirmCycleDeletion from "components/project/cycles/confirm-cycle-deletion";
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [ const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
{ name: "State", key: "state_detail.name" }, { name: "State", key: "state_detail.name" },
@ -77,15 +67,16 @@ const filterIssueOptions: Array<{
}, },
]; ];
type Props = {}; const SingleCycle: React.FC = () => {
const SingleCycle: React.FC<Props> = () => {
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false); const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
const [selectedCycle, setSelectedCycle] = useState<SelectSprintType>();
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>(); const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined); const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined);
const { activeWorkspace, activeProject, cycles, issues } = useUser(); const { activeWorkspace, activeProject, cycles, issues } = useUser();
const router = useRouter(); const router = useRouter();
@ -149,15 +140,8 @@ const SingleCycle: React.FC<Props> = () => {
issue?: IIssue, issue?: IIssue,
actionType: "create" | "edit" | "delete" = "create" actionType: "create" | "edit" | "delete" = "create"
) => { ) => {
const cycle = cycles?.find((cycle) => cycle.id === cycleId); if (issue) setSelectedIssues({ ...issue, actionType });
if (cycle) { setIsIssueModalOpen(true);
setSelectedCycle({
...cycle,
actionType: "create-issue",
});
if (issue) setSelectedIssues({ ...issue, actionType });
setIsIssueModalOpen(true);
}
}; };
const openIssuesListModal = () => { const openIssuesListModal = () => {
@ -229,13 +213,9 @@ const SingleCycle: React.FC<Props> = () => {
return ( return (
<> <>
<CreateUpdateIssuesModal <CreateUpdateIssuesModal
isOpen={ isOpen={isIssueModalOpen && selectedIssues?.actionType !== "delete"}
isIssueModalOpen &&
selectedCycle?.actionType === "create-issue" &&
selectedIssues?.actionType !== "delete"
}
data={selectedIssues} data={selectedIssues}
prePopulateData={{ sprints: selectedCycle?.id }} prePopulateData={{ sprints: cycleId as string, ...preloadedData }}
setIsOpen={setIsIssueModalOpen} setIsOpen={setIsIssueModalOpen}
projectId={activeProject?.id} projectId={activeProject?.id}
/> />
@ -429,6 +409,7 @@ const SingleCycle: React.FC<Props> = () => {
openCreateIssueModal={openCreateIssueModal} openCreateIssueModal={openCreateIssueModal}
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
removeIssueFromCycle={removeIssueFromCycle} removeIssueFromCycle={removeIssueFromCycle}
setPreloadedData={setPreloadedData}
/> />
) : ( ) : (
<div className="h-screen"> <div className="h-screen">
@ -442,6 +423,7 @@ const SingleCycle: React.FC<Props> = () => {
openIssuesListModal={openIssuesListModal} openIssuesListModal={openIssuesListModal}
handleDeleteIssue={setDeleteIssue} handleDeleteIssue={setDeleteIssue}
partialUpdateIssue={partialUpdateIssue} partialUpdateIssue={partialUpdateIssue}
setPreloadedData={setPreloadedData}
/> />
</div> </div>
)} )}

View File

@ -106,7 +106,13 @@ const ProjectSprints: NextPage = () => {
</span> </span>
} }
Icon={PlusIcon} Icon={PlusIcon}
action={() => setCreateUpdateCycleModal(true)} action={() => {
const e = new KeyboardEvent("keydown", {
ctrlKey: true,
key: "q",
});
document.dispatchEvent(e);
}}
/> />
</EmptySpace> </EmptySpace>
</div> </div>

View File

@ -94,7 +94,7 @@ const IssueDetail: NextPage = () => {
blockers_list: [], blockers_list: [],
blocked_list: [], blocked_list: [],
target_date: new Date().toString(), target_date: new Date().toString(),
cycle: "", issue_cycle: null,
labels_list: [], labels_list: [],
}, },
}); });
@ -171,6 +171,7 @@ const IssueDetail: NextPage = () => {
assignees_list: assignees_list:
issueDetail.assignees_list ?? issueDetail.assignee_details?.map((user) => user.id), issueDetail.assignees_list ?? issueDetail.assignee_details?.map((user) => user.id),
labels_list: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label), labels_list: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label),
labels: issueDetail.labels_list ?? issueDetail.labels?.map((label) => label),
}); });
}, [issueDetail, reset]); }, [issueDetail, reset]);
@ -211,7 +212,7 @@ const IssueDetail: NextPage = () => {
} }
}; };
// console.log(issueDetail); console.log(issueDetail);
return ( return (
<AppLayout <AppLayout

View File

@ -49,7 +49,7 @@ import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [ const groupByOptions: Array<{ name: string; key: NestedKeyOf<IIssue> | null }> = [
{ name: "State", key: "state_detail.name" }, { name: "State", key: "state_detail.name" },
{ name: "Priority", key: "priority" }, { name: "Priority", key: "priority" },
{ name: "Cycle", key: "issue_cycle.cycle_detail.name" }, // { name: "Cycle", key: "issue_cycle.cycle_detail.name" },
{ name: "Created By", key: "created_by" }, { name: "Created By", key: "created_by" },
{ name: "None", key: null }, { name: "None", key: null },
]; ];

View File

@ -80,13 +80,17 @@ const ProjectModules: NextPage = () => {
title="Create a new module" title="Create a new module"
description={ description={
<span> <span>
Use <pre className="inline bg-gray-100 px-2 py-1 rounded">Ctrl/Command + Q</pre>{" "} Use <pre className="inline bg-gray-100 px-2 py-1 rounded">Ctrl/Command + M</pre>{" "}
shortcut to create a new cycle shortcut to create a new cycle
</span> </span>
} }
Icon={PlusIcon} Icon={PlusIcon}
action={() => { action={() => {
return; const e = new KeyboardEvent("keydown", {
ctrlKey: true,
key: "m",
});
document.dispatchEvent(e);
}} }}
/> />
</EmptySpace> </EmptySpace>

View File

@ -27,22 +27,22 @@ import { Breadcrumbs, BreadcrumbItem } from "ui/Breadcrumbs";
// types // types
import type { IProject, IWorkspace } from "types"; import type { IProject, IWorkspace } from "types";
const GeneralSettings = dynamic(() => import("components/project/settings/GeneralSettings"), { const GeneralSettings = dynamic(() => import("components/project/settings/general"), {
loading: () => <p>Loading...</p>, loading: () => <p>Loading...</p>,
ssr: false, ssr: false,
}); });
const ControlSettings = dynamic(() => import("components/project/settings/ControlSettings"), { const ControlSettings = dynamic(() => import("components/project/settings/control"), {
loading: () => <p>Loading...</p>, loading: () => <p>Loading...</p>,
ssr: false, ssr: false,
}); });
const StatesSettings = dynamic(() => import("components/project/settings/StatesSettings"), { const StatesSettings = dynamic(() => import("components/project/settings/states"), {
loading: () => <p>Loading...</p>, loading: () => <p>Loading...</p>,
ssr: false, ssr: false,
}); });
const LabelsSettings = dynamic(() => import("components/project/settings/LabelsSettings"), { const LabelsSettings = dynamic(() => import("components/project/settings/labels"), {
loading: () => <p>Loading...</p>, loading: () => <p>Loading...</p>,
ssr: false, ssr: false,
}); });

View File

@ -30,7 +30,6 @@ export interface IIssue {
label_details: any[]; label_details: any[];
assignee_details: IUser[]; assignee_details: IUser[];
assignees_list: string[]; assignees_list: string[];
bridge?: string;
blocked_by_issue_details: any[]; blocked_by_issue_details: any[];
blocked_issues: BlockeIssue[]; blocked_issues: BlockeIssue[];
blocker_issues: BlockeIssue[]; blocker_issues: BlockeIssue[];
@ -51,7 +50,7 @@ export interface IIssue {
updated_at: Date; updated_at: Date;
updated_by: string; updated_by: string;
workspace: string; workspace: string;
}; } | null;
description: any; description: any;
priority: string | null; priority: string | null;
start_date: string | null; start_date: string | null;

View File

@ -39,7 +39,7 @@ const Button = React.forwardRef<HTMLButtonElement, Props>(
disabled ? "opacity-70" : "" disabled ? "opacity-70" : ""
} text-white shadow-sm bg-theme hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 border border-transparent` } text-white shadow-sm bg-theme hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 border border-transparent`
: theme === "secondary" : theme === "secondary"
? "border bg-white hover:bg-gray-100" ? "border bg-transparent hover:bg-gray-100"
: theme === "success" : theme === "success"
? `${ ? `${
disabled ? "opacity-70" : "" disabled ? "opacity-70" : ""

View File

@ -92,7 +92,7 @@ const CustomListbox: React.FC<Props> = ({
: "" : ""
} rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none z-10`} } rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none z-10`}
> >
<div className="p-1"> <div className="py-1">
{options ? ( {options ? (
options.length > 0 ? ( options.length > 0 ? (
options.map((option) => ( options.map((option) => (
@ -100,8 +100,8 @@ const CustomListbox: React.FC<Props> = ({
key={option.value} key={option.value}
className={({ active }) => className={({ active }) =>
`${ `${
active ? "text-white bg-theme" : "text-gray-900" active ? "bg-indigo-50" : ""
} cursor-pointer select-none relative p-2 rounded-md` } text-gray-900 cursor-pointer select-none relative p-2`
} }
value={option.value} value={option.value}
> >
@ -115,8 +115,16 @@ const CustomListbox: React.FC<Props> = ({
: value === option.value) : value === option.value)
? "font-semibold" ? "font-semibold"
: "font-normal" : "font-normal"
} block truncate`} } flex items-center gap-2 truncate`}
> >
{option.color && (
<span
className="flex-shrink-0 h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: option.color,
}}
></span>
)}
{option.display} {option.display}
</span> </span>

View File

@ -1,7 +1,7 @@
export type Props = { export type Props = {
title?: string; title?: string;
label?: string; label?: string;
options?: Array<{ display: string; value: any }>; options?: Array<{ display: string; value: any; color?: string }>;
icon?: JSX.Element; icon?: JSX.Element;
value: any; value: any;
onChange: (value: any) => void; onChange: (value: any) => void;

View File

@ -15,11 +15,13 @@ const CustomMenu = ({
label, label,
className = "", className = "",
ellipsis = false, ellipsis = false,
width, width = "auto",
textAlignment, textAlignment,
withoutBorder = false,
optionsPosition = "right",
}: Props) => { }: Props) => {
return ( return (
<Menu as="div" className={`relative text-left ${className}`}> <Menu as="div" className={`relative w-min whitespace-nowrap text-left ${className}`}>
<div> <div>
{ellipsis ? ( {ellipsis ? (
<Menu.Button className="grid relative place-items-center hover:bg-gray-100 rounded p-1 focus:outline-none"> <Menu.Button className="grid relative place-items-center hover:bg-gray-100 rounded p-1 focus:outline-none">
@ -27,16 +29,20 @@ const CustomMenu = ({
</Menu.Button> </Menu.Button>
) : ( ) : (
<Menu.Button <Menu.Button
className={`flex justify-between items-center gap-1 hover:bg-gray-100 border rounded-md shadow-sm px-2 w-full py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${ className={`flex justify-between items-center gap-1 hover:bg-gray-100 px-2 py-1 cursor-pointer text-xs duration-300 ${
textAlignment === "right" textAlignment === "right"
? "text-right" ? "text-right"
: textAlignment === "center" : textAlignment === "center"
? "text-center" ? "text-center"
: "text-left" : "text-left"
} ${
withoutBorder
? "rounded"
: "w-full border shadow-sm rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
}`} }`}
> >
{label} {label}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> {!withoutBorder && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
</Menu.Button> </Menu.Button>
)} )}
</div> </div>
@ -51,9 +57,9 @@ const CustomMenu = ({
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items <Menu.Items
className={`absolute right-0 z-10 mt-1 origin-top-right rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${ className={`absolute z-20 mt-1 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" optionsPosition === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
}`} } ${width === "auto" ? "min-w-full whitespace-nowrap" : "w-56"}`}
> >
<div className="py-1">{children}</div> <div className="py-1">{children}</div>
</Menu.Items> </Menu.Items>

View File

@ -5,6 +5,8 @@ export type Props = {
ellipsis?: boolean; ellipsis?: boolean;
width?: "auto"; width?: "auto";
textAlignment?: "left" | "center" | "right"; textAlignment?: "left" | "center" | "right";
withoutBorder?: boolean;
optionsPosition?: "left" | "right";
}; };
export type MenuItemProps = { export type MenuItemProps = {

View File

@ -4,6 +4,7 @@ export { default as Select } from "./Select";
export { default as TextArea } from "./TextArea"; export { default as TextArea } from "./TextArea";
export { default as CustomListbox } from "./custom-listbox"; export { default as CustomListbox } from "./custom-listbox";
export { default as CustomMenu } from "./custom-menu"; export { default as CustomMenu } from "./custom-menu";
export { default as CustomSelect } from "./custom-select";
export { default as Spinner } from "./Spinner"; export { default as Spinner } from "./Spinner";
export { default as Tooltip } from "./Tooltip"; export { default as Tooltip } from "./Tooltip";
export { default as SearchListbox } from "./search-listbox"; export { default as SearchListbox } from "./search-listbox";