Skip to content

Commit 7a2cd0b

Browse files
committed
ref: extract widget creation to function, allow handling of multiple widgets
1 parent 6ef6f29 commit 7a2cd0b

File tree

10 files changed

+427
-262
lines changed

10 files changed

+427
-262
lines changed

packages/feedback/src/integration.ts

Lines changed: 118 additions & 247 deletions
Large diffs are not rendered by default.

packages/feedback/src/sendFeedback.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { BrowserClient, Replay } from '@sentry/browser';
22
import { getCurrentHub } from '@sentry/core';
3+
import { getLocationHref } from '@sentry/utils';
34

45
import { sendFeedbackRequest } from './util/sendFeedbackRequest';
56

@@ -18,7 +19,7 @@ interface SendFeedbackOptions {
1819
* Public API to send a Feedback item to Sentry
1920
*/
2021
export function sendFeedback(
21-
{ name, email, message, url = document.location.href }: SendFeedbackParams,
22+
{ name, email, message, url = getLocationHref() }: SendFeedbackParams,
2223
{ includeReplay = true }: SendFeedbackOptions = {},
2324
): ReturnType<typeof sendFeedbackRequest> {
2425
const hub = getCurrentHub();

packages/feedback/src/types/index.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { Event, Primitive } from '@sentry/types';
22

3+
import type { ActorComponent } from '../widget/Actor';
4+
import type { DialogComponent } from '../widget/Dialog';
5+
36
export type SentryTags = { [key: string]: Primitive } | undefined;
47

58
/**
@@ -40,11 +43,6 @@ export interface FeedbackConfigurationWithDefaults {
4043
*/
4144
id: string;
4245

43-
/**
44-
* DOM Selector to attach click listener to, for opening Feedback dialog.
45-
*/
46-
attachTo: Node | string | null;
47-
4846
/**
4947
* Auto-inject default Feedback actor button to the DOM when integration is
5048
* added.
@@ -92,12 +90,13 @@ export interface FeedbackConfigurationWithDefaults {
9290
colorScheme: 'system' | 'light' | 'dark';
9391

9492
/**
95-
* Theme customization, will be merged with default theme values.
93+
* Light theme customization, will be merged with default theme values.
9694
*/
97-
theme: {
98-
dark: FeedbackTheme;
99-
light: FeedbackTheme;
100-
};
95+
themeLight: FeedbackTheme;
96+
/**
97+
* Dark theme customization, will be merged with default theme values.
98+
*/
99+
themeDark: FeedbackTheme;
101100
// * End of Color theme customization * //
102101

103102
// * Text customization * //
@@ -148,6 +147,11 @@ export interface FeedbackConfigurationWithDefaults {
148147
// * End of text customization * //
149148

150149
// * Start of Callbacks * //
150+
/**
151+
* Callback when dialog is closed
152+
*/
153+
onDialogClose?: () => void;
154+
151155
/**
152156
* Callback when dialog is opened
153157
*/
@@ -217,3 +221,22 @@ export interface FeedbackThemes {
217221
export interface FeedbackComponent<T extends HTMLElement> {
218222
$el: T;
219223
}
224+
225+
/**
226+
* A widget consists of:
227+
* - actor button [that opens dialog]
228+
* - dialog + feedback form
229+
* - shadow root?
230+
*/
231+
export interface Widget {
232+
actor: ActorComponent | undefined;
233+
dialog: DialogComponent | undefined;
234+
235+
showActor: () => void;
236+
hideActor: () => void;
237+
removeActor: () => void;
238+
239+
openDialog: () => void;
240+
hideDialog: () => void;
241+
removeDialog: () => void;
242+
}

packages/feedback/src/widget/Actor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface Props {
77
onClick?: (e: MouseEvent) => void;
88
}
99

10-
interface ActorComponent extends FeedbackComponent<HTMLButtonElement> {
10+
export interface ActorComponent extends FeedbackComponent<HTMLButtonElement> {
1111
/**
1212
* Shows the actor element
1313
*/

packages/feedback/src/widget/Dialog.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export interface DialogComponent extends FeedbackComponent<HTMLDialogElement> {
4141
* Closes the dialog and form
4242
*/
4343
close: () => void;
44+
45+
/**
46+
* Check if dialog is currently opened
47+
*/
48+
checkIsOpen: () => boolean;
4449
}
4550

4651
/**
@@ -87,6 +92,13 @@ export function Dialog({
8792
}
8893
}
8994

95+
/**
96+
* Check if dialog is currently opened
97+
*/
98+
function checkIsOpen(): boolean {
99+
return ($el && $el.open === true) || false;
100+
}
101+
90102
const {
91103
$el: $form,
92104
setSubmitEnabled,
@@ -104,7 +116,6 @@ export function Dialog({
104116
$el = h(
105117
'dialog',
106118
{
107-
id: 'feedback-dialog',
108119
className: 'dialog',
109120
open: true,
110121
onClick: handleDialogClick,
@@ -131,5 +142,6 @@ export function Dialog({
131142
setSubmitEnabled,
132143
open,
133144
close,
145+
checkIsOpen,
134146
};
135147
}

packages/feedback/src/widget/Icon.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ interface IconReturn {
1111
* Feedback Icon
1212
*/
1313
export function Icon(): IconReturn {
14-
const cENS = document.createElementNS.bind(document, XMLNS);
14+
const cENS = <K extends keyof SVGElementTagNameMap>(tagName: K): SVGElementTagNameMap[K] =>
15+
// eslint-disable-next-line no-restricted-globals
16+
document.createElementNS(XMLNS, tagName);
1517
const svg = setAttributesNS(cENS('svg'), {
1618
class: 'feedback-icon',
1719
width: `${SIZE}`,

packages/feedback/src/widget/SuccessIcon.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ interface IconReturn {
1212
* Success Icon (checkmark)
1313
*/
1414
export function SuccessIcon(): IconReturn {
15-
const cENS = document.createElementNS.bind(document, XMLNS);
15+
const cENS = <K extends keyof SVGElementTagNameMap>(tagName: K): SVGElementTagNameMap[K] =>
16+
// eslint-disable-next-line no-restricted-globals
17+
document.createElementNS(XMLNS, tagName);
1618
const svg = setAttributesNS(cENS('svg'), {
1719
class: 'success-icon',
1820
width: `${WIDTH}`,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { logger } from '@sentry/utils';
2+
3+
import type { FeedbackConfigurationWithDefaults } from '../types';
4+
import { createDialogStyles } from './Dialog.css';
5+
import { createMainStyles } from './Main.css';
6+
7+
interface CreateShadowHostParams {
8+
options: FeedbackConfigurationWithDefaults;
9+
}
10+
/**
11+
*
12+
*/
13+
export function createShadowHost({ options }: CreateShadowHostParams): [shadow: ShadowRoot, host: HTMLDivElement] {
14+
// eslint-disable-next-line no-restricted-globals
15+
const doc = document;
16+
if (!doc.head.attachShadow) {
17+
// Shadow DOM not supported
18+
logger.warn('[Feedback] Browser does not support shadow DOM API');
19+
throw new Error('Browser does not support shadow DOM API.');
20+
}
21+
22+
// Create the host
23+
const host = doc.createElement('div');
24+
host.id = options.id;
25+
26+
// Create the shadow root
27+
const shadow = host.attachShadow({ mode: 'open' });
28+
29+
shadow.appendChild(
30+
createMainStyles(doc, options.colorScheme, { dark: options.themeDark, light: options.themeLight }),
31+
);
32+
shadow.appendChild(createDialogStyles(doc));
33+
34+
return [shadow, host];
35+
}

0 commit comments

Comments
 (0)