style: gantt layout quick-add padding (#2272)

* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* style: gantt layout quick-add padding

style: removed 'State group' from draft issue

* style: decrese shadow, quick-add position on calender layout, and 'add issue' sticky

* style: button color
This commit is contained in:
Dakshesh Jain 2023-09-27 08:51:29 +05:30 committed by GitHub
parent 3a6d72e4b6
commit 2d8cbccfbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 187 additions and 106 deletions

View File

@ -67,7 +67,7 @@ export const IssuesFilterView: React.FC = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query; const { workspaceSlug, projectId, viewId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues"); const isArchivedIssues = router.pathname.includes("archived-issues");
const isDraftIssues = router.pathname.includes("draft-issues"); const isDraftIssues = router.pathname?.split("/")?.[4] === "draft-issues";
const { const {
displayFilters, displayFilters,
@ -230,6 +230,9 @@ export const IssuesFilterView: React.FC = () => {
return null; return null;
if (option.key === "project") return null; if (option.key === "project") return null;
if (isDraftIssues && option.key === "state_detail.group")
return null;
return ( return (
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}

View File

@ -48,7 +48,7 @@ const InlineInput = () => {
export const BoardInlineCreateIssueForm: React.FC<Props> = (props) => ( export const BoardInlineCreateIssueForm: React.FC<Props> = (props) => (
<> <>
<InlineCreateIssueFormWrapper <InlineCreateIssueFormWrapper
className="flex flex-col justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px] mb-3 rounded bg-custom-background-100 shadow" className="flex flex-col justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px] mb-3 rounded bg-custom-background-100 shadow-custom-shadow-sm"
{...props} {...props}
> >
<InlineInput /> <InlineInput />

View File

@ -184,7 +184,7 @@ export const CalendarView: React.FC<Props> = ({
<div className="h-full overflow-y-auto"> <div className="h-full overflow-y-auto">
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<div <div
id={`calendar-view-${cycleId ?? moduleId ?? viewId}`} id={`calendar-view-${cycleId ?? moduleId ?? viewId ?? ""}`}
className="h-full rounded-lg p-8 text-custom-text-200" className="h-full rounded-lg p-8 text-custom-text-200"
> >
<CalendarHeader <CalendarHeader

View File

@ -84,13 +84,13 @@ export const CalendarInlineCreateIssueForm: React.FC<Props> = (props) => {
<> <>
<div <div
ref={ref} ref={ref}
className={`absolute -translate-x-1 top-5 transition-all z-20 ${ className={`absolute w-60 top-5 transition-all z-20 ${
isOpen ? "opacity-100 scale-100" : "opacity-0 pointer-events-none scale-95" isOpen ? "opacity-100 scale-100" : "opacity-0 pointer-events-none scale-95"
} ${isSpaceOnRight ? "left-full" : "right-0"}`} } right-0`}
> >
<InlineCreateIssueFormWrapper <InlineCreateIssueFormWrapper
{...props} {...props}
className="flex w-60 p-1 px-1.5 rounded items-center gap-x-3 bg-custom-background-100 shadow-custom-shadow-md transition-opacity" className="flex w-full p-1 px-1.5 rounded z-50 items-center gap-x-3 bg-custom-background-100 shadow-custom-shadow-sm transition-opacity"
> >
<InlineInput /> <InlineInput />
</InlineCreateIssueFormWrapper> </InlineCreateIssueFormWrapper>

View File

@ -39,6 +39,8 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
const [showAllIssues, setShowAllIssues] = useState(false); const [showAllIssues, setShowAllIssues] = useState(false);
const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false); const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false);
const [formPosition, setFormPosition] = useState({ x: 0, y: 0 });
const totalIssues = date.issues.length; const totalIssues = date.issues.length;
return ( return (
@ -81,16 +83,23 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
</Draggable> </Draggable>
))} ))}
<CalendarInlineCreateIssueForm <div
isOpen={isCreateIssueFormOpen} className="fixed top-0 left-0 z-50"
dependencies={[showWeekEnds]} style={{
handleClose={() => setIsCreateIssueFormOpen(false)} transform: `translate(${formPosition.x}px, ${formPosition.y}px)`,
prePopulatedData={{
target_date: date.date,
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
}} }}
/> >
<CalendarInlineCreateIssueForm
isOpen={isCreateIssueFormOpen}
dependencies={[showWeekEnds]}
handleClose={() => setIsCreateIssueFormOpen(false)}
prePopulatedData={{
target_date: date.date,
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
}}
/>
</div>
{totalIssues > 4 && ( {totalIssues > 4 && (
<button <button
@ -106,8 +115,11 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
className={`absolute top-2 right-2 flex items-center justify-center rounded-md bg-custom-background-80 p-1 text-xs text-custom-text-200 opacity-0 group-hover:opacity-100`} className={`absolute top-2 right-2 flex items-center justify-center rounded-md bg-custom-background-80 p-1 text-xs text-custom-text-200 opacity-0 group-hover:opacity-100`}
> >
<button <button
onClick={(e) => {
setIsCreateIssueFormOpen(true);
setFormPosition({ x: e.clientX, y: e.clientY });
}}
className="flex items-center justify-center gap-1 text-center" className="flex items-center justify-center gap-1 text-center"
onClick={() => setIsCreateIssueFormOpen(true)}
> >
<PlusSmallIcon className="h-4 w-4 text-custom-text-200" /> <PlusSmallIcon className="h-4 w-4 text-custom-text-200" />
Add issue Add issue

View File

@ -39,7 +39,7 @@ const InlineInput = () => {
{...register("name", { {...register("name", {
required: "Issue title is required.", required: "Issue title is required.",
})} })}
className="w-full px-2 py-1.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none" className="w-full px-2 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
/> />
</> </>
); );

View File

@ -48,7 +48,7 @@ const InlineInput = () => {
export const ListInlineCreateIssueForm: React.FC<Props> = (props) => ( export const ListInlineCreateIssueForm: React.FC<Props> = (props) => (
<> <>
<InlineCreateIssueFormWrapper <InlineCreateIssueFormWrapper
className="flex py-3 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-md" className="flex py-3 px-4 items-center gap-x-5 bg-custom-background-100 shadow-custom-shadow-sm z-10"
{...props} {...props}
> >
<InlineInput /> <InlineInput />

View File

@ -7,6 +7,7 @@ import { KeyedMutator, mutate } from "swr";
// components // components
import { import {
ListInlineCreateIssueForm,
SpreadsheetAssigneeColumn, SpreadsheetAssigneeColumn,
SpreadsheetCreatedOnColumn, SpreadsheetCreatedOnColumn,
SpreadsheetDueDateColumn, SpreadsheetDueDateColumn,
@ -18,7 +19,7 @@ import {
SpreadsheetStateColumn, SpreadsheetStateColumn,
SpreadsheetUpdatedOnColumn, SpreadsheetUpdatedOnColumn,
} from "components/core"; } from "components/core";
import { Spinner } from "components/ui"; import { CustomMenu, Spinner } from "components/ui";
import { IssuePeekOverview } from "components/issues"; import { IssuePeekOverview } from "components/issues";
// hooks // hooks
import useIssuesProperties from "hooks/use-issue-properties"; import useIssuesProperties from "hooks/use-issue-properties";
@ -38,6 +39,7 @@ import {
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
import projectIssuesServices from "services/issues.service"; import projectIssuesServices from "services/issues.service";
// icon // icon
import { PlusIcon } from "lucide-react";
type Props = { type Props = {
spreadsheetIssues: IIssue[]; spreadsheetIssues: IIssue[];
@ -66,9 +68,13 @@ export const SpreadsheetView: React.FC<Props> = ({
const [expandedIssues, setExpandedIssues] = useState<string[]>([]); const [expandedIssues, setExpandedIssues] = useState<string[]>([]);
const [currentProjectId, setCurrentProjectId] = useState<string | null>(null); const [currentProjectId, setCurrentProjectId] = useState<string | null>(null);
const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId, workspaceViewId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId, workspaceViewId } = router.query;
const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
const workspaceIssuesPath = [ const workspaceIssuesPath = [
@ -232,51 +238,106 @@ export const SpreadsheetView: React.FC<Props> = ({
workspaceSlug={workspaceSlug?.toString() ?? ""} workspaceSlug={workspaceSlug?.toString() ?? ""}
readOnly={disableUserActions} readOnly={disableUserActions}
/> />
<div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100"> <div className="relative flex h-full w-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100">
{spreadsheetIssues ? ( <div className="h-full w-full flex flex-col">
<> <div className="flex max-h-full overflow-y-auto">
<div className="sticky left-0 w-[28rem] z-[2]"> {spreadsheetIssues ? (
<div className="relative flex flex-col h-max w-full bg-custom-background-100 rounded-sm z-[2]"> <>
<div className="flex items-center text-sm font-medium z-[2] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-200"> <div className="sticky left-0 w-[28rem] z-[2]">
<span className="flex items-center px-4 py-2.5 h-full w-20 flex-shrink-0"> <div className="relative flex flex-col h-max w-full bg-custom-background-100 rounded-sm z-[2]">
ID <div className="flex items-center text-sm font-medium z-[2] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-200">
</span> <span className="flex items-center px-4 py-2.5 h-full w-20 flex-shrink-0">
<span className="flex items-center px-4 py-2.5 h-full w-full flex-grow"> ID
Issue </span>
</span> <span className="flex items-center px-4 py-2.5 h-full w-full flex-grow">
</div> Issue
</span>
</div>
{spreadsheetIssues.map((issue: IIssue, index) => ( {spreadsheetIssues.map((issue: IIssue, index) => (
<SpreadsheetIssuesColumn <SpreadsheetIssuesColumn
key={`${issue.id}_${index}`} key={`${issue.id}_${index}`}
issue={issue} issue={issue}
projectId={issue.project_detail.id} projectId={issue.project_detail.id}
expandedIssues={expandedIssues} expandedIssues={expandedIssues}
setExpandedIssues={setExpandedIssues} setExpandedIssues={setExpandedIssues}
setCurrentProjectId={setCurrentProjectId} setCurrentProjectId={setCurrentProjectId}
properties={properties} properties={properties}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
userAuth={userAuth} userAuth={userAuth}
/> />
))} ))}
</div>
</div>
{renderColumn("State", SpreadsheetStateColumn)}
{renderColumn("Priority", SpreadsheetPriorityColumn)}
{renderColumn("Assignees", SpreadsheetAssigneeColumn)}
{renderColumn("Label", SpreadsheetLabelColumn)}
{renderColumn("Start Date", SpreadsheetStartDateColumn)}
{renderColumn("Due Date", SpreadsheetDueDateColumn)}
{renderColumn("Estimate", SpreadsheetEstimateColumn)}
{renderColumn("Created On", SpreadsheetCreatedOnColumn)}
{renderColumn("Updated On", SpreadsheetUpdatedOnColumn)}
</>
) : (
<div className="flex flex-col justify-center items-center h-full w-full">
<Spinner />
</div> </div>
</div> )}
{renderColumn("State", SpreadsheetStateColumn)}
{renderColumn("Priority", SpreadsheetPriorityColumn)}
{renderColumn("Assignees", SpreadsheetAssigneeColumn)}
{renderColumn("Label", SpreadsheetLabelColumn)}
{renderColumn("Start Date", SpreadsheetStartDateColumn)}
{renderColumn("Due Date", SpreadsheetDueDateColumn)}
{renderColumn("Estimate", SpreadsheetEstimateColumn)}
{renderColumn("Created On", SpreadsheetCreatedOnColumn)}
{renderColumn("Updated On", SpreadsheetUpdatedOnColumn)}
</>
) : (
<div className="flex flex-col justify-center items-center h-full w-full">
<Spinner />
</div> </div>
)}
<div>
<ListInlineCreateIssueForm
isOpen={isInlineCreateIssueFormOpen}
handleClose={() => setIsInlineCreateIssueFormOpen(false)}
prePopulatedData={{
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
}}
/>
{type === "issue"
? !disableUserActions &&
!isInlineCreateIssueFormOpen && (
<button
className="flex gap-1.5 items-center text-custom-primary-100 pl-7 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
onClick={() => setIsInlineCreateIssueFormOpen(true)}
>
<PlusIcon className="h-4 w-4" />
Add Issue
</button>
)
: !disableUserActions &&
!isInlineCreateIssueFormOpen && (
<CustomMenu
className="sticky left-0 z-10"
customButton={
<button
className="flex gap-1.5 items-center text-custom-primary-100 pl-7 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
type="button"
>
<PlusIcon className="h-4 w-4" />
Add Issue
</button>
}
position="left"
verticalPosition="top"
optionsClassName="left-5 !w-36"
noBorder
>
<CustomMenu.MenuItem onClick={() => setIsInlineCreateIssueFormOpen(true)}>
Create new
</CustomMenu.MenuItem>
{openIssuesListModal && (
<CustomMenu.MenuItem onClick={openIssuesListModal}>
Add an existing issue
</CustomMenu.MenuItem>
)}
</CustomMenu>
)}
</div>
</div>
</div> </div>
</> </>
); );

View File

@ -1,4 +1,6 @@
import { FC, useEffect, useState } from "react"; import { FC, useEffect, useState } from "react";
// next
import { useRouter } from "next/router";
// icons // icons
import { ArrowsPointingInIcon, ArrowsPointingOutIcon } from "@heroicons/react/20/solid"; import { ArrowsPointingInIcon, ArrowsPointingOutIcon } from "@heroicons/react/20/solid";
// components // components
@ -11,6 +13,8 @@ import { GanttSidebar } from "../sidebar";
import { MonthChartView } from "./month"; import { MonthChartView } from "./month";
// import { QuarterChartView } from "./quarter"; // import { QuarterChartView } from "./quarter";
// import { YearChartView } from "./year"; // import { YearChartView } from "./year";
// icons
import { PlusIcon } from "lucide-react";
// views // views
import { import {
// generateHourChart, // generateHourChart,
@ -25,6 +29,7 @@ import {
getNumberOfDaysBetweenTwoDatesInYear, getNumberOfDaysBetweenTwoDatesInYear,
getMonthChartItemPositionWidthInMonth, getMonthChartItemPositionWidthInMonth,
} from "../views"; } from "../views";
import { GanttInlineCreateIssueForm } from "components/core/views/gantt-chart-view/inline-create-issue-form";
// types // types
import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types"; import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types";
// data // data
@ -64,12 +69,17 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
const [itemsContainerWidth, setItemsContainerWidth] = useState<number>(0); const [itemsContainerWidth, setItemsContainerWidth] = useState<number>(0);
const [fullScreenMode, setFullScreenMode] = useState<boolean>(false); const [fullScreenMode, setFullScreenMode] = useState<boolean>(false);
const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false);
// blocks state management starts // blocks state management starts
const [chartBlocks, setChartBlocks] = useState<IGanttBlock[] | null>(null); const [chartBlocks, setChartBlocks] = useState<IGanttBlock[] | null>(null);
const { currentView, currentViewData, renderView, dispatch, allViews, updateScrollLeft } = const { currentView, currentViewData, renderView, dispatch, allViews, updateScrollLeft } =
useChart(); useChart();
const router = useRouter();
const { cycleId, moduleId } = router.query;
const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) => const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) =>
blocks && blocks.length > 0 blocks && blocks.length > 0
? blocks.map((block: any) => ({ ? blocks.map((block: any) => ({
@ -304,6 +314,44 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
SidebarBlockRender={SidebarBlockRender} SidebarBlockRender={SidebarBlockRender}
enableReorder={enableReorder} enableReorder={enableReorder}
/> />
{chartBlocks && (
<div className="pl-2.5">
<GanttInlineCreateIssueForm
isOpen={isCreateIssueFormOpen}
handleClose={() => setIsCreateIssueFormOpen(false)}
onSuccess={() => {
const ganttSidebar = document.getElementById(`gantt-sidebar-${cycleId}`);
const timeoutId = setTimeout(() => {
if (ganttSidebar)
ganttSidebar.scrollBy({
top: ganttSidebar.scrollHeight,
left: 0,
behavior: "smooth",
});
clearTimeout(timeoutId);
}, 10);
}}
prePopulatedData={{
start_date: new Date(Date.now()).toISOString().split("T")[0],
target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0],
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
}}
/>
{!isCreateIssueFormOpen && (
<button
type="button"
onClick={() => setIsCreateIssueFormOpen(true)}
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 py-1 rounded-md"
>
<PlusIcon className="h-4 w-4" />
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
</button>
)}
</div>
)}
</div> </div>
<div <div
className="relative flex h-full w-full flex-1 flex-col overflow-hidden overflow-x-auto horizontal-scroll-enable" className="relative flex h-full w-full flex-1 flex-col overflow-hidden overflow-x-auto horizontal-scroll-enable"

View File

@ -1,5 +1,3 @@
import { useState } from "react";
// next
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// react-beautiful-dnd // react-beautiful-dnd
import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd"; import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd";
@ -10,9 +8,6 @@ import { useChart } from "./hooks";
import { Loader } from "components/ui"; import { Loader } from "components/ui";
// icons // icons
import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; import { EllipsisVerticalIcon } from "@heroicons/react/24/outline";
import { PlusIcon } from "lucide-react";
// components
import { GanttInlineCreateIssueForm } from "components/core/views/gantt-chart-view/inline-create-issue-form";
// types // types
import { IBlockUpdateData, IGanttBlock } from "./types"; import { IBlockUpdateData, IGanttBlock } from "./types";
@ -28,12 +23,10 @@ export const GanttSidebar: React.FC<Props> = (props) => {
const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder } = props; const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder } = props;
const router = useRouter(); const router = useRouter();
const { cycleId, moduleId } = router.query; const { cycleId } = router.query;
const { activeBlock, dispatch } = useChart(); const { activeBlock, dispatch } = useChart();
const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false);
// update the active block on hover // update the active block on hover
const updateActiveBlock = (block: IGanttBlock | null) => { const updateActiveBlock = (block: IGanttBlock | null) => {
dispatch({ dispatch({
@ -93,7 +86,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
{(droppableProvided) => ( {(droppableProvided) => (
<div <div
id={`gantt-sidebar-${cycleId}`} id={`gantt-sidebar-${cycleId}`}
className="h-full overflow-y-auto pl-2.5" className="max-h-full overflow-y-auto pl-2.5"
ref={droppableProvided.innerRef} ref={droppableProvided.innerRef}
{...droppableProvided.droppableProps} {...droppableProvided.droppableProps}
> >
@ -159,42 +152,6 @@ export const GanttSidebar: React.FC<Props> = (props) => {
</div> </div>
)} )}
</StrictModeDroppable> </StrictModeDroppable>
<div className="pl-2.5">
<GanttInlineCreateIssueForm
isOpen={isCreateIssueFormOpen}
handleClose={() => setIsCreateIssueFormOpen(false)}
onSuccess={() => {
const ganttSidebar = document.getElementById(`gantt-sidebar-${cycleId}`);
const timeoutId = setTimeout(() => {
if (ganttSidebar)
ganttSidebar.scrollBy({
top: ganttSidebar.scrollHeight,
left: 0,
behavior: "smooth",
});
clearTimeout(timeoutId);
}, 10);
}}
prePopulatedData={{
start_date: new Date(Date.now()).toISOString().split("T")[0],
target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0],
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
}}
/>
{!isCreateIssueFormOpen && (
<button
type="button"
onClick={() => setIsCreateIssueFormOpen(true)}
className="flex items-center gap-x-[6px] text-custom-primary-100 px-2 py-1 rounded-md mt-3"
>
<PlusIcon className="h-4 w-4" />
<span className="text-sm font-medium text-custom-primary-100">New Issue</span>
</button>
)}
</div>
</DragDropContext> </DragDropContext>
); );
}; };