snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { IGroupHeaderProps } from "@plane/types";
|
||||
|
||||
export function GroupHeader({ group, itemCount, onToggleGroup }: IGroupHeaderProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => onToggleGroup(group.id)}
|
||||
className="flex w-full items-center gap-2 text-13 font-medium text-secondary"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{group.icon}
|
||||
<span>{group.name}</span>
|
||||
</div>
|
||||
<span className="text-11 text-tertiary">{itemCount}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
104
apps/web/core/components/base-layouts/kanban/group.tsx
Normal file
104
apps/web/core/components/base-layouts/kanban/group.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { IBaseLayoutsKanbanItem, IBaseLayoutsKanbanGroupProps } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useGroupDropTarget } from "../hooks/use-group-drop-target";
|
||||
import { GroupHeader } from "./group-header";
|
||||
import { BaseKanbanItem } from "./item";
|
||||
|
||||
export const BaseKanbanGroup = observer(function BaseKanbanGroup<T extends IBaseLayoutsKanbanItem>(
|
||||
props: IBaseLayoutsKanbanGroupProps<T>
|
||||
) {
|
||||
const {
|
||||
group,
|
||||
itemIds,
|
||||
items,
|
||||
renderItem,
|
||||
renderGroupHeader,
|
||||
isCollapsed,
|
||||
onToggleGroup,
|
||||
enableDragDrop = false,
|
||||
onDrop,
|
||||
canDrag,
|
||||
groupClassName,
|
||||
loadMoreItems: _loadMoreItems,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { groupRef, isDraggingOver } = useGroupDropTarget({
|
||||
groupId: group.id,
|
||||
enableDragDrop,
|
||||
onDrop,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={groupRef}
|
||||
className={cn(
|
||||
"relative flex max-h-full w-[350px] flex-shrink-0 flex-col overflow-y-auto rounded-md border-[1px] border-transparent bg-layer-1 p-2 pt-0",
|
||||
{
|
||||
"bg-layer-1": isDraggingOver,
|
||||
},
|
||||
groupClassName
|
||||
)}
|
||||
>
|
||||
{/* Group Header */}
|
||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 cursor-pointer px-1 py-2">
|
||||
{renderGroupHeader ? (
|
||||
renderGroupHeader({ group, itemCount: itemIds.length, isCollapsed, onToggleGroup })
|
||||
) : (
|
||||
<GroupHeader
|
||||
group={group}
|
||||
itemCount={itemIds.length}
|
||||
isCollapsed={isCollapsed}
|
||||
onToggleGroup={onToggleGroup}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Group Items */}
|
||||
{!isCollapsed && (
|
||||
<div className="flex flex-col gap-2 py-2">
|
||||
{itemIds.map((itemId, index) => {
|
||||
const item = items[itemId];
|
||||
if (!item) return null;
|
||||
|
||||
return (
|
||||
<BaseKanbanItem
|
||||
key={itemId}
|
||||
item={item}
|
||||
index={index}
|
||||
groupId={group.id}
|
||||
renderItem={renderItem}
|
||||
enableDragDrop={enableDragDrop}
|
||||
canDrag={canDrag}
|
||||
onDrop={onDrop}
|
||||
isLast={index === itemIds.length - 1}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{itemIds.length === 0 && (
|
||||
<div className="flex items-center justify-center py-8 text-13 text-tertiary">
|
||||
{t("common.no_items_in_this_group")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDraggingOver && enableDragDrop && (
|
||||
<div className="absolute top-0 left-0 z-[2] flex h-full w-full items-center justify-center rounded-sm border-[1px] border-strong bg-layer-1/85 text-13 font-medium text-tertiary">
|
||||
<div className="my-8 flex flex-col items-center rounded-sm p-3 text-secondary">
|
||||
{t("common.drop_here_to_move")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
48
apps/web/core/components/base-layouts/kanban/item.tsx
Normal file
48
apps/web/core/components/base-layouts/kanban/item.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
||||
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IBaseLayoutsKanbanItem, IBaseLayoutsKanbanItemProps } from "@plane/types";
|
||||
|
||||
export const BaseKanbanItem = observer(function BaseKanbanItem<T extends IBaseLayoutsKanbanItem>(
|
||||
props: IBaseLayoutsKanbanItemProps<T>
|
||||
) {
|
||||
const { item, groupId, renderItem, enableDragDrop, canDrag } = props;
|
||||
|
||||
const itemRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const isDragAllowed = canDrag ? canDrag(item) : true;
|
||||
|
||||
// Setup draggable and drop target
|
||||
useEffect(() => {
|
||||
const element = itemRef.current;
|
||||
if (!element || !enableDragDrop) return;
|
||||
|
||||
return combine(
|
||||
draggable({
|
||||
element,
|
||||
canDrag: () => isDragAllowed,
|
||||
getInitialData: () => ({ id: item.id, type: "ITEM", groupId }),
|
||||
}),
|
||||
dropTargetForElements({
|
||||
element,
|
||||
getData: () => ({ id: item.id, groupId, type: "ITEM" }),
|
||||
canDrop: ({ source }) => source?.data?.id !== item.id,
|
||||
})
|
||||
);
|
||||
}, [enableDragDrop, isDragAllowed, item.id, groupId]);
|
||||
|
||||
const renderedItem = renderItem(item, groupId);
|
||||
|
||||
return (
|
||||
<div ref={itemRef} className="cursor-pointer">
|
||||
{renderedItem}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
74
apps/web/core/components/base-layouts/kanban/layout.tsx
Normal file
74
apps/web/core/components/base-layouts/kanban/layout.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import type { IBaseLayoutsKanbanItem, IBaseLayoutsKanbanProps } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
import { useLayoutState } from "../hooks/use-layout-state";
|
||||
import { BaseKanbanGroup } from "./group";
|
||||
|
||||
export const BaseKanbanLayout = observer(function BaseKanbanLayout<T extends IBaseLayoutsKanbanItem>(
|
||||
props: IBaseLayoutsKanbanProps<T>
|
||||
) {
|
||||
const {
|
||||
items,
|
||||
groups,
|
||||
groupedItemIds,
|
||||
renderItem,
|
||||
renderGroupHeader,
|
||||
onDrop,
|
||||
canDrag,
|
||||
className,
|
||||
groupClassName,
|
||||
showEmptyGroups = true,
|
||||
enableDragDrop = false,
|
||||
loadMoreItems,
|
||||
collapsedGroups: externalCollapsedGroups,
|
||||
onToggleGroup: externalOnToggleGroup,
|
||||
} = props;
|
||||
|
||||
const useExternalMode = externalCollapsedGroups !== undefined && externalOnToggleGroup !== undefined;
|
||||
const { containerRef, collapsedGroups, onToggleGroup } = useLayoutState(
|
||||
useExternalMode
|
||||
? {
|
||||
mode: "external",
|
||||
externalCollapsedGroups,
|
||||
externalOnToggleGroup,
|
||||
}
|
||||
: {
|
||||
mode: "internal",
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={cn("relative flex h-full w-full gap-2 overflow-x-auto p-3", className)}>
|
||||
{groups.map((group) => {
|
||||
const itemIds = groupedItemIds[group.id] || [];
|
||||
const isCollapsed = collapsedGroups.includes(group.id);
|
||||
|
||||
if (!showEmptyGroups && itemIds.length === 0) return null;
|
||||
|
||||
return (
|
||||
<BaseKanbanGroup
|
||||
key={group.id}
|
||||
group={group}
|
||||
itemIds={itemIds}
|
||||
items={items}
|
||||
renderItem={renderItem}
|
||||
renderGroupHeader={renderGroupHeader}
|
||||
isCollapsed={isCollapsed}
|
||||
onToggleGroup={onToggleGroup}
|
||||
enableDragDrop={enableDragDrop}
|
||||
onDrop={onDrop}
|
||||
canDrag={canDrag}
|
||||
groupClassName={groupClassName}
|
||||
loadMoreItems={loadMoreItems}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user