@dxos/plugin-kanban 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/blueprints/index.mjs +23 -4
- package/dist/lib/browser/blueprints/index.mjs.map +4 -4
- package/dist/lib/browser/chunk-A3PBV3S5.mjs +105 -0
- package/dist/lib/browser/chunk-A3PBV3S5.mjs.map +7 -0
- package/dist/lib/browser/delete-card-VPNVIWOA.mjs +32 -0
- package/dist/lib/browser/delete-card-VPNVIWOA.mjs.map +7 -0
- package/dist/lib/browser/delete-card-field-4HHF2GYX.mjs +50 -0
- package/dist/lib/browser/delete-card-field-4HHF2GYX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +84 -61
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/operations/index.mjs +13 -0
- package/dist/lib/browser/operations/index.mjs.map +7 -0
- package/dist/lib/browser/restore-card-4GG2RYKR.mjs +29 -0
- package/dist/lib/browser/restore-card-4GG2RYKR.mjs.map +7 -0
- package/dist/lib/browser/restore-card-field-3T26ACYX.mjs +48 -0
- package/dist/lib/browser/restore-card-field-3T26ACYX.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +94 -8
- package/dist/lib/browser/types/index.mjs.map +4 -4
- package/dist/lib/node-esm/blueprints/index.mjs +23 -4
- package/dist/lib/node-esm/blueprints/index.mjs.map +4 -4
- package/dist/lib/node-esm/chunk-6LELYA2G.mjs +106 -0
- package/dist/lib/node-esm/chunk-6LELYA2G.mjs.map +7 -0
- package/dist/lib/node-esm/delete-card-5PW5OMFN.mjs +33 -0
- package/dist/lib/node-esm/delete-card-5PW5OMFN.mjs.map +7 -0
- package/dist/lib/node-esm/delete-card-field-KPJU2AQ3.mjs +51 -0
- package/dist/lib/node-esm/delete-card-field-KPJU2AQ3.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +84 -61
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/operations/index.mjs +14 -0
- package/dist/lib/node-esm/operations/index.mjs.map +7 -0
- package/dist/lib/node-esm/restore-card-X2TKMU5A.mjs +30 -0
- package/dist/lib/node-esm/restore-card-X2TKMU5A.mjs.map +7 -0
- package/dist/lib/node-esm/restore-card-field-IUTL4RTR.mjs +49 -0
- package/dist/lib/node-esm/restore-card-field-IUTL4RTR.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +94 -8
- package/dist/lib/node-esm/types/index.mjs.map +4 -4
- package/dist/types/src/KanbanPlugin.d.ts.map +1 -1
- package/dist/types/src/blueprints/index.d.ts +1 -1
- package/dist/types/src/blueprints/index.d.ts.map +1 -1
- package/dist/types/src/blueprints/kanban-blueprint.d.ts +3 -21
- package/dist/types/src/blueprints/kanban-blueprint.d.ts.map +1 -1
- package/dist/types/src/capabilities/{artifact-definition/artifact-definition.d.ts → artifact-definition.d.ts} +1 -1
- package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -0
- package/dist/types/src/capabilities/blueprint-definition.d.ts +6 -0
- package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +6 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/undo-mappings.d.ts +5 -0
- package/dist/types/src/capabilities/undo-mappings.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +65 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +72 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts +10 -0
- package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts +9 -0
- package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/index.d.ts +2 -0
- package/dist/types/src/components/KanbanBoard/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts +6 -0
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts +79 -0
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanContainer/index.d.ts +3 -0
- package/dist/types/src/containers/KanbanContainer/index.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts +6 -0
- package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanViewEditor/index.d.ts +3 -0
- package/dist/types/src/containers/KanbanViewEditor/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +4 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +6 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useEchoChangeCallback.d.ts +13 -0
- package/dist/types/src/hooks/useEchoChangeCallback.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanBoardModel.d.ts +16 -0
- package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts +2 -0
- package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +22 -0
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +19 -0
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts +15 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/operations/definitions.d.ts +52 -0
- package/dist/types/src/operations/definitions.d.ts.map +1 -0
- package/dist/types/src/operations/delete-card-field.d.ts +5 -0
- package/dist/types/src/operations/delete-card-field.d.ts.map +1 -0
- package/dist/types/src/operations/delete-card.d.ts +5 -0
- package/dist/types/src/operations/delete-card.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +4 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/restore-card-field.d.ts +5 -0
- package/dist/types/src/operations/restore-card-field.d.ts.map +1 -0
- package/dist/types/src/operations/restore-card.d.ts +5 -0
- package/dist/types/src/operations/restore-card.d.ts.map +1 -0
- package/dist/types/src/playwright/board-manager.d.ts +5 -0
- package/dist/types/src/playwright/board-manager.d.ts.map +1 -0
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
- package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
- package/dist/types/src/testing/KanbanCardTileSimple.d.ts +7 -0
- package/dist/types/src/testing/KanbanCardTileSimple.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +48 -32
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Kanban.d.ts +37 -0
- package/dist/types/src/types/Kanban.d.ts.map +1 -0
- package/dist/types/src/types/constants.d.ts +6 -0
- package/dist/types/src/types/constants.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +0 -103
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +28 -0
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util/arrangement.d.ts +68 -0
- package/dist/types/src/util/arrangement.d.ts.map +1 -0
- package/dist/types/src/util/arrangement.test.d.ts +2 -0
- package/dist/types/src/util/arrangement.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +64 -42
- package/src/KanbanPlugin.tsx +35 -23
- package/src/blueprints/index.ts +1 -1
- package/src/blueprints/kanban-blueprint.ts +12 -8
- package/src/capabilities/{artifact-definition/artifact-definition.ts → artifact-definition.ts} +10 -9
- package/src/capabilities/blueprint-definition.ts +17 -0
- package/src/capabilities/index.ts +10 -3
- package/src/capabilities/operation-handler.ts +14 -0
- package/src/capabilities/{react-surface/react-surface.tsx → react-surface.tsx} +15 -15
- package/src/capabilities/undo-mappings.ts +34 -0
- package/src/components/KanbanBoard/KanbanBoard.stories.tsx +142 -0
- package/src/components/KanbanBoard/KanbanBoard.tsx +193 -0
- package/src/components/KanbanBoard/KanbanCard.tsx +86 -0
- package/src/components/KanbanBoard/KanbanColumn.tsx +69 -0
- package/src/components/KanbanBoard/index.ts +5 -0
- package/src/components/index.ts +1 -2
- package/src/{components → containers/KanbanContainer}/KanbanContainer.stories.tsx +70 -87
- package/src/containers/KanbanContainer/KanbanContainer.tsx +96 -0
- package/src/containers/KanbanContainer/index.ts +7 -0
- package/src/{components → containers/KanbanViewEditor}/KanbanViewEditor.tsx +23 -19
- package/src/containers/KanbanViewEditor/index.ts +7 -0
- package/src/containers/index.ts +8 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useEchoChangeCallback.ts +30 -0
- package/src/hooks/useKanbanBoardModel.test.ts +235 -0
- package/src/hooks/useKanbanBoardModel.ts +143 -0
- package/src/hooks/useKanbanColumnEventHandler.ts +106 -0
- package/src/hooks/useKanbanItemEventHandler.ts +133 -0
- package/src/hooks/useProjectionModel.ts +58 -0
- package/src/meta.ts +1 -1
- package/src/operations/definitions.ts +63 -0
- package/src/operations/delete-card-field.ts +47 -0
- package/src/operations/delete-card.ts +23 -0
- package/src/operations/index.ts +12 -0
- package/src/operations/restore-card-field.ts +41 -0
- package/src/operations/restore-card.ts +21 -0
- package/src/playwright/board-manager.ts +13 -0
- package/src/playwright/playwright.config.ts +19 -0
- package/src/playwright/smoke.spec.ts +107 -0
- package/src/testing/KanbanCardTileSimple.tsx +82 -0
- package/src/testing/index.ts +5 -0
- package/src/translations.ts +26 -18
- package/src/types/Kanban.ts +71 -0
- package/src/types/constants.ts +9 -0
- package/src/types/index.ts +2 -0
- package/src/types/schema.ts +0 -76
- package/src/types/types.ts +35 -0
- package/src/util/arrangement.test.ts +208 -0
- package/src/util/arrangement.ts +167 -0
- package/src/util/index.ts +5 -0
- package/dist/lib/browser/blueprint-definition-T2544VMJ.mjs +0 -17
- package/dist/lib/browser/blueprint-definition-T2544VMJ.mjs.map +0 -7
- package/dist/lib/browser/chunk-L6N4ZDZ7.mjs +0 -35
- package/dist/lib/browser/chunk-L6N4ZDZ7.mjs.map +0 -7
- package/dist/lib/browser/chunk-XYQO4VL7.mjs +0 -150
- package/dist/lib/browser/chunk-XYQO4VL7.mjs.map +0 -7
- package/dist/lib/browser/operation-resolver-UEJHX42A.mjs +0 -162
- package/dist/lib/browser/operation-resolver-UEJHX42A.mjs.map +0 -7
- package/dist/lib/browser/react-surface-LFUJAPRL.mjs +0 -236
- package/dist/lib/browser/react-surface-LFUJAPRL.mjs.map +0 -7
- package/dist/lib/node-esm/blueprint-definition-APJQFSHJ.mjs +0 -18
- package/dist/lib/node-esm/blueprint-definition-APJQFSHJ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NN6JMKIT.mjs +0 -152
- package/dist/lib/node-esm/chunk-NN6JMKIT.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZHRMUKTF.mjs +0 -36
- package/dist/lib/node-esm/chunk-ZHRMUKTF.mjs.map +0 -7
- package/dist/lib/node-esm/operation-resolver-5RPWHZCF.mjs +0 -163
- package/dist/lib/node-esm/operation-resolver-5RPWHZCF.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-7TSGBRJL.mjs +0 -237
- package/dist/lib/node-esm/react-surface-7TSGBRJL.mjs.map +0 -7
- package/dist/types/src/capabilities/artifact-definition/artifact-definition.d.ts.map +0 -1
- package/dist/types/src/capabilities/artifact-definition/index.d.ts +0 -3
- package/dist/types/src/capabilities/artifact-definition/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts +0 -9
- package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts.map +0 -1
- package/dist/types/src/capabilities/blueprint-definition/index.d.ts +0 -3
- package/dist/types/src/capabilities/blueprint-definition/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +0 -5
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
- package/dist/types/src/components/KanbanContainer.d.ts +0 -6
- package/dist/types/src/components/KanbanContainer.d.ts.map +0 -1
- package/dist/types/src/components/KanbanContainer.stories.d.ts +0 -75
- package/dist/types/src/components/KanbanContainer.stories.d.ts.map +0 -1
- package/dist/types/src/components/KanbanViewEditor.d.ts +0 -8
- package/dist/types/src/components/KanbanViewEditor.d.ts.map +0 -1
- package/src/capabilities/artifact-definition/index.ts +0 -7
- package/src/capabilities/blueprint-definition/blueprint-definition.ts +0 -23
- package/src/capabilities/blueprint-definition/index.ts +0 -7
- package/src/capabilities/operation-resolver/index.ts +0 -7
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -133
- package/src/capabilities/react-surface/index.ts +0 -7
- package/src/components/KanbanContainer.tsx +0 -86
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Atom, RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import { createContext } from '@radix-ui/react-context';
|
|
7
|
+
import React, {
|
|
8
|
+
type ComponentPropsWithoutRef,
|
|
9
|
+
type ComponentType,
|
|
10
|
+
type PropsWithChildren,
|
|
11
|
+
useCallback,
|
|
12
|
+
useContext,
|
|
13
|
+
useMemo,
|
|
14
|
+
} from 'react';
|
|
15
|
+
|
|
16
|
+
import { Obj } from '@dxos/echo';
|
|
17
|
+
import { useTranslation } from '@dxos/react-ui';
|
|
18
|
+
import { Board, useBoard } from '@dxos/react-ui-mosaic';
|
|
19
|
+
import type { ProjectionModel } from '@dxos/schema';
|
|
20
|
+
import { composable, composableProps } from '@dxos/ui-theme';
|
|
21
|
+
|
|
22
|
+
import { useKanbanBoardModel, useKanbanColumnEventHandler } from '#hooks';
|
|
23
|
+
import { meta } from '#meta';
|
|
24
|
+
import { type Kanban, type KanbanChangeCallback, UNCATEGORIZED_ATTRIBUTES, UNCATEGORIZED_VALUE } from '#types';
|
|
25
|
+
|
|
26
|
+
import { KanbanCard, type KanbanCardProps } from './KanbanCard';
|
|
27
|
+
import { KanbanColumn, type KanbanColumnProps } from './KanbanColumn';
|
|
28
|
+
|
|
29
|
+
// TODO(burdon): Rename Kanban.
|
|
30
|
+
|
|
31
|
+
//
|
|
32
|
+
// Context
|
|
33
|
+
//
|
|
34
|
+
|
|
35
|
+
const KANBAN_BOARD_NAME = 'KanbanBoard.Context';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Context value for the Kanban board.
|
|
39
|
+
* Items are Echo objects (Obj.Unknown).
|
|
40
|
+
*/
|
|
41
|
+
type KanbanBoardContextValue = {
|
|
42
|
+
kanbanId: string;
|
|
43
|
+
projection: ProjectionModel | undefined;
|
|
44
|
+
columnFieldPath: string | undefined;
|
|
45
|
+
change: KanbanChangeCallback<Obj.Unknown>;
|
|
46
|
+
pivotFieldId: string | undefined;
|
|
47
|
+
getPivotAttributes: (columnValue: string) => { title: string; color: string };
|
|
48
|
+
itemTile?: ComponentType<KanbanCardProps>; // TODO(burdon): Prop.
|
|
49
|
+
onCardAdd?: (columnValue: string | undefined) => string | undefined;
|
|
50
|
+
onCardRemove?: (card: Obj.Unknown) => void;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const [KanbanBoardContext, useKanbanBoard] = createContext<KanbanBoardContextValue>(KANBAN_BOARD_NAME, {
|
|
54
|
+
kanbanId: 'never',
|
|
55
|
+
projection: undefined,
|
|
56
|
+
columnFieldPath: undefined,
|
|
57
|
+
change: { kanban: () => {}, setItemField: () => {} },
|
|
58
|
+
pivotFieldId: undefined,
|
|
59
|
+
getPivotAttributes: (id: string) =>
|
|
60
|
+
id === UNCATEGORIZED_VALUE ? UNCATEGORIZED_ATTRIBUTES : { title: id, color: 'neutral' },
|
|
61
|
+
itemTile: (() => null) as ComponentType<KanbanCardProps>,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
//
|
|
65
|
+
// Root
|
|
66
|
+
//
|
|
67
|
+
|
|
68
|
+
const KANBAN_BOARD_ROOT = 'KanbanBoard.Root';
|
|
69
|
+
|
|
70
|
+
type KanbanBoardRootProps = PropsWithChildren<
|
|
71
|
+
{
|
|
72
|
+
kanban: Kanban.Kanban;
|
|
73
|
+
/** Required when providing context; Root derives columnFieldPath, pivotFieldId, getPivotAttributes from kanban + projection. */
|
|
74
|
+
projection: ProjectionModel;
|
|
75
|
+
/** Atom of items (e.g. from AtomQuery for DB, or Atom.make([]) for in-memory). */
|
|
76
|
+
items: Atom.Atom<Obj.Unknown[]>;
|
|
77
|
+
onCardAdd?: (columnValue: string | undefined) => string | undefined;
|
|
78
|
+
onCardRemove?: (card: Obj.Unknown) => void;
|
|
79
|
+
} & Pick<KanbanBoardContextValue, 'change' | 'itemTile'> &
|
|
80
|
+
ComponentPropsWithoutRef<'div'>
|
|
81
|
+
>;
|
|
82
|
+
|
|
83
|
+
export const KanbanBoardRoot = ({
|
|
84
|
+
children,
|
|
85
|
+
kanban,
|
|
86
|
+
projection,
|
|
87
|
+
items,
|
|
88
|
+
change,
|
|
89
|
+
itemTile = KanbanCard,
|
|
90
|
+
onCardAdd,
|
|
91
|
+
onCardRemove,
|
|
92
|
+
}: KanbanBoardRootProps) => {
|
|
93
|
+
const registry = useContext(RegistryContext);
|
|
94
|
+
const { t } = useTranslation(meta.id);
|
|
95
|
+
const model = useKanbanBoardModel(kanban, projection, items, registry);
|
|
96
|
+
const columns = model?.getColumns?.() ?? [];
|
|
97
|
+
const view = kanban?.view?.target;
|
|
98
|
+
const pivotFieldId = view?.projection?.pivotFieldId;
|
|
99
|
+
const columnFieldPath = useMemo(() => {
|
|
100
|
+
if (pivotFieldId === undefined || !projection) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return projection.tryGetFieldProjection(pivotFieldId)?.props.property;
|
|
105
|
+
}, [projection, pivotFieldId]);
|
|
106
|
+
|
|
107
|
+
const getPivotAttributes = useCallback<KanbanBoardContextValue['getPivotAttributes']>(
|
|
108
|
+
(columnValue) => {
|
|
109
|
+
if (columnValue === UNCATEGORIZED_VALUE) {
|
|
110
|
+
return UNCATEGORIZED_ATTRIBUTES;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const options = projection?.tryGetFieldProjection(pivotFieldId ?? '')?.props.options ?? [];
|
|
114
|
+
const option = options.find((option) => option.id === columnValue);
|
|
115
|
+
return option ?? ({ title: columnValue, color: 'neutral' } as const);
|
|
116
|
+
},
|
|
117
|
+
[projection, pivotFieldId],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (columns.length === 0) {
|
|
121
|
+
return (
|
|
122
|
+
<div role='none' className='flex flex-1 items-center justify-center p-8 text-center text-description'>
|
|
123
|
+
{t('select-pivot.placeholder')}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<KanbanBoardContext
|
|
130
|
+
kanbanId={Obj.getDXN(kanban).toString()}
|
|
131
|
+
projection={projection}
|
|
132
|
+
columnFieldPath={columnFieldPath}
|
|
133
|
+
pivotFieldId={pivotFieldId}
|
|
134
|
+
getPivotAttributes={getPivotAttributes}
|
|
135
|
+
itemTile={itemTile}
|
|
136
|
+
change={change}
|
|
137
|
+
onCardAdd={onCardAdd}
|
|
138
|
+
onCardRemove={onCardRemove}
|
|
139
|
+
>
|
|
140
|
+
<Board.Root model={model}>{children}</Board.Root>
|
|
141
|
+
</KanbanBoardContext>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
KanbanBoardRoot.displayName = KANBAN_BOARD_ROOT;
|
|
146
|
+
|
|
147
|
+
//
|
|
148
|
+
// KanbanBoardContent
|
|
149
|
+
//
|
|
150
|
+
|
|
151
|
+
const KANBAN_BOARD_CONTENT = 'KanbanBoard.Content';
|
|
152
|
+
|
|
153
|
+
type KanbanBoardContentProps = {};
|
|
154
|
+
|
|
155
|
+
export const KanbanBoardContent = composable<HTMLDivElement, KanbanBoardContentProps>((props, forwardedRef) => {
|
|
156
|
+
const { model } = useBoard(KANBAN_BOARD_CONTENT);
|
|
157
|
+
const { kanbanId, projection, pivotFieldId, change } = useKanbanBoard(KANBAN_BOARD_CONTENT);
|
|
158
|
+
|
|
159
|
+
const columnEventHandler = useKanbanColumnEventHandler({
|
|
160
|
+
id: `${kanbanId}-columns`,
|
|
161
|
+
model,
|
|
162
|
+
projection: projection ?? undefined,
|
|
163
|
+
pivotFieldId: pivotFieldId ?? undefined,
|
|
164
|
+
change,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Board.Content
|
|
169
|
+
{...composableProps(props)}
|
|
170
|
+
ref={forwardedRef}
|
|
171
|
+
id={kanbanId}
|
|
172
|
+
eventHandler={columnEventHandler}
|
|
173
|
+
Tile={KanbanColumn}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
KanbanBoardContent.displayName = KANBAN_BOARD_CONTENT;
|
|
179
|
+
|
|
180
|
+
//
|
|
181
|
+
// KanbanBoard
|
|
182
|
+
//
|
|
183
|
+
|
|
184
|
+
export const KanbanBoard = {
|
|
185
|
+
Root: KanbanBoardRoot,
|
|
186
|
+
Content: KanbanBoardContent,
|
|
187
|
+
Column: KanbanColumn,
|
|
188
|
+
Card: KanbanCard,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export { useKanbanBoard };
|
|
192
|
+
|
|
193
|
+
export type { KanbanBoardRootProps as KanbanBoardProps, KanbanCardProps, KanbanColumnProps };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { forwardRef, useCallback, useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
8
|
+
import { useObjectMenuItems } from '@dxos/app-toolkit/ui';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
import { Card, Toolbar, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { Menu, createMenuAction } from '@dxos/react-ui-menu';
|
|
12
|
+
import { Focus, Mosaic, type MosaicTileProps, useBoard } from '@dxos/react-ui-mosaic';
|
|
13
|
+
|
|
14
|
+
import { meta } from '#meta';
|
|
15
|
+
|
|
16
|
+
import { useKanbanBoard } from './KanbanBoard';
|
|
17
|
+
|
|
18
|
+
const KANBAN_CARD_TILE_NAME = 'KanbanBoard.Card';
|
|
19
|
+
|
|
20
|
+
export type KanbanCardProps = Pick<MosaicTileProps<Obj.Unknown>, 'location' | 'data' | 'debug'>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mosaic Tile for Kanban card.
|
|
24
|
+
* Uses Surface for content; requires plugin manager context.
|
|
25
|
+
*/
|
|
26
|
+
export const KanbanCard = forwardRef<HTMLDivElement, KanbanCardProps>(({ data, location, debug }, forwardedRef) => {
|
|
27
|
+
const { t } = useTranslation(meta.id);
|
|
28
|
+
const { model } = useBoard(KANBAN_CARD_TILE_NAME);
|
|
29
|
+
const { projection, onCardRemove } = useKanbanBoard(KANBAN_CARD_TILE_NAME);
|
|
30
|
+
const [dragHandle, setDragHandle] = useState<HTMLButtonElement | null>(null);
|
|
31
|
+
const dragHandleRef = useCallback((el: HTMLButtonElement | null) => setDragHandle(el), []);
|
|
32
|
+
|
|
33
|
+
const objectMenuItems = useObjectMenuItems(data);
|
|
34
|
+
|
|
35
|
+
const menuItems = useMemo(
|
|
36
|
+
() => [
|
|
37
|
+
...objectMenuItems,
|
|
38
|
+
...(onCardRemove
|
|
39
|
+
? [
|
|
40
|
+
createMenuAction('remove', () => onCardRemove(data), {
|
|
41
|
+
label: t('remove-card.label'),
|
|
42
|
+
icon: 'ph--trash--regular',
|
|
43
|
+
}),
|
|
44
|
+
]
|
|
45
|
+
: []),
|
|
46
|
+
],
|
|
47
|
+
[objectMenuItems, onCardRemove, data, t],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Menu.Root>
|
|
52
|
+
<Mosaic.Tile
|
|
53
|
+
asChild
|
|
54
|
+
id={model.getItemId(data)}
|
|
55
|
+
data={data}
|
|
56
|
+
location={location}
|
|
57
|
+
debug={debug}
|
|
58
|
+
dragHandle={dragHandle}
|
|
59
|
+
>
|
|
60
|
+
<Focus.Item asChild>
|
|
61
|
+
<Card.Root ref={forwardedRef} data-testid='board-item'>
|
|
62
|
+
<Card.Toolbar>
|
|
63
|
+
<Card.DragHandle ref={dragHandleRef} testId='mosaicBoard.cardDragHandle' />
|
|
64
|
+
<Card.Title data-testid='mosaicBoard.cardTitle'>{Obj.getLabel(data)}</Card.Title>
|
|
65
|
+
{/* TODO(wittjosiah): Reconcile with Card.Menu. */}
|
|
66
|
+
<Menu.Trigger asChild disabled={!menuItems?.length}>
|
|
67
|
+
<Toolbar.IconButton
|
|
68
|
+
iconOnly
|
|
69
|
+
variant='ghost'
|
|
70
|
+
icon='ph--dots-three-vertical--regular'
|
|
71
|
+
label={t('action-menu.label')}
|
|
72
|
+
/>
|
|
73
|
+
</Menu.Trigger>
|
|
74
|
+
<Menu.Content items={menuItems} />
|
|
75
|
+
</Card.Toolbar>
|
|
76
|
+
<Card.Content>
|
|
77
|
+
{projection && <Surface.Surface role='card--content' limit={1} data={{ subject: data, projection }} />}
|
|
78
|
+
</Card.Content>
|
|
79
|
+
</Card.Root>
|
|
80
|
+
</Focus.Item>
|
|
81
|
+
</Mosaic.Tile>
|
|
82
|
+
</Menu.Root>
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
KanbanCard.displayName = KANBAN_CARD_TILE_NAME;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { FC, forwardRef, RefObject, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import type { Obj } from '@dxos/echo';
|
|
8
|
+
import { Board, type MosaicTileProps, useBoard } from '@dxos/react-ui-mosaic';
|
|
9
|
+
|
|
10
|
+
import { useKanbanItemEventHandler } from '#hooks';
|
|
11
|
+
import { type ColumnStructure, UNCATEGORIZED_VALUE } from '#types';
|
|
12
|
+
|
|
13
|
+
import { useKanbanBoard } from './KanbanBoard';
|
|
14
|
+
|
|
15
|
+
const KANBAN_COLUMN_NAME = 'KanbanBoard.Column';
|
|
16
|
+
|
|
17
|
+
export type KanbanColumnProps = Pick<MosaicTileProps<ColumnStructure>, 'location' | 'data' | 'debug'>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Mosaic Tile for Kanban column.
|
|
21
|
+
*/
|
|
22
|
+
export const KanbanColumn = forwardRef<HTMLDivElement, KanbanColumnProps>(
|
|
23
|
+
({ data: column, location, debug }, forwardedRef) => {
|
|
24
|
+
const { model } = useBoard<ColumnStructure, Obj.Unknown>(KANBAN_COLUMN_NAME);
|
|
25
|
+
const { columnFieldPath, change, onCardAdd, getPivotAttributes, itemTile } = useKanbanBoard(KANBAN_COLUMN_NAME);
|
|
26
|
+
|
|
27
|
+
const { title } = getPivotAttributes(column.columnValue);
|
|
28
|
+
const uncategorized = column.columnValue === UNCATEGORIZED_VALUE;
|
|
29
|
+
const dragHandleRef = useRef<HTMLButtonElement | null>(null);
|
|
30
|
+
|
|
31
|
+
const eventHandler = useKanbanItemEventHandler({
|
|
32
|
+
column,
|
|
33
|
+
columnFieldPath,
|
|
34
|
+
model,
|
|
35
|
+
change,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Board.Column.Root
|
|
40
|
+
data={column}
|
|
41
|
+
location={location}
|
|
42
|
+
classNames='grid grid-rows-[var(--dx-rail-action)_1fr_var(--dx-rail-action)]'
|
|
43
|
+
debug={debug}
|
|
44
|
+
dragHandleRef={dragHandleRef}
|
|
45
|
+
ref={forwardedRef}
|
|
46
|
+
>
|
|
47
|
+
{uncategorized ? (
|
|
48
|
+
<div className='border-b border-separator p-2' data-testid='board-column-header'>
|
|
49
|
+
<span className='font-medium'>{title}</span>
|
|
50
|
+
</div>
|
|
51
|
+
) : (
|
|
52
|
+
<Board.Column.Header label={title} dragHandleRef={dragHandleRef as RefObject<HTMLButtonElement>} />
|
|
53
|
+
)}
|
|
54
|
+
<Board.Column.Body
|
|
55
|
+
data={column}
|
|
56
|
+
eventHandler={eventHandler}
|
|
57
|
+
Tile={itemTile as FC<MosaicTileProps<Obj.Unknown>>}
|
|
58
|
+
/>
|
|
59
|
+
{onCardAdd && (
|
|
60
|
+
<Board.Column.Footer
|
|
61
|
+
onAdd={() => onCardAdd(column.columnValue === UNCATEGORIZED_VALUE ? undefined : column.columnValue)}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
</Board.Column.Root>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
KanbanColumn.displayName = KANBAN_COLUMN_NAME;
|
package/src/components/index.ts
CHANGED
|
@@ -5,43 +5,42 @@
|
|
|
5
5
|
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
6
|
import { type Decorator, type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
7
|
import * as Effect from 'effect/Effect';
|
|
8
|
-
import React, { useCallback, useContext } from 'react';
|
|
8
|
+
import React, { useCallback, useContext, useMemo } from 'react';
|
|
9
9
|
import { expect, waitFor, within } from 'storybook/test';
|
|
10
10
|
|
|
11
11
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
12
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
12
13
|
import { Obj, type QueryAST, Type } from '@dxos/echo';
|
|
14
|
+
import { View } from '@dxos/echo';
|
|
13
15
|
import { type Mutable } from '@dxos/echo/internal';
|
|
14
16
|
import { invariant } from '@dxos/invariant';
|
|
15
17
|
import { ClientPlugin } from '@dxos/plugin-client';
|
|
18
|
+
import { initializeIdentity } from '@dxos/plugin-client/testing';
|
|
16
19
|
import { PreviewPlugin } from '@dxos/plugin-preview';
|
|
17
|
-
import { useGlobalFilteredObjects } from '@dxos/plugin-search';
|
|
18
20
|
import { SpacePlugin } from '@dxos/plugin-space';
|
|
19
21
|
import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
|
|
20
22
|
import { faker } from '@dxos/random';
|
|
21
23
|
import { Filter, type Space, useQuery, useSchema, useSpaces } from '@dxos/react-client/echo';
|
|
22
|
-
import {
|
|
24
|
+
import { withLayout } from '@dxos/react-ui/testing';
|
|
23
25
|
import { ViewEditor } from '@dxos/react-ui-form';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
useKanbanModel,
|
|
28
|
-
useProjectionModel,
|
|
29
|
-
} from '@dxos/react-ui-kanban';
|
|
30
|
-
import { Kanban } from '@dxos/react-ui-kanban/types';
|
|
31
|
-
import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
|
|
32
|
-
import { View, getTypenameFromQuery } from '@dxos/schema';
|
|
26
|
+
import { Json } from '@dxos/react-ui-syntax-highlighter';
|
|
27
|
+
import { ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
28
|
+
// TODO(wittjosiah): Replace with echo/testing.
|
|
33
29
|
import { Organization, Person } from '@dxos/types';
|
|
34
30
|
|
|
35
|
-
import {
|
|
31
|
+
import { useProjectionModel } from '#hooks';
|
|
32
|
+
import { KanbanPlugin } from '../../KanbanPlugin';
|
|
33
|
+
import { translations } from '../../translations';
|
|
34
|
+
import { Kanban } from '#types';
|
|
36
35
|
|
|
37
36
|
faker.seed(0);
|
|
38
37
|
|
|
39
|
-
const createOrg = () => ({
|
|
38
|
+
const createOrg = (status?: Organization.Organization['status']) => ({
|
|
40
39
|
name: faker.commerce.productName(),
|
|
41
40
|
description: faker.lorem.paragraph(),
|
|
42
41
|
image: faker.image.url(),
|
|
43
42
|
website: faker.internet.url(),
|
|
44
|
-
status: faker.helpers.arrayElement(Organization.StatusOptions).id as Organization.Organization['status'],
|
|
43
|
+
status: (status ?? faker.helpers.arrayElement(Organization.StatusOptions).id) as Organization.Organization['status'],
|
|
45
44
|
});
|
|
46
45
|
|
|
47
46
|
//
|
|
@@ -49,12 +48,13 @@ const createOrg = () => ({
|
|
|
49
48
|
//
|
|
50
49
|
|
|
51
50
|
type ClientSetupOptions = {
|
|
52
|
-
types?: Type.
|
|
51
|
+
types?: Type.AnyEntity[];
|
|
53
52
|
onSpaceCreated?: (space: Space) => Promise<void>;
|
|
54
53
|
};
|
|
55
54
|
|
|
56
55
|
/**
|
|
57
56
|
* Creates the standard plugin manager decorator with client configuration.
|
|
57
|
+
* Includes KanbanPlugin so the Surface resolves to KanbanContainer.
|
|
58
58
|
*/
|
|
59
59
|
const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions): Decorator =>
|
|
60
60
|
withPluginManager({
|
|
@@ -64,7 +64,7 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
|
|
|
64
64
|
types: [...types, View.View, Kanban.Kanban],
|
|
65
65
|
onClientInitialized: ({ client }) =>
|
|
66
66
|
Effect.gen(function* () {
|
|
67
|
-
yield*
|
|
67
|
+
yield* initializeIdentity(client);
|
|
68
68
|
const space = yield* Effect.promise(() => client.spaces.create());
|
|
69
69
|
yield* Effect.promise(() => space.waitUntilReady());
|
|
70
70
|
yield* Effect.promise(() => onSpaceCreated?.(space) ?? Promise.resolve());
|
|
@@ -73,61 +73,37 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
|
|
|
73
73
|
PreviewPlugin(),
|
|
74
74
|
SpacePlugin({}),
|
|
75
75
|
StorybookPlugin({}),
|
|
76
|
+
KanbanPlugin(),
|
|
76
77
|
],
|
|
77
78
|
});
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Renders the first Kanban in the space via Surface (resolves to KanbanContainer),
|
|
82
|
+
* with a sidebar containing ViewEditor and Json filter.
|
|
83
|
+
*/
|
|
83
84
|
const DefaultComponent = () => {
|
|
84
85
|
const registry = useContext(RegistryContext);
|
|
85
86
|
const spaces = useSpaces();
|
|
86
87
|
const space = spaces[spaces.length - 1];
|
|
87
|
-
const [
|
|
88
|
-
const typename =
|
|
88
|
+
const [kanban] = useQuery(space?.db, Filter.type(Kanban.Kanban));
|
|
89
|
+
const typename = kanban?.view.target?.query ? getTypenameFromQuery(kanban.view.target.query.ast) : undefined;
|
|
89
90
|
const schema = useSchema(space?.db, typename);
|
|
91
|
+
const projection = useProjectionModel(schema, kanban, registry);
|
|
90
92
|
|
|
91
|
-
const
|
|
92
|
-
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
93
|
-
|
|
94
|
-
const projection = useProjectionModel(schema, object, registry);
|
|
95
|
-
const model = useKanbanModel({
|
|
96
|
-
object,
|
|
97
|
-
projection,
|
|
98
|
-
items: filteredObjects,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const handleAddCard = useCallback(
|
|
102
|
-
(columnValue: string | undefined) => {
|
|
103
|
-
const path = model?.columnFieldPath;
|
|
104
|
-
if (space && schema && Type.isObjectSchema(schema) && path) {
|
|
105
|
-
const card = Obj.make(schema, {
|
|
106
|
-
...createOrg(),
|
|
107
|
-
[path]: columnValue,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
space.db.add(card);
|
|
111
|
-
return card.id;
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
[space, schema, model],
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const handleRemoveCard = useCallback((card: { id: string }) => Obj.isObject(card) && space?.db.remove(card), [space]);
|
|
93
|
+
const data = useMemo(() => (kanban ? { subject: kanban } : {}), [kanban]);
|
|
118
94
|
|
|
119
95
|
const handleUpdateQuery = useCallback(
|
|
120
96
|
(newQuery: QueryAST.Query) => {
|
|
121
97
|
invariant(schema);
|
|
122
|
-
invariant(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Obj.change(
|
|
127
|
-
|
|
98
|
+
invariant(kanban?.view.target);
|
|
99
|
+
if (Type.isMutable(schema)) {
|
|
100
|
+
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
101
|
+
}
|
|
102
|
+
Obj.change(kanban.view.target, (view) => {
|
|
103
|
+
view.query.ast = newQuery as Mutable<QueryAST.Query>;
|
|
128
104
|
});
|
|
129
105
|
},
|
|
130
|
-
[
|
|
106
|
+
[kanban, schema],
|
|
131
107
|
);
|
|
132
108
|
|
|
133
109
|
const handleDeleteField = useCallback(
|
|
@@ -139,23 +115,27 @@ const DefaultComponent = () => {
|
|
|
139
115
|
[schema, projection],
|
|
140
116
|
);
|
|
141
117
|
|
|
142
|
-
if (!schema || !
|
|
118
|
+
if (!schema || !kanban?.view.target) {
|
|
143
119
|
return null;
|
|
144
120
|
}
|
|
145
121
|
|
|
146
122
|
return (
|
|
147
|
-
<div className='grow grid grid-cols-[1fr_350px] overflow-hidden'>
|
|
148
|
-
|
|
149
|
-
<div className='flex flex-col
|
|
123
|
+
<div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
|
|
124
|
+
<Surface.Surface role='article' data={data} limit={1} />
|
|
125
|
+
<div className='flex flex-col h-full overflow-hidden border-l border-separator'>
|
|
150
126
|
<ViewEditor
|
|
151
|
-
classNames='p-2'
|
|
152
127
|
registry={space?.db.schemaRegistry}
|
|
153
128
|
schema={schema}
|
|
154
|
-
view={
|
|
129
|
+
view={kanban.view.target}
|
|
155
130
|
onQueryChanged={handleUpdateQuery}
|
|
156
|
-
onDelete={Type.isMutable(schema) ? handleDeleteField : undefined}
|
|
131
|
+
onDelete={schema && Type.isMutable(schema) ? handleDeleteField : undefined}
|
|
157
132
|
/>
|
|
158
|
-
<
|
|
133
|
+
<Json.Root data={{ view: kanban.view.target, schema }}>
|
|
134
|
+
<Json.Content>
|
|
135
|
+
<Json.Filter />
|
|
136
|
+
<Json.Data classNames='text-xs' />
|
|
137
|
+
</Json.Content>
|
|
138
|
+
</Json.Root>
|
|
159
139
|
</div>
|
|
160
140
|
</div>
|
|
161
141
|
);
|
|
@@ -166,13 +146,13 @@ const DefaultComponent = () => {
|
|
|
166
146
|
//
|
|
167
147
|
|
|
168
148
|
const meta = {
|
|
169
|
-
title: 'plugins/plugin-kanban/Kanban',
|
|
149
|
+
title: 'plugins/plugin-kanban/containers/Kanban',
|
|
170
150
|
component: DefaultComponent,
|
|
171
151
|
render: () => <DefaultComponent />,
|
|
172
|
-
decorators: [
|
|
152
|
+
decorators: [withLayout({ layout: 'fullscreen' })],
|
|
173
153
|
parameters: {
|
|
174
154
|
layout: 'fullscreen',
|
|
175
|
-
translations
|
|
155
|
+
translations,
|
|
176
156
|
},
|
|
177
157
|
} satisfies Meta<typeof DefaultComponent>;
|
|
178
158
|
|
|
@@ -189,7 +169,7 @@ export const Default: Story = {
|
|
|
189
169
|
withKanbanPlugins({
|
|
190
170
|
types: [Organization.Organization, Person.Person],
|
|
191
171
|
onSpaceCreated: async (space) => {
|
|
192
|
-
const { view } = await
|
|
172
|
+
const { view } = await ViewModel.makeFromDatabase({
|
|
193
173
|
db: space.db,
|
|
194
174
|
typename: Organization.Organization.typename,
|
|
195
175
|
pivotFieldName: 'status',
|
|
@@ -197,8 +177,7 @@ export const Default: Story = {
|
|
|
197
177
|
const kanban = Kanban.make({ view });
|
|
198
178
|
space.db.add(kanban);
|
|
199
179
|
|
|
200
|
-
|
|
201
|
-
Array.from({ length: 80 }).map(() => {
|
|
180
|
+
Array.from({ length: 10 }).map(() => {
|
|
202
181
|
return space.db.add(Obj.make(Organization.Organization, createOrg()));
|
|
203
182
|
});
|
|
204
183
|
},
|
|
@@ -218,16 +197,15 @@ export const Default: Story = {
|
|
|
218
197
|
await expect(prospectTag).toBeTruthy();
|
|
219
198
|
await expect(commitTag).toBeTruthy();
|
|
220
199
|
|
|
221
|
-
// Find the column containers.
|
|
222
|
-
const activeColumn = activeTag.closest('[data-
|
|
223
|
-
const prospectColumn = prospectTag.closest('[data-
|
|
200
|
+
// Find the column containers (Board uses data-testid="board-column").
|
|
201
|
+
const activeColumn = activeTag.closest('[data-testid="board-column"]') as HTMLElement;
|
|
202
|
+
const prospectColumn = prospectTag.closest('[data-testid="board-column"]') as HTMLElement;
|
|
224
203
|
await expect(activeColumn).toBeTruthy();
|
|
225
204
|
await expect(prospectColumn).toBeTruthy();
|
|
226
205
|
|
|
227
|
-
// Wait for cards to render in the columns.
|
|
228
|
-
// Cards have data-dx-item-id attribute from StackItem.Root.
|
|
206
|
+
// Wait for cards to render in the columns (Board items use data-testid="board-item").
|
|
229
207
|
const getColumnCards = (column: HTMLElement) =>
|
|
230
|
-
Array.from(column.querySelectorAll('[data-
|
|
208
|
+
Array.from(column.querySelectorAll('[data-testid="board-item"]')) as HTMLElement[];
|
|
231
209
|
|
|
232
210
|
await waitFor(() => expect(getColumnCards(activeColumn).length).toBeGreaterThan(0));
|
|
233
211
|
|
|
@@ -237,16 +215,16 @@ export const Default: Story = {
|
|
|
237
215
|
await expect(activeCards.length).toBeGreaterThan(0);
|
|
238
216
|
await expect(prospectCards.length).toBeGreaterThan(0);
|
|
239
217
|
|
|
240
|
-
// Verify cards have drag handles (
|
|
218
|
+
// Verify cards have drag handles (Card.Toolbar includes drag handle).
|
|
241
219
|
const firstActiveCard = activeCards[0];
|
|
242
220
|
const buttons = firstActiveCard.querySelectorAll('button');
|
|
243
221
|
await expect(buttons.length).toBeGreaterThan(0);
|
|
244
222
|
|
|
245
|
-
// Verify
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
await expect(
|
|
249
|
-
await expect(
|
|
223
|
+
// Verify add-card action exists in columns (optional footer).
|
|
224
|
+
const activeAddItem = activeColumn.querySelector('[data-testid="board-column-add-item"]');
|
|
225
|
+
const prospectAddItem = prospectColumn.querySelector('[data-testid="board-column-add-item"]');
|
|
226
|
+
await expect(activeAddItem).toBeTruthy();
|
|
227
|
+
await expect(prospectAddItem).toBeTruthy();
|
|
250
228
|
|
|
251
229
|
// TODO(wittjosiah): Get drag & drop tests working.
|
|
252
230
|
// See packages/apps/composer-app/src/playwright/stack.spec.ts for reference.
|
|
@@ -268,7 +246,7 @@ export const MutableSchema: Story = {
|
|
|
268
246
|
// Register schema in the database to make it mutable (EchoSchema).
|
|
269
247
|
const [schema] = await space.db.schemaRegistry.register([Organization.Organization]);
|
|
270
248
|
|
|
271
|
-
const { view } = await
|
|
249
|
+
const { view } = await ViewModel.makeFromDatabase({
|
|
272
250
|
db: space.db,
|
|
273
251
|
typename: schema.typename,
|
|
274
252
|
pivotFieldName: 'status',
|
|
@@ -277,9 +255,14 @@ export const MutableSchema: Story = {
|
|
|
277
255
|
space.db.add(kanban);
|
|
278
256
|
|
|
279
257
|
// Create test data using the registered schema.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
258
|
+
const requiredOrgs = [
|
|
259
|
+
...Array.from({ length: 2 }, () => createOrg('prospect')),
|
|
260
|
+
...Array.from({ length: 5 }, () => createOrg('qualified')),
|
|
261
|
+
...Array.from({ length: 1 }, () => createOrg('active')),
|
|
262
|
+
...Array.from({ length: 1 }, () => createOrg('commit')),
|
|
263
|
+
...Array.from({ length: 1 }, () => createOrg('reject')),
|
|
264
|
+
];
|
|
265
|
+
requiredOrgs.forEach((org) => space.db.add(Obj.make(schema, org)));
|
|
283
266
|
},
|
|
284
267
|
}),
|
|
285
268
|
],
|