@devicai/ui 0.40.2 → 0.40.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/dist/cjs/components/IntegrationsModal/IntegrationsHint.js +36 -21
- package/dist/cjs/components/IntegrationsModal/IntegrationsHint.js.map +1 -1
- package/dist/esm/components/IntegrationsModal/IntegrationsHint.js +36 -21
- package/dist/esm/components/IntegrationsModal/IntegrationsHint.js.map +1 -1
- package/package.json +1 -1
|
@@ -165,27 +165,42 @@ function IntegrationsHint({ state, onOpen, label, maxLogos = 6, storageKey, flyT
|
|
|
165
165
|
const bowX = ((-dy / len) * bow) * side;
|
|
166
166
|
const bowY = ((dx / len) * bow) * side;
|
|
167
167
|
const lift = Math.min(70, Math.max(18, len * 0.18));
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
168
|
+
// The curve the copy travels, as a quadratic Bézier through that midpoint.
|
|
169
|
+
// The control point is derived so the curve actually passes through it —
|
|
170
|
+
// a Bézier does not go through its control.
|
|
171
|
+
const midX = dx * 0.45 + bowX;
|
|
172
|
+
const midY = dy * 0.5 - lift + bowY;
|
|
173
|
+
const ctrlX = 2 * midX - dx / 2;
|
|
174
|
+
const ctrlY = 2 * midY - dy / 2;
|
|
175
|
+
// Sampled into many linear steps rather than three keyframes with an easing
|
|
176
|
+
// each. Per-keyframe easings ease OUT into the midpoint and IN out of it,
|
|
177
|
+
// which is a stop halfway — the shape was right and the motion was two
|
|
178
|
+
// hops. Here the keyframes carry only the shape, and one global easing
|
|
179
|
+
// carries the pace: speed builds to a peak at the centre and settles into
|
|
180
|
+
// the target.
|
|
181
|
+
const STEPS = 24;
|
|
182
|
+
// Above the 1.2 of a plain keyframe: this rides on top of the shrink
|
|
183
|
+
// towards the target's size, so the two partly cancel at the apex.
|
|
184
|
+
const PEAK = 1.35;
|
|
185
|
+
const frames = Array.from({ length: STEPS + 1 }, (_, i) => {
|
|
186
|
+
const t = i / STEPS;
|
|
187
|
+
const u = 1 - t;
|
|
188
|
+
return {
|
|
189
|
+
offset: t,
|
|
190
|
+
transform: `translate(${2 * u * t * ctrlX + t * t * dx}px, ` +
|
|
191
|
+
`${2 * u * t * ctrlY + t * t * dy}px) ` +
|
|
192
|
+
// Swells on the way and shrinks into the header, so the eye follows
|
|
193
|
+
// the thing rather than the fade.
|
|
194
|
+
`scale(${1 + (scale - 1) * t + (PEAK - 1) * Math.sin(Math.PI * t)})`,
|
|
195
|
+
opacity: t <= 0.5 ? 1 : 1 - Math.pow((t - 0.5) * 2, 1.6),
|
|
196
|
+
easing: "linear",
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
const animation = ghost.animate?.(frames, {
|
|
200
|
+
duration: 780,
|
|
201
|
+
easing: "cubic-bezier(0.5, 0, 0.5, 1)",
|
|
202
|
+
fill: "forwards",
|
|
203
|
+
});
|
|
189
204
|
const remove = () => ghost.remove();
|
|
190
205
|
if (animation)
|
|
191
206
|
animation.onfinish = remove;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntegrationsHint.js","sources":["../../../../../src/components/IntegrationsModal/IntegrationsHint.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport type { Integration } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsHintProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /** Opens the connected-apps modal. */\n onOpen: () => void;\n /**\n * Text on the left. Defaults to \"Connect your apps\", or \"Explore connected\n * apps\" once at least one is connected — an invitation the user has already\n * accepted stops being an invitation.\n */\n label?: string;\n /** Most logos to show before the `+N` box. @default 6 */\n maxLogos?: number;\n /**\n * Identity the dismissal is remembered against, so closing it for one end\n * user does not close it for the next one on the same browser.\n */\n storageKey?: string;\n /** Where the bar flies to when closed — the header control. */\n flyToSelector?: string;\n /** Light chips for a dark surface. */\n dark?: boolean;\n className?: string;\n}\n\nconst STORAGE_PREFIX = \"devic:apps-hint-dismissed:\";\n\n/** Reading storage throws in Safari's private mode and in sandboxed frames. */\nfunction wasDismissed(key?: string): boolean {\n if (!key) return false;\n try {\n return localStorage.getItem(STORAGE_PREFIX + key) === \"1\";\n } catch {\n return false;\n }\n}\n\nfunction rememberDismissal(key?: string): void {\n if (!key) return;\n try {\n localStorage.setItem(STORAGE_PREFIX + key, \"1\");\n } catch {\n // Then it comes back next session. Not worth a word to the user.\n }\n}\n\n/** Connected first, same order as the header control. */\nfunction order(integrations: Integration[]): Integration[] {\n return [...integrations].sort((a, b) => {\n if (a.connected !== b.connected) return a.connected ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * How many 24px chips fit once the label and the close button are paid for.\n *\n * The label is the whole reason this row exists, so it gets its space first:\n * a strip reading \"Connect your …\" beside seven logos has lost the argument.\n * Never fewer than three chips, below which the row stops reading as a set of\n * apps at all.\n */\nfunction boxesThatFit(width: number, max: number): number {\n if (!width) return max + 1;\n const LABEL_AND_CLOSE = 195;\n return Math.max(3, Math.min(max + 1, Math.floor((width - LABEL_AND_CLOSE) / 30)));\n}\n\n/** Whether an element is on screen right now. */\nfunction isVisible(el: Element): boolean {\n const r = el.getBoundingClientRect();\n if (!r.width || !r.height) return false;\n return (\n r.bottom > 0 &&\n r.right > 0 &&\n r.top < window.innerHeight &&\n r.left < window.innerWidth\n );\n}\n\n/**\n * The header control this bar belongs to.\n *\n * Scoped to its own drawer first — a page can hold several, each with its own\n * header — and only then to the nearest visible one anywhere. The class name is\n * checked against both the drawer's own container and the legacy one, because\n * getting this wrong is silent: `closest` returns null, the search widens to the\n * whole document, and the copy flies to whichever control happens to be first\n * in the DOM.\n */\nfunction flyTarget(bar: Element, selector: string): HTMLElement | null {\n if (!selector) return null;\n\n const drawer = bar.closest(\".devic-chat-drawer, .devic-drawer\");\n const own = drawer?.querySelector(selector) as HTMLElement | null;\n if (own && isVisible(own)) return own;\n\n const barRect = bar.getBoundingClientRect();\n const candidates = [...document.querySelectorAll(selector)].filter(isVisible);\n if (!candidates.length) return null;\n\n // Nearest to the bar, so a page with several drawers still animates towards\n // the one the user is looking at.\n return candidates.reduce((best, el) => {\n const d = (e: Element) => {\n const r = e.getBoundingClientRect();\n return Math.hypot(r.left - barRect.left, r.top - barRect.top);\n };\n return d(el) < d(best) ? el : best;\n }) as HTMLElement;\n}\n\n/**\n * The strip above the composer that tells the end user their own apps can be\n * connected here.\n *\n * It exists because the header control is a row of small logos and nothing\n * else: discoverable once you know what it is, invisible until then. This says\n * it in words, once, and then gets out of the way for good — the dismissal is\n * remembered per end user, and closing it animates towards the header so that\n * what was dismissed is understood to still be there.\n */\nexport function IntegrationsHint({\n state,\n onOpen,\n label,\n maxLogos = 6,\n storageKey,\n flyToSelector = \".devic-int-launcher\",\n dark = false,\n className = \"\",\n}: IntegrationsHintProps): JSX.Element | null {\n const sorted = useMemo(() => order(state.integrations), [state.integrations]);\n const ref = useRef<HTMLDivElement>(null);\n const [boxes, setBoxes] = useState(maxLogos + 1);\n const [dismissed, setDismissed] = useState(() => wasDismissed(storageKey));\n const [leaving, setLeaving] = useState(false);\n\n // A different end user has their own answer to whether this was dismissed.\n useEffect(() => {\n setDismissed(wasDismissed(storageKey));\n setLeaving(false);\n }, [storageKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const measure = () =>\n setBoxes(boxesThatFit(el.getBoundingClientRect().width, maxLogos));\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [maxLogos, state.offered, dismissed]);\n\n /**\n * Sends a copy of the logos from the bar to the header control.\n *\n * A dismissal that simply blanks the row reads as \"that is gone now\". The\n * flight is the sentence \"it moved up there\" said without words — so if it\n * cannot be drawn (reduced motion, no target, no Web Animations API), the bar\n * just closes and nothing is lost.\n */\n const flyToLauncher = useCallback(() => {\n const bar = ref.current;\n if (!bar || typeof document === \"undefined\") return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const target = flyTarget(bar, flyToSelector);\n // Nothing to fly to, or nothing the user can see: the bar just closes.\n // A copy sailing off towards something 2000px down the page says the\n // opposite of what this animation is for.\n if (!target) return;\n\n const logos = bar.querySelector(\".devic-int-hint-logos\");\n const from = (logos ?? bar).getBoundingClientRect();\n const to = target.getBoundingClientRect();\n\n const ghost = (logos ?? bar).cloneNode(true) as HTMLElement;\n ghost.classList.add(\"devic-int-hint-ghost\");\n ghost.style.left = `${from.left}px`;\n ghost.style.top = `${from.top}px`;\n ghost.style.width = `${from.width}px`;\n ghost.style.height = `${from.height}px`;\n document.body.appendChild(ghost);\n\n const dx = to.left + to.width / 2 - (from.left + from.width / 2);\n const dy = to.top + to.height / 2 - (from.top + from.height / 2);\n const scale = from.width ? Math.max(0.4, to.width / from.width) : 0.6;\n\n // An arc, not a straight line. The bow is perpendicular to the path, which\n // is what actually reads as a curve: this trip is nearly vertical, and\n // pulling only on Y just makes it accelerate. It opens away from the\n // target's side so the copy swings out and comes back in, and both terms\n // scale with the distance — a short hop should not loop — and are capped so\n // a long page does not throw it off screen.\n const len = Math.hypot(dx, dy) || 1;\n const bow = Math.min(48, Math.max(16, len * 0.18));\n const side = dx <= 0 ? 1 : -1;\n const bowX = ((-dy / len) * bow) * side;\n const bowY = ((dx / len) * bow) * side;\n const lift = Math.min(70, Math.max(18, len * 0.18));\n\n const animation = ghost.animate?.(\n [\n {\n transform: \"translate(0, 0) scale(1)\",\n opacity: 0.95,\n offset: 0,\n easing: \"cubic-bezier(0.22, 0.7, 0.4, 1)\",\n },\n {\n // Bigger in the middle: it grows as it leaves the bar and shrinks\n // into the header, so the eye follows the thing rather than the fade.\n transform: `translate(${dx * 0.45 + bowX}px, ${dy * 0.5 - lift + bowY}px) scale(1.2)`,\n opacity: 1,\n offset: 0.5,\n easing: \"cubic-bezier(0.5, 0, 0.7, 0.9)\",\n },\n {\n transform: `translate(${dx}px, ${dy}px) scale(${scale})`,\n opacity: 0,\n offset: 1,\n },\n ],\n { duration: 780, fill: \"forwards\" }\n );\n\n const remove = () => ghost.remove();\n if (animation) animation.onfinish = remove;\n else remove();\n // Belt and braces: a tab backgrounded mid-flight never fires onfinish.\n window.setTimeout(remove, 2000);\n }, [flyToSelector]);\n\n const dismiss = () => {\n flyToLauncher();\n rememberDismissal(storageKey);\n setLeaving(true);\n window.setTimeout(() => setDismissed(true), 220);\n };\n\n if (dismissed || !state.offered || sorted.length === 0) return null;\n\n const text =\n label ??\n (sorted.some((i) => i.connected)\n ? \"Explore connected apps\"\n : \"Connect your apps\");\n\n // When some apps will not fit, the `+N` box takes one of the slots itself.\n const capacity = Math.max(1, boxes);\n const shown = sorted.slice(\n 0,\n sorted.length > capacity ? capacity - 1 : capacity\n );\n const extra = sorted.length - shown.length;\n\n return (\n <div\n ref={ref}\n className={`devic-int-hint ${className}`.trim()}\n data-dark={dark}\n data-leaving={leaving}\n >\n <button\n type=\"button\"\n className=\"devic-int-hint-main\"\n onClick={onOpen}\n >\n <span className=\"devic-int-hint-label\">{text}</span>\n <span className=\"devic-int-hint-logos\" aria-hidden=\"true\">\n {shown.map((integration) => (\n <span\n key={integration.app}\n className=\"devic-int-hint-item\"\n data-connected={integration.connected}\n >\n <IntegrationLogo\n integration={integration}\n className=\"devic-int-hint-logo\"\n />\n </span>\n ))}\n {extra > 0 && (\n <span className=\"devic-int-hint-item devic-int-hint-more\">\n +{extra}\n </span>\n )}\n </span>\n </button>\n <button\n type=\"button\"\n className=\"devic-int-hint-close\"\n onClick={dismiss}\n aria-label=\"Hide this\"\n title=\"Hide this. Your apps stay in the header.\"\n >\n ×\n </button>\n </div>\n );\n}\n\nexport default IntegrationsHint;\n"],"names":["useMemo","useRef","useState","useEffect","useCallback","_jsxs","_jsx","IntegrationLogo"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IntegrationsHint.js","sources":["../../../../../src/components/IntegrationsModal/IntegrationsHint.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport type { Integration } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsHintProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /** Opens the connected-apps modal. */\n onOpen: () => void;\n /**\n * Text on the left. Defaults to \"Connect your apps\", or \"Explore connected\n * apps\" once at least one is connected — an invitation the user has already\n * accepted stops being an invitation.\n */\n label?: string;\n /** Most logos to show before the `+N` box. @default 6 */\n maxLogos?: number;\n /**\n * Identity the dismissal is remembered against, so closing it for one end\n * user does not close it for the next one on the same browser.\n */\n storageKey?: string;\n /** Where the bar flies to when closed — the header control. */\n flyToSelector?: string;\n /** Light chips for a dark surface. */\n dark?: boolean;\n className?: string;\n}\n\nconst STORAGE_PREFIX = \"devic:apps-hint-dismissed:\";\n\n/** Reading storage throws in Safari's private mode and in sandboxed frames. */\nfunction wasDismissed(key?: string): boolean {\n if (!key) return false;\n try {\n return localStorage.getItem(STORAGE_PREFIX + key) === \"1\";\n } catch {\n return false;\n }\n}\n\nfunction rememberDismissal(key?: string): void {\n if (!key) return;\n try {\n localStorage.setItem(STORAGE_PREFIX + key, \"1\");\n } catch {\n // Then it comes back next session. Not worth a word to the user.\n }\n}\n\n/** Connected first, same order as the header control. */\nfunction order(integrations: Integration[]): Integration[] {\n return [...integrations].sort((a, b) => {\n if (a.connected !== b.connected) return a.connected ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * How many 24px chips fit once the label and the close button are paid for.\n *\n * The label is the whole reason this row exists, so it gets its space first:\n * a strip reading \"Connect your …\" beside seven logos has lost the argument.\n * Never fewer than three chips, below which the row stops reading as a set of\n * apps at all.\n */\nfunction boxesThatFit(width: number, max: number): number {\n if (!width) return max + 1;\n const LABEL_AND_CLOSE = 195;\n return Math.max(3, Math.min(max + 1, Math.floor((width - LABEL_AND_CLOSE) / 30)));\n}\n\n/** Whether an element is on screen right now. */\nfunction isVisible(el: Element): boolean {\n const r = el.getBoundingClientRect();\n if (!r.width || !r.height) return false;\n return (\n r.bottom > 0 &&\n r.right > 0 &&\n r.top < window.innerHeight &&\n r.left < window.innerWidth\n );\n}\n\n/**\n * The header control this bar belongs to.\n *\n * Scoped to its own drawer first — a page can hold several, each with its own\n * header — and only then to the nearest visible one anywhere. The class name is\n * checked against both the drawer's own container and the legacy one, because\n * getting this wrong is silent: `closest` returns null, the search widens to the\n * whole document, and the copy flies to whichever control happens to be first\n * in the DOM.\n */\nfunction flyTarget(bar: Element, selector: string): HTMLElement | null {\n if (!selector) return null;\n\n const drawer = bar.closest(\".devic-chat-drawer, .devic-drawer\");\n const own = drawer?.querySelector(selector) as HTMLElement | null;\n if (own && isVisible(own)) return own;\n\n const barRect = bar.getBoundingClientRect();\n const candidates = [...document.querySelectorAll(selector)].filter(isVisible);\n if (!candidates.length) return null;\n\n // Nearest to the bar, so a page with several drawers still animates towards\n // the one the user is looking at.\n return candidates.reduce((best, el) => {\n const d = (e: Element) => {\n const r = e.getBoundingClientRect();\n return Math.hypot(r.left - barRect.left, r.top - barRect.top);\n };\n return d(el) < d(best) ? el : best;\n }) as HTMLElement;\n}\n\n/**\n * The strip above the composer that tells the end user their own apps can be\n * connected here.\n *\n * It exists because the header control is a row of small logos and nothing\n * else: discoverable once you know what it is, invisible until then. This says\n * it in words, once, and then gets out of the way for good — the dismissal is\n * remembered per end user, and closing it animates towards the header so that\n * what was dismissed is understood to still be there.\n */\nexport function IntegrationsHint({\n state,\n onOpen,\n label,\n maxLogos = 6,\n storageKey,\n flyToSelector = \".devic-int-launcher\",\n dark = false,\n className = \"\",\n}: IntegrationsHintProps): JSX.Element | null {\n const sorted = useMemo(() => order(state.integrations), [state.integrations]);\n const ref = useRef<HTMLDivElement>(null);\n const [boxes, setBoxes] = useState(maxLogos + 1);\n const [dismissed, setDismissed] = useState(() => wasDismissed(storageKey));\n const [leaving, setLeaving] = useState(false);\n\n // A different end user has their own answer to whether this was dismissed.\n useEffect(() => {\n setDismissed(wasDismissed(storageKey));\n setLeaving(false);\n }, [storageKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const measure = () =>\n setBoxes(boxesThatFit(el.getBoundingClientRect().width, maxLogos));\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [maxLogos, state.offered, dismissed]);\n\n /**\n * Sends a copy of the logos from the bar to the header control.\n *\n * A dismissal that simply blanks the row reads as \"that is gone now\". The\n * flight is the sentence \"it moved up there\" said without words — so if it\n * cannot be drawn (reduced motion, no target, no Web Animations API), the bar\n * just closes and nothing is lost.\n */\n const flyToLauncher = useCallback(() => {\n const bar = ref.current;\n if (!bar || typeof document === \"undefined\") return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const target = flyTarget(bar, flyToSelector);\n // Nothing to fly to, or nothing the user can see: the bar just closes.\n // A copy sailing off towards something 2000px down the page says the\n // opposite of what this animation is for.\n if (!target) return;\n\n const logos = bar.querySelector(\".devic-int-hint-logos\");\n const from = (logos ?? bar).getBoundingClientRect();\n const to = target.getBoundingClientRect();\n\n const ghost = (logos ?? bar).cloneNode(true) as HTMLElement;\n ghost.classList.add(\"devic-int-hint-ghost\");\n ghost.style.left = `${from.left}px`;\n ghost.style.top = `${from.top}px`;\n ghost.style.width = `${from.width}px`;\n ghost.style.height = `${from.height}px`;\n document.body.appendChild(ghost);\n\n const dx = to.left + to.width / 2 - (from.left + from.width / 2);\n const dy = to.top + to.height / 2 - (from.top + from.height / 2);\n const scale = from.width ? Math.max(0.4, to.width / from.width) : 0.6;\n\n // An arc, not a straight line. The bow is perpendicular to the path, which\n // is what actually reads as a curve: this trip is nearly vertical, and\n // pulling only on Y just makes it accelerate. It opens away from the\n // target's side so the copy swings out and comes back in, and both terms\n // scale with the distance — a short hop should not loop — and are capped so\n // a long page does not throw it off screen.\n const len = Math.hypot(dx, dy) || 1;\n const bow = Math.min(48, Math.max(16, len * 0.18));\n const side = dx <= 0 ? 1 : -1;\n const bowX = ((-dy / len) * bow) * side;\n const bowY = ((dx / len) * bow) * side;\n const lift = Math.min(70, Math.max(18, len * 0.18));\n\n // The curve the copy travels, as a quadratic Bézier through that midpoint.\n // The control point is derived so the curve actually passes through it —\n // a Bézier does not go through its control.\n const midX = dx * 0.45 + bowX;\n const midY = dy * 0.5 - lift + bowY;\n const ctrlX = 2 * midX - dx / 2;\n const ctrlY = 2 * midY - dy / 2;\n\n // Sampled into many linear steps rather than three keyframes with an easing\n // each. Per-keyframe easings ease OUT into the midpoint and IN out of it,\n // which is a stop halfway — the shape was right and the motion was two\n // hops. Here the keyframes carry only the shape, and one global easing\n // carries the pace: speed builds to a peak at the centre and settles into\n // the target.\n const STEPS = 24;\n // Above the 1.2 of a plain keyframe: this rides on top of the shrink\n // towards the target's size, so the two partly cancel at the apex.\n const PEAK = 1.35;\n const frames = Array.from({ length: STEPS + 1 }, (_, i) => {\n const t = i / STEPS;\n const u = 1 - t;\n return {\n offset: t,\n transform:\n `translate(${2 * u * t * ctrlX + t * t * dx}px, ` +\n `${2 * u * t * ctrlY + t * t * dy}px) ` +\n // Swells on the way and shrinks into the header, so the eye follows\n // the thing rather than the fade.\n `scale(${1 + (scale - 1) * t + (PEAK - 1) * Math.sin(Math.PI * t)})`,\n opacity: t <= 0.5 ? 1 : 1 - Math.pow((t - 0.5) * 2, 1.6),\n easing: \"linear\",\n };\n });\n\n const animation = ghost.animate?.(frames, {\n duration: 780,\n easing: \"cubic-bezier(0.5, 0, 0.5, 1)\",\n fill: \"forwards\",\n });\n\n const remove = () => ghost.remove();\n if (animation) animation.onfinish = remove;\n else remove();\n // Belt and braces: a tab backgrounded mid-flight never fires onfinish.\n window.setTimeout(remove, 2000);\n }, [flyToSelector]);\n\n const dismiss = () => {\n flyToLauncher();\n rememberDismissal(storageKey);\n setLeaving(true);\n window.setTimeout(() => setDismissed(true), 220);\n };\n\n if (dismissed || !state.offered || sorted.length === 0) return null;\n\n const text =\n label ??\n (sorted.some((i) => i.connected)\n ? \"Explore connected apps\"\n : \"Connect your apps\");\n\n // When some apps will not fit, the `+N` box takes one of the slots itself.\n const capacity = Math.max(1, boxes);\n const shown = sorted.slice(\n 0,\n sorted.length > capacity ? capacity - 1 : capacity\n );\n const extra = sorted.length - shown.length;\n\n return (\n <div\n ref={ref}\n className={`devic-int-hint ${className}`.trim()}\n data-dark={dark}\n data-leaving={leaving}\n >\n <button\n type=\"button\"\n className=\"devic-int-hint-main\"\n onClick={onOpen}\n >\n <span className=\"devic-int-hint-label\">{text}</span>\n <span className=\"devic-int-hint-logos\" aria-hidden=\"true\">\n {shown.map((integration) => (\n <span\n key={integration.app}\n className=\"devic-int-hint-item\"\n data-connected={integration.connected}\n >\n <IntegrationLogo\n integration={integration}\n className=\"devic-int-hint-logo\"\n />\n </span>\n ))}\n {extra > 0 && (\n <span className=\"devic-int-hint-item devic-int-hint-more\">\n +{extra}\n </span>\n )}\n </span>\n </button>\n <button\n type=\"button\"\n className=\"devic-int-hint-close\"\n onClick={dismiss}\n aria-label=\"Hide this\"\n title=\"Hide this. Your apps stay in the header.\"\n >\n ×\n </button>\n </div>\n );\n}\n\nexport default IntegrationsHint;\n"],"names":["useMemo","useRef","useState","useEffect","useCallback","_jsxs","_jsx","IntegrationLogo"],"mappings":"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"}
|
|
@@ -163,27 +163,42 @@ function IntegrationsHint({ state, onOpen, label, maxLogos = 6, storageKey, flyT
|
|
|
163
163
|
const bowX = ((-dy / len) * bow) * side;
|
|
164
164
|
const bowY = ((dx / len) * bow) * side;
|
|
165
165
|
const lift = Math.min(70, Math.max(18, len * 0.18));
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
166
|
+
// The curve the copy travels, as a quadratic Bézier through that midpoint.
|
|
167
|
+
// The control point is derived so the curve actually passes through it —
|
|
168
|
+
// a Bézier does not go through its control.
|
|
169
|
+
const midX = dx * 0.45 + bowX;
|
|
170
|
+
const midY = dy * 0.5 - lift + bowY;
|
|
171
|
+
const ctrlX = 2 * midX - dx / 2;
|
|
172
|
+
const ctrlY = 2 * midY - dy / 2;
|
|
173
|
+
// Sampled into many linear steps rather than three keyframes with an easing
|
|
174
|
+
// each. Per-keyframe easings ease OUT into the midpoint and IN out of it,
|
|
175
|
+
// which is a stop halfway — the shape was right and the motion was two
|
|
176
|
+
// hops. Here the keyframes carry only the shape, and one global easing
|
|
177
|
+
// carries the pace: speed builds to a peak at the centre and settles into
|
|
178
|
+
// the target.
|
|
179
|
+
const STEPS = 24;
|
|
180
|
+
// Above the 1.2 of a plain keyframe: this rides on top of the shrink
|
|
181
|
+
// towards the target's size, so the two partly cancel at the apex.
|
|
182
|
+
const PEAK = 1.35;
|
|
183
|
+
const frames = Array.from({ length: STEPS + 1 }, (_, i) => {
|
|
184
|
+
const t = i / STEPS;
|
|
185
|
+
const u = 1 - t;
|
|
186
|
+
return {
|
|
187
|
+
offset: t,
|
|
188
|
+
transform: `translate(${2 * u * t * ctrlX + t * t * dx}px, ` +
|
|
189
|
+
`${2 * u * t * ctrlY + t * t * dy}px) ` +
|
|
190
|
+
// Swells on the way and shrinks into the header, so the eye follows
|
|
191
|
+
// the thing rather than the fade.
|
|
192
|
+
`scale(${1 + (scale - 1) * t + (PEAK - 1) * Math.sin(Math.PI * t)})`,
|
|
193
|
+
opacity: t <= 0.5 ? 1 : 1 - Math.pow((t - 0.5) * 2, 1.6),
|
|
194
|
+
easing: "linear",
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
const animation = ghost.animate?.(frames, {
|
|
198
|
+
duration: 780,
|
|
199
|
+
easing: "cubic-bezier(0.5, 0, 0.5, 1)",
|
|
200
|
+
fill: "forwards",
|
|
201
|
+
});
|
|
187
202
|
const remove = () => ghost.remove();
|
|
188
203
|
if (animation)
|
|
189
204
|
animation.onfinish = remove;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntegrationsHint.js","sources":["../../../../src/components/IntegrationsModal/IntegrationsHint.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport type { Integration } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsHintProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /** Opens the connected-apps modal. */\n onOpen: () => void;\n /**\n * Text on the left. Defaults to \"Connect your apps\", or \"Explore connected\n * apps\" once at least one is connected — an invitation the user has already\n * accepted stops being an invitation.\n */\n label?: string;\n /** Most logos to show before the `+N` box. @default 6 */\n maxLogos?: number;\n /**\n * Identity the dismissal is remembered against, so closing it for one end\n * user does not close it for the next one on the same browser.\n */\n storageKey?: string;\n /** Where the bar flies to when closed — the header control. */\n flyToSelector?: string;\n /** Light chips for a dark surface. */\n dark?: boolean;\n className?: string;\n}\n\nconst STORAGE_PREFIX = \"devic:apps-hint-dismissed:\";\n\n/** Reading storage throws in Safari's private mode and in sandboxed frames. */\nfunction wasDismissed(key?: string): boolean {\n if (!key) return false;\n try {\n return localStorage.getItem(STORAGE_PREFIX + key) === \"1\";\n } catch {\n return false;\n }\n}\n\nfunction rememberDismissal(key?: string): void {\n if (!key) return;\n try {\n localStorage.setItem(STORAGE_PREFIX + key, \"1\");\n } catch {\n // Then it comes back next session. Not worth a word to the user.\n }\n}\n\n/** Connected first, same order as the header control. */\nfunction order(integrations: Integration[]): Integration[] {\n return [...integrations].sort((a, b) => {\n if (a.connected !== b.connected) return a.connected ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * How many 24px chips fit once the label and the close button are paid for.\n *\n * The label is the whole reason this row exists, so it gets its space first:\n * a strip reading \"Connect your …\" beside seven logos has lost the argument.\n * Never fewer than three chips, below which the row stops reading as a set of\n * apps at all.\n */\nfunction boxesThatFit(width: number, max: number): number {\n if (!width) return max + 1;\n const LABEL_AND_CLOSE = 195;\n return Math.max(3, Math.min(max + 1, Math.floor((width - LABEL_AND_CLOSE) / 30)));\n}\n\n/** Whether an element is on screen right now. */\nfunction isVisible(el: Element): boolean {\n const r = el.getBoundingClientRect();\n if (!r.width || !r.height) return false;\n return (\n r.bottom > 0 &&\n r.right > 0 &&\n r.top < window.innerHeight &&\n r.left < window.innerWidth\n );\n}\n\n/**\n * The header control this bar belongs to.\n *\n * Scoped to its own drawer first — a page can hold several, each with its own\n * header — and only then to the nearest visible one anywhere. The class name is\n * checked against both the drawer's own container and the legacy one, because\n * getting this wrong is silent: `closest` returns null, the search widens to the\n * whole document, and the copy flies to whichever control happens to be first\n * in the DOM.\n */\nfunction flyTarget(bar: Element, selector: string): HTMLElement | null {\n if (!selector) return null;\n\n const drawer = bar.closest(\".devic-chat-drawer, .devic-drawer\");\n const own = drawer?.querySelector(selector) as HTMLElement | null;\n if (own && isVisible(own)) return own;\n\n const barRect = bar.getBoundingClientRect();\n const candidates = [...document.querySelectorAll(selector)].filter(isVisible);\n if (!candidates.length) return null;\n\n // Nearest to the bar, so a page with several drawers still animates towards\n // the one the user is looking at.\n return candidates.reduce((best, el) => {\n const d = (e: Element) => {\n const r = e.getBoundingClientRect();\n return Math.hypot(r.left - barRect.left, r.top - barRect.top);\n };\n return d(el) < d(best) ? el : best;\n }) as HTMLElement;\n}\n\n/**\n * The strip above the composer that tells the end user their own apps can be\n * connected here.\n *\n * It exists because the header control is a row of small logos and nothing\n * else: discoverable once you know what it is, invisible until then. This says\n * it in words, once, and then gets out of the way for good — the dismissal is\n * remembered per end user, and closing it animates towards the header so that\n * what was dismissed is understood to still be there.\n */\nexport function IntegrationsHint({\n state,\n onOpen,\n label,\n maxLogos = 6,\n storageKey,\n flyToSelector = \".devic-int-launcher\",\n dark = false,\n className = \"\",\n}: IntegrationsHintProps): JSX.Element | null {\n const sorted = useMemo(() => order(state.integrations), [state.integrations]);\n const ref = useRef<HTMLDivElement>(null);\n const [boxes, setBoxes] = useState(maxLogos + 1);\n const [dismissed, setDismissed] = useState(() => wasDismissed(storageKey));\n const [leaving, setLeaving] = useState(false);\n\n // A different end user has their own answer to whether this was dismissed.\n useEffect(() => {\n setDismissed(wasDismissed(storageKey));\n setLeaving(false);\n }, [storageKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const measure = () =>\n setBoxes(boxesThatFit(el.getBoundingClientRect().width, maxLogos));\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [maxLogos, state.offered, dismissed]);\n\n /**\n * Sends a copy of the logos from the bar to the header control.\n *\n * A dismissal that simply blanks the row reads as \"that is gone now\". The\n * flight is the sentence \"it moved up there\" said without words — so if it\n * cannot be drawn (reduced motion, no target, no Web Animations API), the bar\n * just closes and nothing is lost.\n */\n const flyToLauncher = useCallback(() => {\n const bar = ref.current;\n if (!bar || typeof document === \"undefined\") return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const target = flyTarget(bar, flyToSelector);\n // Nothing to fly to, or nothing the user can see: the bar just closes.\n // A copy sailing off towards something 2000px down the page says the\n // opposite of what this animation is for.\n if (!target) return;\n\n const logos = bar.querySelector(\".devic-int-hint-logos\");\n const from = (logos ?? bar).getBoundingClientRect();\n const to = target.getBoundingClientRect();\n\n const ghost = (logos ?? bar).cloneNode(true) as HTMLElement;\n ghost.classList.add(\"devic-int-hint-ghost\");\n ghost.style.left = `${from.left}px`;\n ghost.style.top = `${from.top}px`;\n ghost.style.width = `${from.width}px`;\n ghost.style.height = `${from.height}px`;\n document.body.appendChild(ghost);\n\n const dx = to.left + to.width / 2 - (from.left + from.width / 2);\n const dy = to.top + to.height / 2 - (from.top + from.height / 2);\n const scale = from.width ? Math.max(0.4, to.width / from.width) : 0.6;\n\n // An arc, not a straight line. The bow is perpendicular to the path, which\n // is what actually reads as a curve: this trip is nearly vertical, and\n // pulling only on Y just makes it accelerate. It opens away from the\n // target's side so the copy swings out and comes back in, and both terms\n // scale with the distance — a short hop should not loop — and are capped so\n // a long page does not throw it off screen.\n const len = Math.hypot(dx, dy) || 1;\n const bow = Math.min(48, Math.max(16, len * 0.18));\n const side = dx <= 0 ? 1 : -1;\n const bowX = ((-dy / len) * bow) * side;\n const bowY = ((dx / len) * bow) * side;\n const lift = Math.min(70, Math.max(18, len * 0.18));\n\n const animation = ghost.animate?.(\n [\n {\n transform: \"translate(0, 0) scale(1)\",\n opacity: 0.95,\n offset: 0,\n easing: \"cubic-bezier(0.22, 0.7, 0.4, 1)\",\n },\n {\n // Bigger in the middle: it grows as it leaves the bar and shrinks\n // into the header, so the eye follows the thing rather than the fade.\n transform: `translate(${dx * 0.45 + bowX}px, ${dy * 0.5 - lift + bowY}px) scale(1.2)`,\n opacity: 1,\n offset: 0.5,\n easing: \"cubic-bezier(0.5, 0, 0.7, 0.9)\",\n },\n {\n transform: `translate(${dx}px, ${dy}px) scale(${scale})`,\n opacity: 0,\n offset: 1,\n },\n ],\n { duration: 780, fill: \"forwards\" }\n );\n\n const remove = () => ghost.remove();\n if (animation) animation.onfinish = remove;\n else remove();\n // Belt and braces: a tab backgrounded mid-flight never fires onfinish.\n window.setTimeout(remove, 2000);\n }, [flyToSelector]);\n\n const dismiss = () => {\n flyToLauncher();\n rememberDismissal(storageKey);\n setLeaving(true);\n window.setTimeout(() => setDismissed(true), 220);\n };\n\n if (dismissed || !state.offered || sorted.length === 0) return null;\n\n const text =\n label ??\n (sorted.some((i) => i.connected)\n ? \"Explore connected apps\"\n : \"Connect your apps\");\n\n // When some apps will not fit, the `+N` box takes one of the slots itself.\n const capacity = Math.max(1, boxes);\n const shown = sorted.slice(\n 0,\n sorted.length > capacity ? capacity - 1 : capacity\n );\n const extra = sorted.length - shown.length;\n\n return (\n <div\n ref={ref}\n className={`devic-int-hint ${className}`.trim()}\n data-dark={dark}\n data-leaving={leaving}\n >\n <button\n type=\"button\"\n className=\"devic-int-hint-main\"\n onClick={onOpen}\n >\n <span className=\"devic-int-hint-label\">{text}</span>\n <span className=\"devic-int-hint-logos\" aria-hidden=\"true\">\n {shown.map((integration) => (\n <span\n key={integration.app}\n className=\"devic-int-hint-item\"\n data-connected={integration.connected}\n >\n <IntegrationLogo\n integration={integration}\n className=\"devic-int-hint-logo\"\n />\n </span>\n ))}\n {extra > 0 && (\n <span className=\"devic-int-hint-item devic-int-hint-more\">\n +{extra}\n </span>\n )}\n </span>\n </button>\n <button\n type=\"button\"\n className=\"devic-int-hint-close\"\n onClick={dismiss}\n aria-label=\"Hide this\"\n title=\"Hide this. Your apps stay in the header.\"\n >\n ×\n </button>\n </div>\n );\n}\n\nexport default IntegrationsHint;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IntegrationsHint.js","sources":["../../../../src/components/IntegrationsModal/IntegrationsHint.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport type { Integration } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsHintProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /** Opens the connected-apps modal. */\n onOpen: () => void;\n /**\n * Text on the left. Defaults to \"Connect your apps\", or \"Explore connected\n * apps\" once at least one is connected — an invitation the user has already\n * accepted stops being an invitation.\n */\n label?: string;\n /** Most logos to show before the `+N` box. @default 6 */\n maxLogos?: number;\n /**\n * Identity the dismissal is remembered against, so closing it for one end\n * user does not close it for the next one on the same browser.\n */\n storageKey?: string;\n /** Where the bar flies to when closed — the header control. */\n flyToSelector?: string;\n /** Light chips for a dark surface. */\n dark?: boolean;\n className?: string;\n}\n\nconst STORAGE_PREFIX = \"devic:apps-hint-dismissed:\";\n\n/** Reading storage throws in Safari's private mode and in sandboxed frames. */\nfunction wasDismissed(key?: string): boolean {\n if (!key) return false;\n try {\n return localStorage.getItem(STORAGE_PREFIX + key) === \"1\";\n } catch {\n return false;\n }\n}\n\nfunction rememberDismissal(key?: string): void {\n if (!key) return;\n try {\n localStorage.setItem(STORAGE_PREFIX + key, \"1\");\n } catch {\n // Then it comes back next session. Not worth a word to the user.\n }\n}\n\n/** Connected first, same order as the header control. */\nfunction order(integrations: Integration[]): Integration[] {\n return [...integrations].sort((a, b) => {\n if (a.connected !== b.connected) return a.connected ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * How many 24px chips fit once the label and the close button are paid for.\n *\n * The label is the whole reason this row exists, so it gets its space first:\n * a strip reading \"Connect your …\" beside seven logos has lost the argument.\n * Never fewer than three chips, below which the row stops reading as a set of\n * apps at all.\n */\nfunction boxesThatFit(width: number, max: number): number {\n if (!width) return max + 1;\n const LABEL_AND_CLOSE = 195;\n return Math.max(3, Math.min(max + 1, Math.floor((width - LABEL_AND_CLOSE) / 30)));\n}\n\n/** Whether an element is on screen right now. */\nfunction isVisible(el: Element): boolean {\n const r = el.getBoundingClientRect();\n if (!r.width || !r.height) return false;\n return (\n r.bottom > 0 &&\n r.right > 0 &&\n r.top < window.innerHeight &&\n r.left < window.innerWidth\n );\n}\n\n/**\n * The header control this bar belongs to.\n *\n * Scoped to its own drawer first — a page can hold several, each with its own\n * header — and only then to the nearest visible one anywhere. The class name is\n * checked against both the drawer's own container and the legacy one, because\n * getting this wrong is silent: `closest` returns null, the search widens to the\n * whole document, and the copy flies to whichever control happens to be first\n * in the DOM.\n */\nfunction flyTarget(bar: Element, selector: string): HTMLElement | null {\n if (!selector) return null;\n\n const drawer = bar.closest(\".devic-chat-drawer, .devic-drawer\");\n const own = drawer?.querySelector(selector) as HTMLElement | null;\n if (own && isVisible(own)) return own;\n\n const barRect = bar.getBoundingClientRect();\n const candidates = [...document.querySelectorAll(selector)].filter(isVisible);\n if (!candidates.length) return null;\n\n // Nearest to the bar, so a page with several drawers still animates towards\n // the one the user is looking at.\n return candidates.reduce((best, el) => {\n const d = (e: Element) => {\n const r = e.getBoundingClientRect();\n return Math.hypot(r.left - barRect.left, r.top - barRect.top);\n };\n return d(el) < d(best) ? el : best;\n }) as HTMLElement;\n}\n\n/**\n * The strip above the composer that tells the end user their own apps can be\n * connected here.\n *\n * It exists because the header control is a row of small logos and nothing\n * else: discoverable once you know what it is, invisible until then. This says\n * it in words, once, and then gets out of the way for good — the dismissal is\n * remembered per end user, and closing it animates towards the header so that\n * what was dismissed is understood to still be there.\n */\nexport function IntegrationsHint({\n state,\n onOpen,\n label,\n maxLogos = 6,\n storageKey,\n flyToSelector = \".devic-int-launcher\",\n dark = false,\n className = \"\",\n}: IntegrationsHintProps): JSX.Element | null {\n const sorted = useMemo(() => order(state.integrations), [state.integrations]);\n const ref = useRef<HTMLDivElement>(null);\n const [boxes, setBoxes] = useState(maxLogos + 1);\n const [dismissed, setDismissed] = useState(() => wasDismissed(storageKey));\n const [leaving, setLeaving] = useState(false);\n\n // A different end user has their own answer to whether this was dismissed.\n useEffect(() => {\n setDismissed(wasDismissed(storageKey));\n setLeaving(false);\n }, [storageKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const measure = () =>\n setBoxes(boxesThatFit(el.getBoundingClientRect().width, maxLogos));\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [maxLogos, state.offered, dismissed]);\n\n /**\n * Sends a copy of the logos from the bar to the header control.\n *\n * A dismissal that simply blanks the row reads as \"that is gone now\". The\n * flight is the sentence \"it moved up there\" said without words — so if it\n * cannot be drawn (reduced motion, no target, no Web Animations API), the bar\n * just closes and nothing is lost.\n */\n const flyToLauncher = useCallback(() => {\n const bar = ref.current;\n if (!bar || typeof document === \"undefined\") return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const target = flyTarget(bar, flyToSelector);\n // Nothing to fly to, or nothing the user can see: the bar just closes.\n // A copy sailing off towards something 2000px down the page says the\n // opposite of what this animation is for.\n if (!target) return;\n\n const logos = bar.querySelector(\".devic-int-hint-logos\");\n const from = (logos ?? bar).getBoundingClientRect();\n const to = target.getBoundingClientRect();\n\n const ghost = (logos ?? bar).cloneNode(true) as HTMLElement;\n ghost.classList.add(\"devic-int-hint-ghost\");\n ghost.style.left = `${from.left}px`;\n ghost.style.top = `${from.top}px`;\n ghost.style.width = `${from.width}px`;\n ghost.style.height = `${from.height}px`;\n document.body.appendChild(ghost);\n\n const dx = to.left + to.width / 2 - (from.left + from.width / 2);\n const dy = to.top + to.height / 2 - (from.top + from.height / 2);\n const scale = from.width ? Math.max(0.4, to.width / from.width) : 0.6;\n\n // An arc, not a straight line. The bow is perpendicular to the path, which\n // is what actually reads as a curve: this trip is nearly vertical, and\n // pulling only on Y just makes it accelerate. It opens away from the\n // target's side so the copy swings out and comes back in, and both terms\n // scale with the distance — a short hop should not loop — and are capped so\n // a long page does not throw it off screen.\n const len = Math.hypot(dx, dy) || 1;\n const bow = Math.min(48, Math.max(16, len * 0.18));\n const side = dx <= 0 ? 1 : -1;\n const bowX = ((-dy / len) * bow) * side;\n const bowY = ((dx / len) * bow) * side;\n const lift = Math.min(70, Math.max(18, len * 0.18));\n\n // The curve the copy travels, as a quadratic Bézier through that midpoint.\n // The control point is derived so the curve actually passes through it —\n // a Bézier does not go through its control.\n const midX = dx * 0.45 + bowX;\n const midY = dy * 0.5 - lift + bowY;\n const ctrlX = 2 * midX - dx / 2;\n const ctrlY = 2 * midY - dy / 2;\n\n // Sampled into many linear steps rather than three keyframes with an easing\n // each. Per-keyframe easings ease OUT into the midpoint and IN out of it,\n // which is a stop halfway — the shape was right and the motion was two\n // hops. Here the keyframes carry only the shape, and one global easing\n // carries the pace: speed builds to a peak at the centre and settles into\n // the target.\n const STEPS = 24;\n // Above the 1.2 of a plain keyframe: this rides on top of the shrink\n // towards the target's size, so the two partly cancel at the apex.\n const PEAK = 1.35;\n const frames = Array.from({ length: STEPS + 1 }, (_, i) => {\n const t = i / STEPS;\n const u = 1 - t;\n return {\n offset: t,\n transform:\n `translate(${2 * u * t * ctrlX + t * t * dx}px, ` +\n `${2 * u * t * ctrlY + t * t * dy}px) ` +\n // Swells on the way and shrinks into the header, so the eye follows\n // the thing rather than the fade.\n `scale(${1 + (scale - 1) * t + (PEAK - 1) * Math.sin(Math.PI * t)})`,\n opacity: t <= 0.5 ? 1 : 1 - Math.pow((t - 0.5) * 2, 1.6),\n easing: \"linear\",\n };\n });\n\n const animation = ghost.animate?.(frames, {\n duration: 780,\n easing: \"cubic-bezier(0.5, 0, 0.5, 1)\",\n fill: \"forwards\",\n });\n\n const remove = () => ghost.remove();\n if (animation) animation.onfinish = remove;\n else remove();\n // Belt and braces: a tab backgrounded mid-flight never fires onfinish.\n window.setTimeout(remove, 2000);\n }, [flyToSelector]);\n\n const dismiss = () => {\n flyToLauncher();\n rememberDismissal(storageKey);\n setLeaving(true);\n window.setTimeout(() => setDismissed(true), 220);\n };\n\n if (dismissed || !state.offered || sorted.length === 0) return null;\n\n const text =\n label ??\n (sorted.some((i) => i.connected)\n ? \"Explore connected apps\"\n : \"Connect your apps\");\n\n // When some apps will not fit, the `+N` box takes one of the slots itself.\n const capacity = Math.max(1, boxes);\n const shown = sorted.slice(\n 0,\n sorted.length > capacity ? capacity - 1 : capacity\n );\n const extra = sorted.length - shown.length;\n\n return (\n <div\n ref={ref}\n className={`devic-int-hint ${className}`.trim()}\n data-dark={dark}\n data-leaving={leaving}\n >\n <button\n type=\"button\"\n className=\"devic-int-hint-main\"\n onClick={onOpen}\n >\n <span className=\"devic-int-hint-label\">{text}</span>\n <span className=\"devic-int-hint-logos\" aria-hidden=\"true\">\n {shown.map((integration) => (\n <span\n key={integration.app}\n className=\"devic-int-hint-item\"\n data-connected={integration.connected}\n >\n <IntegrationLogo\n integration={integration}\n className=\"devic-int-hint-logo\"\n />\n </span>\n ))}\n {extra > 0 && (\n <span className=\"devic-int-hint-item devic-int-hint-more\">\n +{extra}\n </span>\n )}\n </span>\n </button>\n <button\n type=\"button\"\n className=\"devic-int-hint-close\"\n onClick={dismiss}\n aria-label=\"Hide this\"\n title=\"Hide this. Your apps stay in the header.\"\n >\n ×\n </button>\n </div>\n );\n}\n\nexport default IntegrationsHint;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
|