@shipfox/client-integrations 22.0.2 → 22.0.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/client-integrations",
3
3
  "license": "MIT",
4
- "version": "22.0.2",
4
+ "version": "22.0.3",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -36,11 +36,11 @@
36
36
  "@shipfox/api-integration-sentry-dto": "12.2.0",
37
37
  "@shipfox/api-integration-webhook-dto": "12.2.0",
38
38
  "@shipfox/client-api": "6.0.1",
39
- "@shipfox/client-auth": "22.0.2",
40
- "@shipfox/client-shell": "22.0.2",
41
- "@shipfox/client-ui": "22.0.2",
42
- "@shipfox/react-ui": "2.1.1",
43
- "@shipfox/integration-icons": "0.3.6"
39
+ "@shipfox/client-auth": "22.0.3",
40
+ "@shipfox/client-shell": "22.0.3",
41
+ "@shipfox/client-ui": "22.0.3",
42
+ "@shipfox/integration-icons": "0.3.7",
43
+ "@shipfox/react-ui": "2.2.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@tanstack/react-query": "^5.101.0",
@@ -161,13 +161,25 @@ export const UsageModalCloseRestoresInteraction: Story = {
161
161
  await user.click(await screen.findByRole('menuitem', {name: 'Use this integration'}));
162
162
  await screen.findByRole('dialog', {name: 'Use acme-corp'});
163
163
  await user.click(await screen.findByRole('button', {name: 'Done'}));
164
- expect(screen.queryByRole('dialog', {name: 'Use acme-corp'})).not.toBeInTheDocument();
165
- // The dialog unmounts before its dismissable layer releases the body, so
166
- // wait for the release rather than asserting it on the same tick: clicking
167
- // while `pointer-events: none` is still set is swallowed silently.
164
+ // The dialog stays mounted while it animates out, which is what keeps the
165
+ // close from being an abrupt unmount.
168
166
  await waitFor(() => {
167
+ expect(screen.queryByRole('dialog', {name: 'Use acme-corp'})).toHaveAttribute(
168
+ 'data-state',
169
+ 'closed',
170
+ );
171
+ });
172
+ // Both the removal and the body pointer-events release therefore land after
173
+ // the click rather than on the same tick.
174
+ await waitFor(() => {
175
+ expect(screen.queryByRole('dialog', {name: 'Use acme-corp'})).not.toBeInTheDocument();
169
176
  expect(document.body.style.pointerEvents).not.toBe('none');
170
177
  });
178
+ // Radix dismisses on the first outside pointerdown while the previous menu is
179
+ // still mounted, so let it finish leaving before reopening.
180
+ await waitFor(() => {
181
+ expect(document.querySelectorAll('[role="menu"]')).toHaveLength(0);
182
+ });
171
183
  await user.click(actionsButton);
172
184
  await screen.findByRole('menuitem', {name: 'Use this integration'});
173
185
  },
@@ -26,7 +26,7 @@ import {
26
26
  } from '@shipfox/react-ui/select';
27
27
  import {Text} from '@shipfox/react-ui/typography';
28
28
  import type {ReactNode} from 'react';
29
- import {useEffect, useMemo, useState} from 'react';
29
+ import {useEffect, useMemo, useRef, useState} from 'react';
30
30
  import {ConnectionStatusBadge} from '#connection-status-badge.js';
31
31
  import type {IntegrationConnection} from '#core/models.js';
32
32
 
@@ -62,19 +62,23 @@ export function IntegrationUsageModal({
62
62
  );
63
63
  }, [eventValuesKey]);
64
64
 
65
- const workflowExample = connection
66
- ? buildWorkflowExample({source: connection.slug, event: selectedEvent})
65
+ // The caller clears the connection in the same render that closes the modal,
66
+ // so keep the last one to render against while the surface animates out.
67
+ const lastConnection = useRef(connection);
68
+ if (connection !== null) lastConnection.current = connection;
69
+ const shownConnection = connection ?? lastConnection.current;
70
+
71
+ const workflowExample = shownConnection
72
+ ? buildWorkflowExample({source: shownConnection.slug, event: selectedEvent})
67
73
  : '';
68
74
  const data = useMemo(
69
75
  () => [{language: 'yaml', filename: WORKFLOW_FILENAME, code: workflowExample}],
70
76
  [workflowExample],
71
77
  );
72
- const title = connection ? `Use ${connection.displayName}` : 'Use integration';
73
-
74
- if (!open || connection === null) return null;
78
+ const title = shownConnection ? `Use ${shownConnection.displayName}` : 'Use integration';
75
79
 
76
80
  return (
77
- <Modal open={open} onOpenChange={onOpenChange}>
81
+ <Modal open={open && connection !== null} onOpenChange={onOpenChange}>
78
82
  <ModalContent aria-describedby={undefined} className="max-h-[calc(100vh-32px)] max-w-[640px]">
79
83
  <ModalTitle className="sr-only">{title}</ModalTitle>
80
84
  <ModalHeader>
@@ -83,8 +87,11 @@ export function IntegrationUsageModal({
83
87
  <Text size="lg" aria-hidden="true" className="truncate">
84
88
  {title}
85
89
  </Text>
86
- {connection ? (
87
- <ConnectionStatusBadge status={connection.lifecycleStatus} className="shrink-0" />
90
+ {shownConnection ? (
91
+ <ConnectionStatusBadge
92
+ status={shownConnection.lifecycleStatus}
93
+ className="shrink-0"
94
+ />
88
95
  ) : null}
89
96
  </div>
90
97
  <Text size="sm" className="text-foreground-neutral-muted">
@@ -92,7 +99,7 @@ export function IntegrationUsageModal({
92
99
  </Text>
93
100
  </div>
94
101
  </ModalHeader>
95
- {connection ? (
102
+ {shownConnection ? (
96
103
  <>
97
104
  <ModalBody className="min-h-0 flex-1 gap-section overflow-y-auto overflow-x-clip scrollbar">
98
105
  <section className="flex w-full flex-col gap-cluster">