-
Notifications
You must be signed in to change notification settings - Fork 67
feat: Enable drag-and-drop tasks to agenda to create timed and all-day events #1398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
f2ea0b6
d1d7223
e1f9339
2bc3ae7
1f89668
4b49359
81ebc37
dc5a0b8
abaed15
68a730a
a23883e
12a1430
c89b828
5af5d6e
ee85d21
35691f3
2be21b3
dbec324
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,12 +18,17 @@ import { | |
| import { CSS } from "@dnd-kit/utilities"; | ||
| import { useMergeRefs } from "@floating-ui/react"; | ||
| import { Categories_Event } from "@core/types/event.types"; | ||
| import { Task } from "@web/common/types/task.types"; | ||
| import { Schema_GridEvent } from "@web/common/types/web.event.types"; | ||
|
|
||
| export type DraggableDataType = Categories_Event | "task"; | ||
|
|
||
| export interface DraggableDNDData { | ||
| type: Categories_Event; | ||
| event: Schema_GridEvent | null; | ||
| type: DraggableDataType; | ||
| event?: Schema_GridEvent | null; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strong type, don't let these be optional
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed all fields to required. Components now explicitly provide null values when fields don't apply (e.g., |
||
| task?: Task | null; | ||
| view: "day" | "week" | "now"; | ||
| deleteTask?: () => void; | ||
| } | ||
|
|
||
| export interface DNDChildProps | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. separate these components into separate files for legibility
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Separated DraggableTaskHandle into its own file at |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,10 @@ | ||
| import classNames from "classnames"; | ||
| import { useDraggable } from "@dnd-kit/core"; | ||
| import { CSS } from "@dnd-kit/utilities"; | ||
| import { autoUpdate, inline, offset, useFloating } from "@floating-ui/react"; | ||
| import { Draggable } from "@hello-pangea/dnd"; | ||
| import { DotsSixVerticalIcon } from "@phosphor-icons/react"; | ||
| import { Task as ITask } from "@web/common/types/task.types"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just use Task, not ITask
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed |
||
| import { getStyle } from "@web/views/Calendar/components/Sidebar/SomedayTab/SomedayEvents/SomedayEvent/styled"; | ||
| import { DNDChildProps, Draggable } from "@web/components/DND/Draggable"; | ||
| import { Task } from "@web/views/Day/components/Task/Task"; | ||
| import { useTasks } from "@web/views/Day/hooks/tasks/useTasks"; | ||
|
|
||
|
|
@@ -38,79 +39,138 @@ export function DraggableTask({ | |
| onTitleChange, | ||
| onStatusToggle, | ||
| migrateTask, | ||
| deleteTask, | ||
| } = tasksProps; | ||
|
|
||
| return ( | ||
| <Draggable | ||
| draggableId={task.id} | ||
| index={index} | ||
| isDragDisabled={tasks.length === 1} | ||
| disableInteractiveElementBlocking | ||
| dndProps={{ | ||
| id: task.id, | ||
| data: { | ||
| type: "task", | ||
| task, | ||
| view: "day", | ||
| deleteTask: () => deleteTask(task.id), | ||
| }, | ||
| disabled: tasks.length === 1, | ||
| }} | ||
| as="div" | ||
| id={task.id} | ||
| className={`group relative mr-2 select-none`} | ||
| ref={(e) => { | ||
| refs.setReference(e); | ||
| update(); | ||
| }} | ||
| asChild | ||
| > | ||
| {(draggableProvider, draggableSnapshot) => ( | ||
| <div | ||
| {...draggableProvider.draggableProps} | ||
| id={task.id} | ||
| className={`group relative mr-2 select-none`} | ||
| style={getStyle( | ||
| draggableSnapshot, | ||
| false, | ||
| draggableProvider.draggableProps.style, | ||
| <DraggableTaskInner | ||
| task={task} | ||
| index={index} | ||
| tasks={tasks} | ||
| editingTaskId={editingTaskId} | ||
| editingTitle={editingTitle} | ||
| setSelectedTaskIndex={setSelectedTaskIndex} | ||
| onCheckboxKeyDown={onCheckboxKeyDown} | ||
| onInputBlur={onInputBlur} | ||
| onInputClick={onInputClick} | ||
| onInputKeyDown={onInputKeyDown} | ||
| onTitleChange={onTitleChange} | ||
| onStatusToggle={onStatusToggle} | ||
| migrateTask={migrateTask} | ||
| refs={refs} | ||
| floatingStyles={floatingStyles} | ||
| /> | ||
| </Draggable> | ||
| ); | ||
| } | ||
|
|
||
| function DraggableTaskInner({ | ||
| task, | ||
| index, | ||
| tasks, | ||
| editingTaskId, | ||
| editingTitle, | ||
| setSelectedTaskIndex, | ||
| onCheckboxKeyDown, | ||
| onInputBlur, | ||
| onInputClick, | ||
| onInputKeyDown, | ||
| onTitleChange, | ||
| onStatusToggle, | ||
| migrateTask, | ||
| refs, | ||
| floatingStyles, | ||
| dndProps, | ||
| }: { | ||
| task: ITask; | ||
| index: number; | ||
| tasks: ITask[]; | ||
| editingTaskId: string | null; | ||
| editingTitle: string; | ||
| setSelectedTaskIndex: (index: number) => void; | ||
| onCheckboxKeyDown: ( | ||
| e: React.KeyboardEvent, | ||
| taskId: string, | ||
| title: string, | ||
| ) => void; | ||
| onInputBlur: (taskId: string) => void; | ||
| onInputClick: (taskId: string) => void; | ||
| onInputKeyDown: (e: React.KeyboardEvent, taskId: string) => void; | ||
| onTitleChange: (title: string) => void; | ||
| onStatusToggle: (id: string) => void; | ||
| migrateTask: (id: string, direction: "forward" | "backward") => void; | ||
| refs: ReturnType<typeof useFloating>["refs"]; | ||
| floatingStyles: React.CSSProperties; | ||
| dndProps?: DNDChildProps; | ||
| }) { | ||
| const isDragging = dndProps?.isDragging ?? false; | ||
|
|
||
| return ( | ||
| <div> | ||
| {tasks.length > 1 ? ( | ||
| <button | ||
| {...(dndProps?.listeners ?? {})} | ||
| ref={refs.setFloating} | ||
| style={floatingStyles} | ||
| aria-label={`Reorder ${task.title}`} | ||
| aria-describedby={`description-${task.id}`} | ||
| onFocus={() => setSelectedTaskIndex(index)} | ||
| className={classNames( | ||
| "opacity-0", | ||
| "hover:bg-border-primary hover:cursor-grab", | ||
| "rounded-xs py-2 transition-colors", | ||
| "group-hover:opacity-100 hover:opacity-100 focus:opacity-100", | ||
| "max-w-48 text-white", | ||
| "focus:bg-white/20 focus:ring-2 focus:ring-white/50", | ||
| "focus:outline-none disabled:cursor-default disabled:opacity-0", | ||
| { | ||
| hidden: tasks.length === 1, | ||
| "opacity-100": isDragging, | ||
| }, | ||
| )} | ||
| ref={(e) => { | ||
| draggableProvider.innerRef(e); | ||
| refs.setReference(e); | ||
| update(); | ||
| }} | ||
| > | ||
| {tasks.length > 1 ? ( | ||
| <button | ||
| {...draggableProvider.dragHandleProps} | ||
| ref={refs.setFloating} | ||
| style={floatingStyles} | ||
| aria-label={`Reorder ${task.title}`} | ||
| aria-describedby={`description-${task.id}`} | ||
| onFocus={() => setSelectedTaskIndex(index)} | ||
| className={classNames( | ||
| "opacity-0", | ||
| "hover:bg-border-primary hover:cursor-grab", | ||
| "rounded-xs py-2 transition-colors", | ||
| "group-hover:opacity-100 hover:opacity-100 focus:opacity-100", | ||
| "max-w-48 text-white", | ||
| "focus:bg-white/20 focus:ring-2 focus:ring-white/50", | ||
| "focus:outline-none disabled:cursor-default disabled:opacity-0", | ||
| { | ||
| hidden: tasks.length === 1, | ||
| "opacity-100": | ||
| draggableSnapshot.isDragging || | ||
| draggableSnapshot.isDropAnimating, | ||
| }, | ||
| )} | ||
| > | ||
| <DotsSixVerticalIcon size={24} /> | ||
| </button> | ||
| ) : null} | ||
| <DotsSixVerticalIcon size={24} /> | ||
| </button> | ||
| ) : null} | ||
|
|
||
| <Task | ||
| task={task} | ||
| index={index} | ||
| isEditing={editingTaskId === task.id} | ||
| onFocus={setSelectedTaskIndex} | ||
| onCheckboxKeyDown={onCheckboxKeyDown} | ||
| onInputBlur={onInputBlur} | ||
| onInputKeyDown={onInputKeyDown} | ||
| onInputClick={onInputClick} | ||
| onTitleChange={onTitleChange} | ||
| onStatusToggle={onStatusToggle} | ||
| onMigrate={migrateTask} | ||
| title={editingTaskId === task.id ? editingTitle : task.title} | ||
| /> | ||
| <Task | ||
| task={task} | ||
| index={index} | ||
| isEditing={editingTaskId === task.id} | ||
| onFocus={setSelectedTaskIndex} | ||
| onCheckboxKeyDown={onCheckboxKeyDown} | ||
| onInputBlur={onInputBlur} | ||
| onInputKeyDown={onInputKeyDown} | ||
| onInputClick={onInputClick} | ||
| onTitleChange={onTitleChange} | ||
| onStatusToggle={onStatusToggle} | ||
| onMigrate={migrateTask} | ||
| title={editingTaskId === task.id ? editingTitle : task.title} | ||
| /> | ||
|
|
||
| <div id={`description-${task.id}`} className="hidden"> | ||
| Press space to start dragging this task. | ||
| </div> | ||
| </div> | ||
| )} | ||
| </Draggable> | ||
| <div id={`description-${task.id}`} className="hidden"> | ||
| Press space to start dragging this task. | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dont add dateInView as an arg here. Instead, infer the data from the URL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to infer
dateInViewfrom URL context using theuseDateInViewhook. Removed the parameter fromuseEventDNDActions. (commit dc5a0b8)