@qoretechnologies/reqraft 0.10.27-pr.74.gc5f6ca3 → 0.10.27
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/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +8 -1
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
- package/dist/components/searchFilterBar/index.d.ts +2 -0
- package/dist/components/searchFilterBar/index.d.ts.map +1 -0
- package/dist/components/searchFilterBar/index.js +18 -0
- package/dist/components/searchFilterBar/index.js.map +1 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +9 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.js +2 -2
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.d.ts +52 -6
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.js +240 -31
- package/dist/components/supportTicket/ReferencePicker.js.map +1 -1
- package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
- package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
- package/dist/components/supportTicket/attachmentAccess.js +132 -0
- package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
- package/dist/components/supportTicket/index.d.ts +2 -0
- package/dist/components/supportTicket/index.d.ts.map +1 -1
- package/dist/components/supportTicket/index.js +2 -0
- package/dist/components/supportTicket/index.js.map +1 -1
- package/dist/components/supportTicket/meta.d.ts +8 -0
- package/dist/components/supportTicket/meta.d.ts.map +1 -1
- package/dist/components/supportTicket/meta.js +37 -1
- package/dist/components/supportTicket/meta.js.map +1 -1
- package/dist/components/supportTicket/ticketModel.d.ts +102 -0
- package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
- package/dist/components/supportTicket/ticketModel.js +3 -0
- package/dist/components/supportTicket/ticketModel.js.map +1 -0
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -1
- package/dist/components/ticketReferences/TicketReferences.js +109 -78
- package/dist/components/ticketReferences/TicketReferences.js.map +1 -1
- package/dist/components/ticketThread/TicketThread.d.ts.map +1 -1
- package/dist/components/ticketThread/TicketThread.js +19 -2
- package/dist/components/ticketThread/TicketThread.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/engine/CompactRow.tsx +7 -0
- package/src/components/form/engine/FormEngine.stories.tsx +90 -0
- package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
- package/src/components/searchFilterBar/index.ts +1 -0
- package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +37 -0
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +11 -0
- package/src/components/supportTicket/ReferencePicker.stories.tsx +257 -34
- package/src/components/supportTicket/ReferencePicker.tsx +421 -95
- package/src/components/supportTicket/TicketConversation.stories.tsx +0 -1
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +0 -4
- package/src/components/supportTicket/attachmentAccess.ts +97 -0
- package/src/components/supportTicket/index.ts +2 -0
- package/src/components/supportTicket/meta.ts +40 -0
- package/src/components/supportTicket/ticketModel.ts +131 -0
- package/src/components/ticketReferences/TicketReferences.tsx +124 -145
- package/src/components/ticketThread/TicketThread.stories.tsx +24 -2
- package/src/components/ticketThread/TicketThread.tsx +23 -3
- package/src/index.tsx +3 -1
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { ReqoreButton, ReqoreUIProvider } from '@qoretechnologies/reqore';
|
|
2
2
|
import { StoryObj } from '@storybook/react-vite';
|
|
3
|
-
import { useState } from 'react';
|
|
3
|
+
import { ComponentProps, useState } from 'react';
|
|
4
4
|
import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
|
|
5
5
|
import { StoryMeta } from '../../types';
|
|
6
6
|
import { InterfaceReferenceTags } from './InterfaceReferenceTags';
|
|
7
7
|
import { IInterfaceReference } from './meta';
|
|
8
|
-
import { ReferencePicker } from './ReferencePicker';
|
|
8
|
+
import { ReferencePicker, TReferencePickerItem } from './ReferencePicker';
|
|
9
9
|
import { TicketReplyBox } from './TicketReplyBox';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* The interface browser — kinds on one side, the selected kind's interfaces on the
|
|
13
|
+
* other, one search bar scoping both. Rendered **on top** of the composer and toggled
|
|
14
|
+
* by its "Reference interfaces" action. Search dims non-matching kinds rather than
|
|
15
|
+
* removing them, picked interfaces stay in the list as selected rows, and the two panes
|
|
16
|
+
* collapse into one column (with a modal on phones) when there isn't width for both.
|
|
15
17
|
*/
|
|
16
18
|
const meta = {
|
|
17
19
|
component: ReferencePicker,
|
|
@@ -22,11 +24,13 @@ type Story = StoryObj<typeof meta>;
|
|
|
22
24
|
|
|
23
25
|
const ACCENT = { main: 'custom1' } as const;
|
|
24
26
|
|
|
27
|
+
/* The kind ids a host passes — wire values, not labels. `qog` is the customer-facing
|
|
28
|
+
* kind for what the instance still lists as `fsm`; hosts map that at the fetch
|
|
29
|
+
* boundary, so `fsm` never reaches the picker. */
|
|
25
30
|
const KINDS = [
|
|
26
31
|
'workflow',
|
|
27
32
|
'service',
|
|
28
33
|
'job',
|
|
29
|
-
'fsm',
|
|
30
34
|
'connection',
|
|
31
35
|
'mapper',
|
|
32
36
|
'class',
|
|
@@ -38,17 +42,26 @@ const KINDS = [
|
|
|
38
42
|
];
|
|
39
43
|
|
|
40
44
|
/* Stands in for the host's fetch: in the IDE these come off the customer's live
|
|
41
|
-
* instance one kind at a time.
|
|
42
|
-
|
|
45
|
+
* instance one kind at a time. Services carry the richer item shape — a description
|
|
46
|
+
* and a badge — with `partner-gateway` left as a bare string so the plain row and the
|
|
47
|
+
* mixed list both stay on screen. */
|
|
48
|
+
const CATALOGUE: Record<string, TReferencePickerItem[]> = {
|
|
43
49
|
workflow: ['order-sync:1.2', 'invoice-export:2.0', 'partner-recon:1.1'],
|
|
44
|
-
service: [
|
|
50
|
+
service: [
|
|
51
|
+
{
|
|
52
|
+
name: 'notifier',
|
|
53
|
+
description: 'Fans order events out to email and Slack',
|
|
54
|
+
badge: '3 running',
|
|
55
|
+
},
|
|
56
|
+
{ name: 'audit-logger', description: 'Writes every state change to the audit table' },
|
|
57
|
+
'partner-gateway',
|
|
58
|
+
],
|
|
45
59
|
job: ['nightly-recon', 'cleanup-temp', 'sla-report'],
|
|
46
|
-
fsm: ['onboarding', 'escalation'],
|
|
47
60
|
connection: ['sftp-partner', 'pgsql-omq', 'salesforce-prod'],
|
|
48
61
|
mapper: ['csv-to-order', 'order-to-invoice'],
|
|
49
62
|
class: ['OrderUtils', 'PartnerApi'],
|
|
50
63
|
'value-map': ['country-codes', 'currency'],
|
|
51
|
-
qog: ['telegram-intake', 'stripe-billing'],
|
|
64
|
+
qog: ['telegram-intake', 'stripe-billing', 'onboarding', 'escalation'],
|
|
52
65
|
'ai-collection': ['kb-support'],
|
|
53
66
|
'ai-endpoint': ['gpt-router'],
|
|
54
67
|
'ai-guardrail': ['pii-filter'],
|
|
@@ -64,11 +77,24 @@ const darkTheme = (Story: () => JSX.Element) => (
|
|
|
64
77
|
|
|
65
78
|
/** Drives the picker the way a host does: owns the selected kind, resolves that
|
|
66
79
|
* kind's interfaces, and accumulates what gets picked. */
|
|
67
|
-
const Hosted = ({
|
|
68
|
-
|
|
80
|
+
const Hosted = ({
|
|
81
|
+
withComposer,
|
|
82
|
+
initialKind = 'workflow',
|
|
83
|
+
...pickerProps
|
|
84
|
+
}: { withComposer?: boolean; initialKind?: string } & Partial<
|
|
85
|
+
ComponentProps<typeof ReferencePicker>
|
|
86
|
+
>) => {
|
|
87
|
+
const [kind, setKind] = useState<string>(initialKind);
|
|
69
88
|
const [picked, setPicked] = useState<IInterfaceReference[]>([]);
|
|
70
89
|
const [open, setOpen] = useState<boolean>(true);
|
|
71
90
|
|
|
91
|
+
const isPicked = (reference: IInterfaceReference) =>
|
|
92
|
+
picked.some(
|
|
93
|
+
(existing) =>
|
|
94
|
+
existing.interface_kind === reference.interface_kind &&
|
|
95
|
+
existing.interface_name === reference.interface_name
|
|
96
|
+
);
|
|
97
|
+
|
|
72
98
|
const picker = (
|
|
73
99
|
<ReferencePicker
|
|
74
100
|
kinds={KINDS}
|
|
@@ -76,7 +102,21 @@ const Hosted = ({ withComposer }: { withComposer?: boolean }) => {
|
|
|
76
102
|
onKindChange={setKind}
|
|
77
103
|
items={CATALOGUE[kind] ?? []}
|
|
78
104
|
picked={picked}
|
|
79
|
-
onAdd={(reference) =>
|
|
105
|
+
onAdd={(reference) =>
|
|
106
|
+
setPicked((current) => (isPicked(reference) ? current : [...current, reference]))
|
|
107
|
+
}
|
|
108
|
+
onRemove={(reference) =>
|
|
109
|
+
setPicked((current) =>
|
|
110
|
+
current.filter(
|
|
111
|
+
(existing) =>
|
|
112
|
+
!(
|
|
113
|
+
existing.interface_kind === reference.interface_kind &&
|
|
114
|
+
existing.interface_name === reference.interface_name
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
{...pickerProps}
|
|
80
120
|
/>
|
|
81
121
|
);
|
|
82
122
|
|
|
@@ -92,7 +132,24 @@ const Hosted = ({ withComposer }: { withComposer?: boolean }) => {
|
|
|
92
132
|
placeholder='Write a reply…'
|
|
93
133
|
aboveInput={
|
|
94
134
|
picked.length ? (
|
|
95
|
-
|
|
135
|
+
/* Composer chips are the references you're about to send, so they carry
|
|
136
|
+
their own remove — and read a size up from the thread's, where they're
|
|
137
|
+
a record rather than a control. */
|
|
138
|
+
<InterfaceReferenceTags
|
|
139
|
+
customTheme={ACCENT}
|
|
140
|
+
references={picked}
|
|
141
|
+
onRemove={(reference) =>
|
|
142
|
+
setPicked((current) =>
|
|
143
|
+
current.filter(
|
|
144
|
+
(existing) =>
|
|
145
|
+
!(
|
|
146
|
+
existing.interface_kind === reference.interface_kind &&
|
|
147
|
+
existing.interface_name === reference.interface_name
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
/>
|
|
96
153
|
) : undefined
|
|
97
154
|
}
|
|
98
155
|
footerActions={
|
|
@@ -123,14 +180,13 @@ const option = (canvas: ReturnType<typeof within>, name: string) =>
|
|
|
123
180
|
const missingOption = (canvas: ReturnType<typeof within>, name: string) =>
|
|
124
181
|
canvas.queryByRole('button', { name });
|
|
125
182
|
|
|
126
|
-
/** Picking an interface adds it as a reference
|
|
127
|
-
* same thing can't be referenced twice. */
|
|
183
|
+
/** Picking an interface adds it as a reference and leaves the row in place, selected. */
|
|
128
184
|
export const Default: Story = {
|
|
129
185
|
parameters: {
|
|
130
186
|
docs: {
|
|
131
187
|
description: {
|
|
132
188
|
story:
|
|
133
|
-
|
|
189
|
+
'Renders the two-pane reference browser. Picking an interface adds it as a reference and leaves the row in the list, selected and ticked, rather than removing it.',
|
|
134
190
|
},
|
|
135
191
|
},
|
|
136
192
|
},
|
|
@@ -139,13 +195,104 @@ export const Default: Story = {
|
|
|
139
195
|
async play({ canvasElement }) {
|
|
140
196
|
const canvas = within(canvasElement);
|
|
141
197
|
await userEvent.click(await canvas.findByRole('button', { name: 'order-sync:1.2' }));
|
|
142
|
-
// it
|
|
143
|
-
await waitFor(() =>
|
|
198
|
+
// it stays in the pane, now selected
|
|
199
|
+
await waitFor(() =>
|
|
200
|
+
expect(option(canvas, 'order-sync:1.2')).toHaveClass('reqore-reference-picker-picked')
|
|
201
|
+
);
|
|
144
202
|
// its siblings are untouched
|
|
145
203
|
await expect(option(canvas, 'invoice-export:2.0')).toBeInTheDocument();
|
|
146
204
|
},
|
|
147
205
|
};
|
|
148
206
|
|
|
207
|
+
/** A picked interface can be un-picked from the list it was picked in. */
|
|
208
|
+
export const DeselectsAPickedInterface: Story = {
|
|
209
|
+
parameters: {
|
|
210
|
+
docs: {
|
|
211
|
+
description: {
|
|
212
|
+
story:
|
|
213
|
+
'Renders the browser and picks an interface, then clicks the same row again — the reference comes back off without the user having to click away from the picker.',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
decorators: [darkTheme],
|
|
218
|
+
render: () => <Hosted />,
|
|
219
|
+
async play({ canvasElement }) {
|
|
220
|
+
const canvas = within(canvasElement);
|
|
221
|
+
const row = await canvas.findByRole('button', { name: 'invoice-export:2.0' });
|
|
222
|
+
|
|
223
|
+
await userEvent.click(row);
|
|
224
|
+
await waitFor(() => expect(row).toHaveClass('reqore-reference-picker-picked'));
|
|
225
|
+
|
|
226
|
+
await userEvent.click(row);
|
|
227
|
+
await waitFor(() => expect(row).not.toHaveClass('reqore-reference-picker-picked'));
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Interfaces can arrive as bare names or as objects carrying a description and a
|
|
233
|
+
* badge — a name alone often isn't enough to tell two of them apart. A picked row
|
|
234
|
+
* tints rather than filling solid, so the ones still up for choosing stay the loudest
|
|
235
|
+
* things in the list.
|
|
236
|
+
*/
|
|
237
|
+
export const WithDescriptionsAndBadges: Story = {
|
|
238
|
+
parameters: {
|
|
239
|
+
docs: {
|
|
240
|
+
description: {
|
|
241
|
+
story:
|
|
242
|
+
'Renders a kind whose interfaces carry descriptions and badges alongside one that has neither. Picking a row tints it rather than filling it solid, keeping the unpicked rows the most prominent.',
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
decorators: [darkTheme],
|
|
247
|
+
render: () => <Hosted initialKind='service' />,
|
|
248
|
+
async play({ canvasElement }) {
|
|
249
|
+
const canvas = within(canvasElement);
|
|
250
|
+
await expect(
|
|
251
|
+
await canvas.findByText('Fans order events out to email and Slack')
|
|
252
|
+
).toBeInTheDocument();
|
|
253
|
+
await expect(canvas.getByText('3 running')).toBeInTheDocument();
|
|
254
|
+
|
|
255
|
+
// a bare-string item in the same list renders as a plain row
|
|
256
|
+
await expect(option(canvas, 'partner-gateway')).toBeInTheDocument();
|
|
257
|
+
|
|
258
|
+
// picking keeps the description and badge in place
|
|
259
|
+
await userEvent.click(canvas.getByRole('button', { name: /audit-logger/ }));
|
|
260
|
+
await waitFor(() =>
|
|
261
|
+
expect(canvas.getByRole('button', { name: /audit-logger/ })).toHaveClass(
|
|
262
|
+
'reqore-reference-picker-picked'
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
await expect(
|
|
266
|
+
canvas.getByText('Writes every state change to the audit table')
|
|
267
|
+
).toBeInTheDocument();
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/** A description is searchable text too, so the search reads it alongside the name. */
|
|
272
|
+
export const SearchMatchesDescriptions: Story = {
|
|
273
|
+
parameters: {
|
|
274
|
+
docs: {
|
|
275
|
+
description: {
|
|
276
|
+
story:
|
|
277
|
+
"Renders a kind with described interfaces and searches for a word that appears only in a description — the matching row survives even though its name doesn't contain the query.",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
decorators: [darkTheme],
|
|
282
|
+
render: () => <Hosted initialKind='service' />,
|
|
283
|
+
async play({ canvasElement }) {
|
|
284
|
+
const canvas = within(canvasElement);
|
|
285
|
+
const search = canvasElement.querySelector(
|
|
286
|
+
'input[placeholder*="Search types"]'
|
|
287
|
+
) as HTMLInputElement;
|
|
288
|
+
|
|
289
|
+
// "Slack" is only in the notifier's description
|
|
290
|
+
await userEvent.type(search, 'slack');
|
|
291
|
+
await waitFor(() => expect(missingOption(canvas, 'partner-gateway')).not.toBeInTheDocument());
|
|
292
|
+
await expect(canvas.getByRole('button', { name: /notifier/ })).toBeInTheDocument();
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
149
296
|
/** Selecting a kind on the left swaps the right pane to that kind's interfaces. */
|
|
150
297
|
export const SwitchesKind: Story = {
|
|
151
298
|
parameters: {
|
|
@@ -161,19 +308,23 @@ export const SwitchesKind: Story = {
|
|
|
161
308
|
async play({ canvasElement }) {
|
|
162
309
|
const canvas = within(canvasElement);
|
|
163
310
|
await expect(option(canvas, 'order-sync:1.2')).toBeInTheDocument();
|
|
164
|
-
await userEvent.click(option(canvas, '
|
|
311
|
+
await userEvent.click(option(canvas, 'Connections'));
|
|
165
312
|
await waitFor(() => expect(option(canvas, 'sftp-partner')).toBeInTheDocument());
|
|
166
313
|
await expect(missingOption(canvas, 'order-sync:1.2')).not.toBeInTheDocument();
|
|
167
314
|
},
|
|
168
315
|
};
|
|
169
316
|
|
|
170
|
-
/**
|
|
171
|
-
|
|
317
|
+
/**
|
|
318
|
+
* One search scopes both panes: non-matching kinds drop off the left so the matches
|
|
319
|
+
* are the whole list, and the selected kind stays on — dimmed — even when the query
|
|
320
|
+
* doesn't match it, so the interfaces on the right always have a visible owner.
|
|
321
|
+
*/
|
|
322
|
+
export const SearchKeepsTheSelectedKind: Story = {
|
|
172
323
|
parameters: {
|
|
173
324
|
docs: {
|
|
174
325
|
description: {
|
|
175
326
|
story:
|
|
176
|
-
"Renders the picker and
|
|
327
|
+
"Renders the picker and types a search. Non-matching kinds drop off the left pane, but the selected kind stays on as a dimmed row so the interfaces still listed on the right can be told apart from the kind they belong to.",
|
|
177
328
|
},
|
|
178
329
|
},
|
|
179
330
|
},
|
|
@@ -182,7 +333,7 @@ export const SearchScopesBothPanes: Story = {
|
|
|
182
333
|
async play({ canvasElement }) {
|
|
183
334
|
const canvas = within(canvasElement);
|
|
184
335
|
const search = canvasElement.querySelector(
|
|
185
|
-
'input[placeholder*="Search
|
|
336
|
+
'input[placeholder*="Search types"]'
|
|
186
337
|
) as HTMLInputElement;
|
|
187
338
|
|
|
188
339
|
await userEvent.type(search, 'sync');
|
|
@@ -191,14 +342,84 @@ export const SearchScopesBothPanes: Story = {
|
|
|
191
342
|
expect(missingOption(canvas, 'invoice-export:2.0')).not.toBeInTheDocument()
|
|
192
343
|
);
|
|
193
344
|
await expect(option(canvas, 'order-sync:1.2')).toBeInTheDocument();
|
|
194
|
-
// left pane:
|
|
195
|
-
|
|
345
|
+
// left pane: nothing is called "sync", so only the selected kind is left — dimmed,
|
|
346
|
+
// which is what tells you the surviving interface is a workflow
|
|
347
|
+
await expect(option(canvas, 'Workflows')).toHaveClass('reqore-reference-picker-dimmed');
|
|
348
|
+
await expect(missingOption(canvas, 'Services')).not.toBeInTheDocument();
|
|
196
349
|
|
|
197
|
-
// a kind name matches:
|
|
350
|
+
// a kind name matches: it joins the list at full strength, next to the still-dimmed
|
|
351
|
+
// selected kind
|
|
198
352
|
await userEvent.clear(search);
|
|
199
353
|
await userEvent.type(search, 'connect');
|
|
200
|
-
await waitFor(() =>
|
|
201
|
-
|
|
354
|
+
await waitFor(() =>
|
|
355
|
+
expect(option(canvas, 'Connections')).not.toHaveClass(
|
|
356
|
+
'reqore-reference-picker-dimmed'
|
|
357
|
+
)
|
|
358
|
+
);
|
|
359
|
+
await expect(option(canvas, 'Workflows')).toHaveClass('reqore-reference-picker-dimmed');
|
|
360
|
+
await expect(missingOption(canvas, 'Jobs')).not.toBeInTheDocument();
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Narrow container: the panes collapse into one column and the selected kind's
|
|
366
|
+
* interfaces render directly under its row. The width measured is the picker's own,
|
|
367
|
+
* not the viewport's — this canvas is 320px wide on a desktop-sized screen, which is
|
|
368
|
+
* exactly the case a viewport media query gets wrong.
|
|
369
|
+
*/
|
|
370
|
+
export const StacksInANarrowContainer: Story = {
|
|
371
|
+
parameters: {
|
|
372
|
+
docs: {
|
|
373
|
+
description: {
|
|
374
|
+
story:
|
|
375
|
+
"Renders the picker in a 320px-wide container on a full-size screen: it collapses to one column on its own measured width, with the selected kind's interfaces indented directly beneath its row instead of in a second pane.",
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
decorators: [darkTheme],
|
|
380
|
+
render: () => (
|
|
381
|
+
<div style={{ width: 320 }}>
|
|
382
|
+
<Hosted />
|
|
383
|
+
</div>
|
|
384
|
+
),
|
|
385
|
+
async play({ canvasElement }) {
|
|
386
|
+
const canvas = within(canvasElement);
|
|
387
|
+
const services = option(canvas, 'Services');
|
|
388
|
+
|
|
389
|
+
/* Stacked is a claim about ORDER, not just presence: the workflows sit between
|
|
390
|
+
their own kind row and the next one. Side by side, every kind would come
|
|
391
|
+
first and the interfaces after — so the comparison tells the two apart
|
|
392
|
+
without reaching for a marker class. */
|
|
393
|
+
await waitFor(() => {
|
|
394
|
+
const workflow = option(canvas, 'order-sync:1.2');
|
|
395
|
+
expect(
|
|
396
|
+
workflow.compareDocumentPosition(services) & Node.DOCUMENT_POSITION_FOLLOWING
|
|
397
|
+
).toBeTruthy();
|
|
398
|
+
});
|
|
399
|
+
await expect(option(canvas, 'Workflows')).toBeInTheDocument();
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/** On a phone the picker lifts into a modal — inline it would own the whole screen. */
|
|
404
|
+
export const OnAPhone: Story = {
|
|
405
|
+
parameters: {
|
|
406
|
+
// capture this one at phone size; the modal portals to the viewport, so a
|
|
407
|
+
// narrow story wrapper wouldn't constrain it
|
|
408
|
+
qlip: { viewport: { width: 390, height: 844 } },
|
|
409
|
+
docs: {
|
|
410
|
+
description: {
|
|
411
|
+
story:
|
|
412
|
+
'Renders the phone presentation: the picker lifts out of the composer into a full-width modal with a close action, stacked into a single column.',
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
decorators: [darkTheme],
|
|
417
|
+
render: () => <Hosted presentation='modal' layout='stacked' onClose={fn()} />,
|
|
418
|
+
async play() {
|
|
419
|
+
// the modal portals out of the story canvas, so query the document
|
|
420
|
+
const canvas = within(document.body);
|
|
421
|
+
await expect(await canvas.findByText('Reference interfaces')).toBeInTheDocument();
|
|
422
|
+
await expect(await canvas.findByRole('button', { name: 'order-sync:1.2' })).toBeInTheDocument();
|
|
202
423
|
},
|
|
203
424
|
};
|
|
204
425
|
|
|
@@ -226,7 +447,7 @@ export const Loading: Story = {
|
|
|
226
447
|
async play({ canvasElement }) {
|
|
227
448
|
const canvas = within(canvasElement);
|
|
228
449
|
await expect(canvas.getByText('Loading workflows…')).toBeInTheDocument();
|
|
229
|
-
await expect(canvas.queryByText(
|
|
450
|
+
await expect(canvas.queryByText(/No workflows/)).not.toBeInTheDocument();
|
|
230
451
|
},
|
|
231
452
|
};
|
|
232
453
|
|
|
@@ -261,7 +482,7 @@ export const LoadFailed: Story = {
|
|
|
261
482
|
const retry = await canvas.findByRole('button', { name: /Couldn't load connections/ });
|
|
262
483
|
await expect(retry).toBeInTheDocument();
|
|
263
484
|
// the failure replaces the list rather than sitting alongside an empty one
|
|
264
|
-
await expect(canvas.queryByText(
|
|
485
|
+
await expect(canvas.queryByText(/No connections/)).not.toBeInTheDocument();
|
|
265
486
|
},
|
|
266
487
|
};
|
|
267
488
|
|
|
@@ -281,8 +502,10 @@ export const AboveTheComposer: Story = {
|
|
|
281
502
|
async play({ canvasElement }) {
|
|
282
503
|
const canvas = within(canvasElement);
|
|
283
504
|
await userEvent.click(await canvas.findByRole('button', { name: 'order-sync:1.2' }));
|
|
284
|
-
// the pick
|
|
285
|
-
await waitFor(() =>
|
|
505
|
+
// the pick stays selected in the pane and surfaces as a chip on the composer
|
|
506
|
+
await waitFor(() =>
|
|
507
|
+
expect(option(canvas, 'order-sync:1.2')).toHaveClass('reqore-reference-picker-picked')
|
|
508
|
+
);
|
|
286
509
|
await expect(canvas.getAllByText('order-sync:1.2').length).toBeGreaterThan(0);
|
|
287
510
|
|
|
288
511
|
const toggle = canvasElement.querySelector('.references-toggle') as HTMLElement;
|