Skip to content

Commit ae610ff

Browse files
committed
Replace default send button
1 parent 4cfe3b2 commit ae610ff

2 files changed

Lines changed: 86 additions & 2 deletions

File tree

Lines changed: 10 additions & 0 deletions
Loading

src/hooks/use-zendesk-iframe-styles.ts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
DEFAULT_MAX_RETRIES,
1111
} from '@/constants/zendesk-timing';
1212
import { setupZendeskObserver } from '@/utils/zendesk-observer';
13+
import { ZENDESK_SEND_BUTTON_SELECTOR } from '@/constants/zendesk-selectors';
1314

1415
interface UseZendeskIframeStylesOptions {
1516
zendeskReady: boolean;
@@ -47,7 +48,12 @@ export function useZendeskIframeStyles({
4748
((retries?: number, delay?: number) => void) | null
4849
>(null);
4950
const observerRef = useRef<MutationObserver | null>(null);
51+
const buttonObserverRef = useRef<MutationObserver | null>(null);
5052
const isMountedRef = useRef(true);
53+
// Track buttons that have already had their SVG replaced to avoid duplicates
54+
const replacedButtonsRef = useRef<WeakSet<HTMLElement>>(
55+
new WeakSet<HTMLElement>(),
56+
);
5157

5258
// Injects styles into the Zendesk iframe document. Retries if iframe is not
5359
// available yet.
@@ -114,6 +120,45 @@ export function useZendeskIframeStyles({
114120
[styles],
115121
);
116122

123+
// Replaces the SVG in the send button with a custom icon
124+
const replaceSendButtonSvg = useCallback((iframeDoc: Document): void => {
125+
try {
126+
// Find the send button using the proper selector
127+
const sendButton = iframeDoc.querySelector<HTMLElement>(
128+
ZENDESK_SEND_BUTTON_SELECTOR,
129+
);
130+
131+
if (!sendButton) {
132+
return;
133+
}
134+
135+
// Skip if we've already replaced this button's SVG
136+
if (replacedButtonsRef.current.has(sendButton)) {
137+
return;
138+
}
139+
140+
const svg = sendButton.querySelector('svg');
141+
142+
if (svg) {
143+
sendButton.dataset.customized = 'true';
144+
145+
// Replace with external SVG file
146+
svg.outerHTML = `
147+
<img src="static-assets/images/Send-Plane-Solid-16.svg"
148+
width="20"
149+
height="20"
150+
alt="Send"
151+
style="display: block;" />
152+
`;
153+
154+
// Mark this button as replaced
155+
replacedButtonsRef.current.add(sendButton);
156+
}
157+
} catch (error) {
158+
window.fireJse?.(error);
159+
}
160+
}, []);
161+
117162
// Store `injectStyles` in a ref to enable recursive calls. ESLint flags
118163
// self-referential calls within useCallback
119164
useEffect(() => {
@@ -142,13 +187,15 @@ export function useZendeskIframeStyles({
142187

143188
// Set up MutationObserver to re-inject styles if iframe reloads
144189
let observerCleanup: (() => void) | null = null;
190+
let buttonObserverCleanup: (() => void) | null = null;
145191

146192
// Set up observer after a delay to ensure iframe is ready
147193
observerTimeout = setTimeout(() => {
148194
if (!isMountedRef.current) {
149195
return;
150196
}
151197

198+
// Observer for style injection (watches head)
152199
observerCleanup = setupZendeskObserver({
153200
onMutation: (iframeDoc) => {
154201
// Check if style element was removed (iframe reloaded)
@@ -165,6 +212,23 @@ export function useZendeskIframeStyles({
165212
isMountedRef,
166213
observerRef,
167214
});
215+
216+
// Observer for send button SVG replacement (watches body)
217+
// The send button appears dynamically when user clicks in textarea
218+
buttonObserverCleanup = setupZendeskObserver({
219+
onMutation: (iframeDoc) => {
220+
// Try to replace SVG whenever DOM mutations occur
221+
replaceSendButtonSvg(iframeDoc);
222+
},
223+
onSetup: (iframeDoc) => {
224+
// Try immediately in case button already exists
225+
replaceSendButtonSvg(iframeDoc);
226+
},
227+
target: 'body',
228+
retryOnNotReady: false,
229+
isMountedRef,
230+
observerRef: buttonObserverRef,
231+
});
168232
}, OBSERVER_SETUP_DELAY_MS);
169233

170234
return () => {
@@ -180,16 +244,26 @@ export function useZendeskIframeStyles({
180244
clearTimeout(observerTimeout);
181245
}
182246

183-
// Clean up observer
247+
// Clean up observers
184248
if (observerCleanup) {
185249
observerCleanup();
186250
}
187251

252+
if (buttonObserverCleanup) {
253+
buttonObserverCleanup();
254+
}
255+
188256
if (observerRef.current) {
189257
observerRef.current.disconnect();
190258

191259
observerRef.current = null;
192260
}
261+
262+
if (buttonObserverRef.current) {
263+
buttonObserverRef.current.disconnect();
264+
265+
buttonObserverRef.current = null;
266+
}
193267
};
194-
}, [zendeskReady, styles, injectStyles]);
268+
}, [zendeskReady, styles, injectStyles, replaceSendButtonSvg]);
195269
}

0 commit comments

Comments
 (0)