@dxos/plugin-kanban 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7
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/KanbanContainer-LUFCGLXA.mjs +303 -0
- package/dist/lib/browser/KanbanContainer-LUFCGLXA.mjs.map +7 -0
- package/dist/lib/browser/KanbanViewEditor-JGJXYP6E.mjs +66 -0
- package/dist/lib/browser/KanbanViewEditor-JGJXYP6E.mjs.map +7 -0
- package/dist/lib/browser/blueprint-definition-2NNRB2ZQ.mjs +17 -0
- package/dist/lib/browser/blueprint-definition-2NNRB2ZQ.mjs.map +7 -0
- package/dist/lib/browser/blueprints/index.mjs +3 -3
- package/dist/lib/browser/{chunk-L6N4ZDZ7.mjs → chunk-B6RIAQMI.mjs} +11 -17
- package/dist/lib/browser/chunk-B6RIAQMI.mjs.map +7 -0
- package/dist/lib/browser/chunk-XNHYJTI4.mjs +385 -0
- package/dist/lib/browser/chunk-XNHYJTI4.mjs.map +7 -0
- package/dist/lib/browser/chunk-YCJAZ27A.mjs +210 -0
- package/dist/lib/browser/chunk-YCJAZ27A.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +31 -27
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{operation-resolver-UEJHX42A.mjs → operation-resolver-ZWHXZBPF.mjs} +7 -7
- package/dist/lib/browser/operation-resolver-ZWHXZBPF.mjs.map +7 -0
- package/dist/lib/browser/react-surface-JSJYXYPF.mjs +99 -0
- package/dist/lib/browser/react-surface-JSJYXYPF.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +9 -3
- package/dist/lib/node-esm/KanbanContainer-LLBEBTVG.mjs +304 -0
- package/dist/lib/node-esm/KanbanContainer-LLBEBTVG.mjs.map +7 -0
- package/dist/lib/node-esm/KanbanViewEditor-Z6PM75TB.mjs +67 -0
- package/dist/lib/node-esm/KanbanViewEditor-Z6PM75TB.mjs.map +7 -0
- package/dist/lib/node-esm/{blueprint-definition-APJQFSHJ.mjs → blueprint-definition-5GWECQLZ.mjs} +6 -6
- package/dist/lib/node-esm/blueprint-definition-5GWECQLZ.mjs.map +7 -0
- package/dist/lib/node-esm/blueprints/index.mjs +3 -3
- package/dist/lib/node-esm/chunk-3APLKFTO.mjs +211 -0
- package/dist/lib/node-esm/chunk-3APLKFTO.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MS6KRXNX.mjs +386 -0
- package/dist/lib/node-esm/chunk-MS6KRXNX.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ZHRMUKTF.mjs → chunk-SD555I65.mjs} +11 -16
- package/dist/lib/node-esm/chunk-SD555I65.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +31 -27
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{operation-resolver-5RPWHZCF.mjs → operation-resolver-KV5GNNHN.mjs} +7 -7
- package/dist/lib/node-esm/operation-resolver-KV5GNNHN.mjs.map +7 -0
- package/dist/lib/node-esm/react-surface-PQEYNBJY.mjs +100 -0
- package/dist/lib/node-esm/react-surface-PQEYNBJY.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +9 -3
- 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 +1 -1
- package/dist/types/src/capabilities/artifact-definition/artifact-definition.d.ts.map +1 -1
- package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts +3 -6
- package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts.map +1 -1
- package/dist/types/src/capabilities/blueprint-definition/index.d.ts +1 -1
- package/dist/types/src/capabilities/blueprint-definition/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +1 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +2 -2
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +59 -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/{components → containers/KanbanContainer}/KanbanContainer.stories.d.ts +18 -14
- 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/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 +5 -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 +16 -0
- 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 +5 -5
- 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 +47 -42
- package/src/KanbanPlugin.tsx +11 -12
- package/src/blueprints/index.ts +1 -1
- package/src/blueprints/kanban-blueprint.ts +14 -7
- package/src/capabilities/artifact-definition/artifact-definition.ts +8 -7
- package/src/capabilities/blueprint-definition/blueprint-definition.ts +6 -12
- package/src/capabilities/operation-resolver/operation-resolver.ts +5 -5
- package/src/capabilities/react-surface/react-surface.tsx +14 -14
- package/src/components/KanbanBoard/KanbanBoard.stories.tsx +142 -0
- package/src/components/KanbanBoard/KanbanBoard.tsx +183 -0
- package/src/components/KanbanBoard/KanbanCard.tsx +87 -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 +62 -85
- package/src/containers/KanbanContainer/KanbanContainer.tsx +87 -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/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 +76 -0
- package/src/testing/index.ts +5 -0
- package/src/translations.ts +9 -1
- package/src/types/Kanban.ts +67 -0
- package/src/types/constants.ts +9 -0
- package/src/types/index.ts +2 -0
- package/src/types/schema.ts +8 -9
- 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.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.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.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.map +0 -7
- 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/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.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/components/KanbanContainer.tsx +0 -86
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
|
|
21
|
+
import { useKanbanBoardModel, useKanbanColumnEventHandler } from '../../hooks';
|
|
22
|
+
import { meta } from '../../meta';
|
|
23
|
+
import { type Kanban, type KanbanChangeCallback, UNCATEGORIZED_ATTRIBUTES, UNCATEGORIZED_VALUE } from '../../types';
|
|
24
|
+
|
|
25
|
+
import { KanbanCard, type KanbanCardProps } from './KanbanCard';
|
|
26
|
+
import { KanbanColumn, type KanbanColumnProps } from './KanbanColumn';
|
|
27
|
+
|
|
28
|
+
// TODO(burdon): Rename Kanban.
|
|
29
|
+
|
|
30
|
+
//
|
|
31
|
+
// Context
|
|
32
|
+
//
|
|
33
|
+
|
|
34
|
+
const KANBAN_BOARD_NAME = 'KanbanBoard.Context';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Context value for the Kanban board.
|
|
38
|
+
* Items are Echo objects (Obj.Unknown).
|
|
39
|
+
*/
|
|
40
|
+
type KanbanBoardContextValue = {
|
|
41
|
+
kanbanId: string;
|
|
42
|
+
projection: ProjectionModel | undefined;
|
|
43
|
+
columnFieldPath: string | undefined;
|
|
44
|
+
change: KanbanChangeCallback<Obj.Unknown>;
|
|
45
|
+
pivotFieldId: string | undefined;
|
|
46
|
+
getPivotAttributes: (columnValue: string) => { title: string; color: string };
|
|
47
|
+
itemTile?: ComponentType<KanbanCardProps>; // TODO(burdon): Prop.
|
|
48
|
+
onCardAdd?: (columnValue: string | undefined) => string | undefined;
|
|
49
|
+
onCardRemove?: (card: Obj.Unknown) => void;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const [KanbanBoardContext, useKanbanBoard] = createContext<KanbanBoardContextValue>(KANBAN_BOARD_NAME, {
|
|
53
|
+
kanbanId: 'never',
|
|
54
|
+
projection: undefined,
|
|
55
|
+
columnFieldPath: undefined,
|
|
56
|
+
change: { kanban: () => {}, setItemField: () => {} },
|
|
57
|
+
pivotFieldId: undefined,
|
|
58
|
+
getPivotAttributes: (id: string) =>
|
|
59
|
+
id === UNCATEGORIZED_VALUE ? UNCATEGORIZED_ATTRIBUTES : { title: id, color: 'neutral' },
|
|
60
|
+
itemTile: (() => null) as ComponentType<KanbanCardProps>,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
//
|
|
64
|
+
// Root
|
|
65
|
+
//
|
|
66
|
+
|
|
67
|
+
const KANBAN_BOARD_ROOT = 'KanbanBoard.Root';
|
|
68
|
+
|
|
69
|
+
type KanbanBoardRootProps = PropsWithChildren<
|
|
70
|
+
Pick<KanbanBoardContextValue, 'change' | 'itemTile'> & {
|
|
71
|
+
kanban: Kanban.Kanban;
|
|
72
|
+
/** Required when providing context; Root derives columnFieldPath, pivotFieldId, getPivotAttributes from kanban + projection. */
|
|
73
|
+
projection: ProjectionModel;
|
|
74
|
+
/** Atom of items (e.g. from AtomQuery for DB, or Atom.make([]) for in-memory). */
|
|
75
|
+
items: Atom.Atom<Obj.Unknown[]>;
|
|
76
|
+
onCardAdd?: (columnValue: string | undefined) => string | undefined;
|
|
77
|
+
onCardRemove?: (card: Obj.Unknown) => void;
|
|
78
|
+
} & ComponentPropsWithoutRef<'div'>
|
|
79
|
+
>;
|
|
80
|
+
|
|
81
|
+
export const KanbanBoardRoot = ({
|
|
82
|
+
children,
|
|
83
|
+
change,
|
|
84
|
+
itemTile = KanbanCard,
|
|
85
|
+
kanban,
|
|
86
|
+
projection,
|
|
87
|
+
items,
|
|
88
|
+
onCardAdd,
|
|
89
|
+
onCardRemove,
|
|
90
|
+
}: KanbanBoardRootProps) => {
|
|
91
|
+
const registry = useContext(RegistryContext);
|
|
92
|
+
const { t } = useTranslation(meta.id);
|
|
93
|
+
const model = useKanbanBoardModel(kanban, projection, items, registry);
|
|
94
|
+
const columns = model?.getColumns?.() ?? [];
|
|
95
|
+
const view = kanban?.view?.target;
|
|
96
|
+
const pivotFieldId = view?.projection?.pivotFieldId;
|
|
97
|
+
const columnFieldPath = useMemo(() => {
|
|
98
|
+
if (pivotFieldId === undefined || !projection) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return projection.tryGetFieldProjection(pivotFieldId)?.props.property;
|
|
103
|
+
}, [projection, pivotFieldId]);
|
|
104
|
+
|
|
105
|
+
const getPivotAttributes = useCallback<KanbanBoardContextValue['getPivotAttributes']>(
|
|
106
|
+
(columnValue) => {
|
|
107
|
+
if (columnValue === UNCATEGORIZED_VALUE) {
|
|
108
|
+
return UNCATEGORIZED_ATTRIBUTES;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const options = projection?.tryGetFieldProjection(pivotFieldId ?? '')?.props.options ?? [];
|
|
112
|
+
const option = options.find((option) => option.id === columnValue);
|
|
113
|
+
return option ?? ({ title: columnValue, color: 'neutral' } as const);
|
|
114
|
+
},
|
|
115
|
+
[projection, pivotFieldId],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (columns.length === 0) {
|
|
119
|
+
return (
|
|
120
|
+
<div className='flex flex-1 items-center justify-center p-8 text-center text-description'>
|
|
121
|
+
{t('select pivot placeholder')}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<KanbanBoardContext
|
|
128
|
+
kanbanId={Obj.getDXN(kanban).toString()}
|
|
129
|
+
projection={projection}
|
|
130
|
+
columnFieldPath={columnFieldPath}
|
|
131
|
+
pivotFieldId={pivotFieldId}
|
|
132
|
+
getPivotAttributes={getPivotAttributes}
|
|
133
|
+
itemTile={itemTile}
|
|
134
|
+
change={change}
|
|
135
|
+
onCardAdd={onCardAdd}
|
|
136
|
+
onCardRemove={onCardRemove}
|
|
137
|
+
>
|
|
138
|
+
<Board.Root model={model}>{children}</Board.Root>
|
|
139
|
+
</KanbanBoardContext>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
KanbanBoardRoot.displayName = KANBAN_BOARD_ROOT;
|
|
144
|
+
|
|
145
|
+
//
|
|
146
|
+
// KanbanBoardContent
|
|
147
|
+
//
|
|
148
|
+
|
|
149
|
+
const KANBAN_BOARD_CONTENT = 'KanbanBoard.Content';
|
|
150
|
+
|
|
151
|
+
type KanbanBoardContentProps = {};
|
|
152
|
+
|
|
153
|
+
export const KanbanBoardContent = (props: KanbanBoardContentProps) => {
|
|
154
|
+
const { model } = useBoard(KANBAN_BOARD_CONTENT);
|
|
155
|
+
const { kanbanId, projection, pivotFieldId, change } = useKanbanBoard(KANBAN_BOARD_CONTENT);
|
|
156
|
+
|
|
157
|
+
const columnEventHandler = useKanbanColumnEventHandler({
|
|
158
|
+
id: `${kanbanId}-columns`,
|
|
159
|
+
model,
|
|
160
|
+
projection: projection ?? undefined,
|
|
161
|
+
pivotFieldId: pivotFieldId ?? undefined,
|
|
162
|
+
change,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return <Board.Content id={kanbanId} eventHandler={columnEventHandler} Tile={KanbanColumn} />;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
KanbanBoardContent.displayName = KANBAN_BOARD_CONTENT;
|
|
169
|
+
|
|
170
|
+
//
|
|
171
|
+
// KanbanBoard
|
|
172
|
+
//
|
|
173
|
+
|
|
174
|
+
export const KanbanBoard = {
|
|
175
|
+
Root: KanbanBoardRoot,
|
|
176
|
+
Content: KanbanBoardContent,
|
|
177
|
+
Column: KanbanColumn,
|
|
178
|
+
Card: KanbanCard,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export { useKanbanBoard };
|
|
182
|
+
|
|
183
|
+
export type { KanbanBoardRootProps as KanbanBoardProps, KanbanCardProps, KanbanColumnProps };
|
|
@@ -0,0 +1,87 @@
|
|
|
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, useObjectNavigate } 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
|
+
const handleNavigate = useObjectNavigate(data);
|
|
35
|
+
|
|
36
|
+
const menuItems = useMemo(
|
|
37
|
+
() => [
|
|
38
|
+
...objectMenuItems,
|
|
39
|
+
...(onCardRemove
|
|
40
|
+
? [
|
|
41
|
+
createMenuAction('remove', () => onCardRemove(data), {
|
|
42
|
+
label: t('remove card label'),
|
|
43
|
+
icon: 'ph--trash--regular',
|
|
44
|
+
}),
|
|
45
|
+
]
|
|
46
|
+
: []),
|
|
47
|
+
],
|
|
48
|
+
[objectMenuItems, onCardRemove, data, t],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
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.Group asChild>
|
|
61
|
+
<Menu.Root>
|
|
62
|
+
<Card.Root ref={forwardedRef} data-testid='board-item'>
|
|
63
|
+
<Card.Toolbar>
|
|
64
|
+
<Card.DragHandle ref={dragHandleRef} />
|
|
65
|
+
<Card.Title onClick={handleNavigate}>{Obj.getLabel(data)}</Card.Title>
|
|
66
|
+
{/* TODO(wittjosiah): Reconcile with Card.Menu. */}
|
|
67
|
+
<Menu.Trigger asChild disabled={!menuItems?.length}>
|
|
68
|
+
<Toolbar.IconButton
|
|
69
|
+
iconOnly
|
|
70
|
+
variant='ghost'
|
|
71
|
+
icon='ph--dots-three-vertical--regular'
|
|
72
|
+
label={t('action menu label')}
|
|
73
|
+
/>
|
|
74
|
+
</Menu.Trigger>
|
|
75
|
+
<Menu.Content items={menuItems} />
|
|
76
|
+
</Card.Toolbar>
|
|
77
|
+
<Card.Content>
|
|
78
|
+
{projection && <Surface.Surface role='card--content' limit={1} data={{ subject: data, projection }} />}
|
|
79
|
+
</Card.Content>
|
|
80
|
+
</Card.Root>
|
|
81
|
+
</Menu.Root>
|
|
82
|
+
</Focus.Group>
|
|
83
|
+
</Mosaic.Tile>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
KanbanCard.displayName = KANBAN_CARD_TILE_NAME;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { forwardRef, 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 React.RefObject<HTMLButtonElement>} />
|
|
53
|
+
)}
|
|
54
|
+
<Board.Column.Body
|
|
55
|
+
data={column}
|
|
56
|
+
eventHandler={eventHandler}
|
|
57
|
+
Tile={itemTile as React.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,41 @@
|
|
|
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';
|
|
16
18
|
import { PreviewPlugin } from '@dxos/plugin-preview';
|
|
17
|
-
import { useGlobalFilteredObjects } from '@dxos/plugin-search';
|
|
18
19
|
import { SpacePlugin } from '@dxos/plugin-space';
|
|
19
20
|
import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
|
|
20
21
|
import { faker } from '@dxos/random';
|
|
21
22
|
import { Filter, type Space, useQuery, useSchema, useSpaces } from '@dxos/react-client/echo';
|
|
22
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
23
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
23
24
|
import { ViewEditor } from '@dxos/react-ui-form';
|
|
24
|
-
import {
|
|
25
|
-
Kanban as KanbanComponent,
|
|
26
|
-
translations as kanbanTranslations,
|
|
27
|
-
useKanbanModel,
|
|
28
|
-
useProjectionModel,
|
|
29
|
-
} from '@dxos/react-ui-kanban';
|
|
30
|
-
import { Kanban } from '@dxos/react-ui-kanban/types';
|
|
31
25
|
import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
|
|
32
|
-
import {
|
|
26
|
+
import { ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
27
|
+
// TODO(wittjosiah): Replace with echo/testing.
|
|
33
28
|
import { Organization, Person } from '@dxos/types';
|
|
34
29
|
|
|
35
|
-
import {
|
|
30
|
+
import { useProjectionModel } from '../../hooks';
|
|
31
|
+
import { KanbanPlugin } from '../../KanbanPlugin';
|
|
32
|
+
import { translations } from '../../translations';
|
|
33
|
+
import { Kanban } from '../../types';
|
|
36
34
|
|
|
37
35
|
faker.seed(0);
|
|
38
36
|
|
|
39
|
-
const createOrg = () => ({
|
|
37
|
+
const createOrg = (status?: Organization.Organization['status']) => ({
|
|
40
38
|
name: faker.commerce.productName(),
|
|
41
39
|
description: faker.lorem.paragraph(),
|
|
42
40
|
image: faker.image.url(),
|
|
43
41
|
website: faker.internet.url(),
|
|
44
|
-
status: faker.helpers.arrayElement(Organization.StatusOptions).id as Organization.Organization['status'],
|
|
42
|
+
status: (status ?? faker.helpers.arrayElement(Organization.StatusOptions).id) as Organization.Organization['status'],
|
|
45
43
|
});
|
|
46
44
|
|
|
47
45
|
//
|
|
@@ -49,12 +47,13 @@ const createOrg = () => ({
|
|
|
49
47
|
//
|
|
50
48
|
|
|
51
49
|
type ClientSetupOptions = {
|
|
52
|
-
types?: Type.
|
|
50
|
+
types?: Type.AnyEntity[];
|
|
53
51
|
onSpaceCreated?: (space: Space) => Promise<void>;
|
|
54
52
|
};
|
|
55
53
|
|
|
56
54
|
/**
|
|
57
55
|
* Creates the standard plugin manager decorator with client configuration.
|
|
56
|
+
* Includes KanbanPlugin so the Surface resolves to KanbanContainer.
|
|
58
57
|
*/
|
|
59
58
|
const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions): Decorator =>
|
|
60
59
|
withPluginManager({
|
|
@@ -73,61 +72,37 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
|
|
|
73
72
|
PreviewPlugin(),
|
|
74
73
|
SpacePlugin({}),
|
|
75
74
|
StorybookPlugin({}),
|
|
75
|
+
KanbanPlugin(),
|
|
76
76
|
],
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Renders the first Kanban in the space via Surface (resolves to KanbanContainer),
|
|
81
|
+
* with a sidebar containing ViewEditor and JsonFilter.
|
|
82
|
+
*/
|
|
83
83
|
const DefaultComponent = () => {
|
|
84
84
|
const registry = useContext(RegistryContext);
|
|
85
85
|
const spaces = useSpaces();
|
|
86
86
|
const space = spaces[spaces.length - 1];
|
|
87
|
-
const [
|
|
88
|
-
const typename =
|
|
87
|
+
const [kanban] = useQuery(space?.db, Filter.type(Kanban.Kanban));
|
|
88
|
+
const typename = kanban?.view.target?.query ? getTypenameFromQuery(kanban.view.target.query.ast) : undefined;
|
|
89
89
|
const schema = useSchema(space?.db, typename);
|
|
90
|
+
const projection = useProjectionModel(schema, kanban, registry);
|
|
90
91
|
|
|
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]);
|
|
92
|
+
const data = useMemo(() => (kanban ? { subject: kanban } : {}), [kanban]);
|
|
118
93
|
|
|
119
94
|
const handleUpdateQuery = useCallback(
|
|
120
95
|
(newQuery: QueryAST.Query) => {
|
|
121
96
|
invariant(schema);
|
|
122
|
-
invariant(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Obj.change(
|
|
127
|
-
|
|
97
|
+
invariant(kanban?.view.target);
|
|
98
|
+
if (Type.isMutable(schema)) {
|
|
99
|
+
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
100
|
+
}
|
|
101
|
+
Obj.change(kanban.view.target, (view) => {
|
|
102
|
+
view.query.ast = newQuery as Mutable<QueryAST.Query>;
|
|
128
103
|
});
|
|
129
104
|
},
|
|
130
|
-
[
|
|
105
|
+
[kanban, schema],
|
|
131
106
|
);
|
|
132
107
|
|
|
133
108
|
const handleDeleteField = useCallback(
|
|
@@ -139,23 +114,22 @@ const DefaultComponent = () => {
|
|
|
139
114
|
[schema, projection],
|
|
140
115
|
);
|
|
141
116
|
|
|
142
|
-
if (!schema || !
|
|
117
|
+
if (!schema || !kanban?.view.target) {
|
|
143
118
|
return null;
|
|
144
119
|
}
|
|
145
120
|
|
|
146
121
|
return (
|
|
147
|
-
<div className='grow grid grid-cols-[1fr_350px] overflow-hidden'>
|
|
148
|
-
|
|
149
|
-
<div className='flex flex-col
|
|
122
|
+
<div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
|
|
123
|
+
<Surface.Surface role='article' data={data} limit={1} />
|
|
124
|
+
<div className='flex flex-col h-full overflow-hidden border-l border-separator'>
|
|
150
125
|
<ViewEditor
|
|
151
|
-
classNames='p-2'
|
|
152
126
|
registry={space?.db.schemaRegistry}
|
|
153
127
|
schema={schema}
|
|
154
|
-
view={
|
|
128
|
+
view={kanban.view.target}
|
|
155
129
|
onQueryChanged={handleUpdateQuery}
|
|
156
|
-
onDelete={Type.isMutable(schema) ? handleDeleteField : undefined}
|
|
130
|
+
onDelete={schema && Type.isMutable(schema) ? handleDeleteField : undefined}
|
|
157
131
|
/>
|
|
158
|
-
<JsonFilter data={{ view:
|
|
132
|
+
<JsonFilter data={{ view: kanban.view.target, schema }} classNames='text-xs' />
|
|
159
133
|
</div>
|
|
160
134
|
</div>
|
|
161
135
|
);
|
|
@@ -166,13 +140,13 @@ const DefaultComponent = () => {
|
|
|
166
140
|
//
|
|
167
141
|
|
|
168
142
|
const meta = {
|
|
169
|
-
title: 'plugins/plugin-kanban/Kanban',
|
|
143
|
+
title: 'plugins/plugin-kanban/containers/Kanban',
|
|
170
144
|
component: DefaultComponent,
|
|
171
145
|
render: () => <DefaultComponent />,
|
|
172
|
-
decorators: [withTheme],
|
|
146
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
173
147
|
parameters: {
|
|
174
148
|
layout: 'fullscreen',
|
|
175
|
-
translations
|
|
149
|
+
translations,
|
|
176
150
|
},
|
|
177
151
|
} satisfies Meta<typeof DefaultComponent>;
|
|
178
152
|
|
|
@@ -189,7 +163,7 @@ export const Default: Story = {
|
|
|
189
163
|
withKanbanPlugins({
|
|
190
164
|
types: [Organization.Organization, Person.Person],
|
|
191
165
|
onSpaceCreated: async (space) => {
|
|
192
|
-
const { view } = await
|
|
166
|
+
const { view } = await ViewModel.makeFromDatabase({
|
|
193
167
|
db: space.db,
|
|
194
168
|
typename: Organization.Organization.typename,
|
|
195
169
|
pivotFieldName: 'status',
|
|
@@ -197,8 +171,7 @@ export const Default: Story = {
|
|
|
197
171
|
const kanban = Kanban.make({ view });
|
|
198
172
|
space.db.add(kanban);
|
|
199
173
|
|
|
200
|
-
|
|
201
|
-
Array.from({ length: 80 }).map(() => {
|
|
174
|
+
Array.from({ length: 10 }).map(() => {
|
|
202
175
|
return space.db.add(Obj.make(Organization.Organization, createOrg()));
|
|
203
176
|
});
|
|
204
177
|
},
|
|
@@ -218,16 +191,15 @@ export const Default: Story = {
|
|
|
218
191
|
await expect(prospectTag).toBeTruthy();
|
|
219
192
|
await expect(commitTag).toBeTruthy();
|
|
220
193
|
|
|
221
|
-
// Find the column containers.
|
|
222
|
-
const activeColumn = activeTag.closest('[data-
|
|
223
|
-
const prospectColumn = prospectTag.closest('[data-
|
|
194
|
+
// Find the column containers (Board uses data-testid="board-column").
|
|
195
|
+
const activeColumn = activeTag.closest('[data-testid="board-column"]') as HTMLElement;
|
|
196
|
+
const prospectColumn = prospectTag.closest('[data-testid="board-column"]') as HTMLElement;
|
|
224
197
|
await expect(activeColumn).toBeTruthy();
|
|
225
198
|
await expect(prospectColumn).toBeTruthy();
|
|
226
199
|
|
|
227
|
-
// Wait for cards to render in the columns.
|
|
228
|
-
// Cards have data-dx-item-id attribute from StackItem.Root.
|
|
200
|
+
// Wait for cards to render in the columns (Board items use data-testid="board-item").
|
|
229
201
|
const getColumnCards = (column: HTMLElement) =>
|
|
230
|
-
Array.from(column.querySelectorAll('[data-
|
|
202
|
+
Array.from(column.querySelectorAll('[data-testid="board-item"]')) as HTMLElement[];
|
|
231
203
|
|
|
232
204
|
await waitFor(() => expect(getColumnCards(activeColumn).length).toBeGreaterThan(0));
|
|
233
205
|
|
|
@@ -237,16 +209,16 @@ export const Default: Story = {
|
|
|
237
209
|
await expect(activeCards.length).toBeGreaterThan(0);
|
|
238
210
|
await expect(prospectCards.length).toBeGreaterThan(0);
|
|
239
211
|
|
|
240
|
-
// Verify cards have drag handles (
|
|
212
|
+
// Verify cards have drag handles (Card.Toolbar includes drag handle).
|
|
241
213
|
const firstActiveCard = activeCards[0];
|
|
242
214
|
const buttons = firstActiveCard.querySelectorAll('button');
|
|
243
215
|
await expect(buttons.length).toBeGreaterThan(0);
|
|
244
216
|
|
|
245
|
-
// Verify
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
await expect(
|
|
249
|
-
await expect(
|
|
217
|
+
// Verify add-card action exists in columns (optional footer).
|
|
218
|
+
const activeAddItem = activeColumn.querySelector('[data-testid="board-column-add-item"]');
|
|
219
|
+
const prospectAddItem = prospectColumn.querySelector('[data-testid="board-column-add-item"]');
|
|
220
|
+
await expect(activeAddItem).toBeTruthy();
|
|
221
|
+
await expect(prospectAddItem).toBeTruthy();
|
|
250
222
|
|
|
251
223
|
// TODO(wittjosiah): Get drag & drop tests working.
|
|
252
224
|
// See packages/apps/composer-app/src/playwright/stack.spec.ts for reference.
|
|
@@ -268,7 +240,7 @@ export const MutableSchema: Story = {
|
|
|
268
240
|
// Register schema in the database to make it mutable (EchoSchema).
|
|
269
241
|
const [schema] = await space.db.schemaRegistry.register([Organization.Organization]);
|
|
270
242
|
|
|
271
|
-
const { view } = await
|
|
243
|
+
const { view } = await ViewModel.makeFromDatabase({
|
|
272
244
|
db: space.db,
|
|
273
245
|
typename: schema.typename,
|
|
274
246
|
pivotFieldName: 'status',
|
|
@@ -277,9 +249,14 @@ export const MutableSchema: Story = {
|
|
|
277
249
|
space.db.add(kanban);
|
|
278
250
|
|
|
279
251
|
// Create test data using the registered schema.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
252
|
+
const requiredOrgs = [
|
|
253
|
+
...Array.from({ length: 2 }, () => createOrg('prospect')),
|
|
254
|
+
...Array.from({ length: 5 }, () => createOrg('qualified')),
|
|
255
|
+
...Array.from({ length: 1 }, () => createOrg('active')),
|
|
256
|
+
...Array.from({ length: 1 }, () => createOrg('commit')),
|
|
257
|
+
...Array.from({ length: 1 }, () => createOrg('reject')),
|
|
258
|
+
];
|
|
259
|
+
requiredOrgs.forEach((org) => space.db.add(Obj.make(schema, org)));
|
|
283
260
|
},
|
|
284
261
|
}),
|
|
285
262
|
],
|