Skip to content

Commit ca2f93b

Browse files
authored
ENG-592 Replace Send feedback button with a FAB (Floating Action Button) containing DG logo (#337)
Create a floating button with a submenu. Trigger the BirdEatsBugs feedback as a menu element.
1 parent db34eb8 commit ca2f93b

File tree

5 files changed

+425
-56
lines changed

5 files changed

+425
-56
lines changed

apps/roam/src/components/BirdEatsBugs.tsx

Lines changed: 14 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import getCurrentUserEmail from "roamjs-components/queries/getCurrentUserEmail";
2-
import { OnloadArgs } from "roamjs-components/types";
2+
3+
// Option types detailed in https://docs.birdeatsbug.com/latest/sdk/options.html
34

45
export type FeedbackWidget = {
56
initialize?: boolean;
@@ -15,9 +16,11 @@ export type FeedbackWidget = {
1516
publicAppId: string;
1617
ui?: {
1718
position?: string;
18-
defaultButton?: {
19-
icon?: string;
20-
};
19+
defaultButton?:
20+
| {
21+
icon?: string;
22+
}
23+
| boolean;
2124
text?: {
2225
defaultButton?: string;
2326
previewScreen?: {
@@ -42,41 +45,7 @@ declare global {
4245
}
4346
}
4447

45-
const STYLE_ID = "feedback-button-hiding-styles";
46-
47-
const addFeedbackButtonHidingStyles = () => {
48-
if (document.getElementById(STYLE_ID)) {
49-
return;
50-
}
51-
52-
const styleElement = document.createElement("style");
53-
styleElement.id = STYLE_ID;
54-
styleElement.textContent = `
55-
#birdeatsbug-default-button {
56-
display: none !important;
57-
}
58-
`;
59-
60-
document.head.appendChild(styleElement);
61-
};
62-
63-
const removeFeedbackButtonHidingStyles = () => {
64-
const styleElement = document.getElementById(STYLE_ID);
65-
if (styleElement) {
66-
styleElement.remove();
67-
}
68-
};
69-
70-
export const initFeedbackWidget = (
71-
extensionAPI: OnloadArgs["extensionAPI"],
72-
): void => {
73-
if (extensionAPI.settings.get("hide-feedback-button") as boolean) {
74-
addFeedbackButtonHidingStyles();
75-
return;
76-
}
77-
78-
removeFeedbackButtonHidingStyles();
79-
48+
export const initFeedbackWidget = (): void => {
8049
const birdeatsbug = (window.birdeatsbug =
8150
window.birdeatsbug || []) as FeedbackWidget;
8251

@@ -141,22 +110,22 @@ export const initFeedbackWidget = (
141110

142111
const customStyles = document.createElement("style");
143112
customStyles.textContent = `
144-
113+
145114
#birdeatsbug-sdk {
146115
--distance-to-window-edge-vertical: 50px;
147116
--distance-to-window-edge-horizontal: 20px;
148117
}
149-
118+
150119
#birdeatsbug-sdk .form-error {
151120
font-size: 1.2rem;
152121
}
153-
122+
154123
#birdeatsbug-sdk:has(.screen) {
155124
box-shadow: none !important;
156125
border-radius: 0 !important;
157126
border: none !important;
158127
}
159-
128+
160129
#birdeatsbug-sdk.dark {
161130
--button-primary-bg-color: #1976d2;
162131
}
@@ -180,7 +149,7 @@ export const initFeedbackWidget = (
180149
181150
#birdeatsbug-sdk .caret {
182151
height: initial;
183-
width: initial;
152+
width: initial;
184153
border-top: initial;
185154
}
186155
`;
@@ -195,9 +164,8 @@ export const initFeedbackWidget = (
195164
},
196165
ui: {
197166
position: "bottom-right",
198-
defaultButton: { icon: undefined },
167+
defaultButton: false, // hide, will be triggered in DiscourseFloatingMenu
199168
text: {
200-
defaultButton: "Send feedback",
201169
previewScreen: {
202170
title: "Discourse Graphs feedback",
203171
},
@@ -211,6 +179,3 @@ export const initFeedbackWidget = (
211179
});
212180
}
213181
};
214-
215-
export const hideFeedbackButton = addFeedbackButtonHidingStyles;
216-
export const showFeedbackButton = removeFeedbackButtonHidingStyles;
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { OnloadArgs } from "roamjs-components/types";
4+
import {
5+
Popover,
6+
Menu,
7+
MenuItem,
8+
Button,
9+
Intent,
10+
Position,
11+
PopoverInteractionKind,
12+
} from "@blueprintjs/core";
13+
import { FeedbackWidget } from "./BirdEatsBugs";
14+
15+
type DiscourseFloatingMenuProps = {
16+
// CSS placement class
17+
position: "top-left" | "top-right" | "bottom-left" | "bottom-right";
18+
theme: string; // e.g., "bp3-light" | "bp3-dark"
19+
buttonTheme?: string; // e.g., "bp3-light" | "bp3-dark"
20+
};
21+
22+
const ANCHOR_ID = "dg-floating-menu-anchor";
23+
24+
export const DiscourseFloatingMenu = (props: DiscourseFloatingMenuProps) => (
25+
<div
26+
id="discourse-floating-menu"
27+
className={`${props.position} ${props.theme}`}
28+
>
29+
<Popover
30+
autoFocus={false}
31+
content={
32+
<Menu>
33+
<MenuItem
34+
text="Send feedback"
35+
icon="send-message"
36+
onClick={() => {
37+
try {
38+
(window.birdeatsbug as FeedbackWidget | undefined)?.trigger?.();
39+
} catch (error) {
40+
console.error("Failed to trigger feedback widget:", error);
41+
}
42+
}}
43+
/>
44+
<MenuItem
45+
text="Docs"
46+
icon="document-open"
47+
href="https://discoursegraphs.com/docs/roam"
48+
rel="noopener noreferrer"
49+
target="_blank"
50+
/>
51+
<MenuItem
52+
text="Community"
53+
icon="people"
54+
href="https://join.slack.com/t/discoursegraphs/shared_invite/zt-37xklatti-cpEjgPQC0YyKYQWPNgAkEg"
55+
rel="noopener noreferrer"
56+
target="_blank"
57+
/>
58+
</Menu>
59+
}
60+
position={Position.TOP}
61+
className="bp3-popover-content-sizing"
62+
interactionKind={PopoverInteractionKind.HOVER}
63+
boundary="viewport"
64+
modifiers={{
65+
arrow: {
66+
enabled: false,
67+
},
68+
}}
69+
>
70+
<Button
71+
intent={Intent.PRIMARY}
72+
text="Discourse Graphs"
73+
id="dg-floating-menu-button"
74+
aria-label="Open Discourse Graphs menu"
75+
className={props.buttonTheme}
76+
/>
77+
</Popover>
78+
</div>
79+
);
80+
81+
export const hideDiscourseFloatingMenu = () => {
82+
const anchor = document.getElementById(ANCHOR_ID);
83+
anchor?.classList.add("hidden");
84+
};
85+
86+
export const showDiscourseFloatingMenu = () => {
87+
const anchor = document.getElementById(ANCHOR_ID);
88+
anchor?.classList.remove("hidden");
89+
};
90+
91+
export const installDiscourseFloatingMenu = (
92+
extensionAPI: OnloadArgs["extensionAPI"],
93+
props: DiscourseFloatingMenuProps = {
94+
position: "bottom-right",
95+
theme: "bp3-light",
96+
buttonTheme: "bp3-dark",
97+
},
98+
) => {
99+
let floatingMenuAnchor = document.getElementById(ANCHOR_ID);
100+
if (!floatingMenuAnchor) {
101+
floatingMenuAnchor = document.createElement("div");
102+
floatingMenuAnchor.id = ANCHOR_ID;
103+
document.getElementById("app")?.appendChild(floatingMenuAnchor);
104+
}
105+
if (extensionAPI.settings.get("hide-feedback-button") as boolean) {
106+
floatingMenuAnchor.classList.add("hidden");
107+
}
108+
ReactDOM.render(
109+
<DiscourseFloatingMenu
110+
position={props.position}
111+
theme={props.theme}
112+
buttonTheme={props.buttonTheme}
113+
/>,
114+
floatingMenuAnchor,
115+
);
116+
};
117+
118+
export const removeDiscourseFloatingMenu = () => {
119+
const anchor = document.getElementById(ANCHOR_ID);
120+
if (anchor) {
121+
try {
122+
ReactDOM.unmountComponentAtNode(anchor);
123+
} catch (e) {
124+
// no-op: unmount best-effort
125+
}
126+
anchor.remove();
127+
}
128+
};

apps/roam/src/components/settings/HomePersonalSettings.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import {
99
previewPageRefHandler,
1010
} from "~/utils/pageRefObserverHandlers";
1111
import {
12-
hideFeedbackButton,
13-
showFeedbackButton,
14-
} from "~/components/BirdEatsBugs";
12+
showDiscourseFloatingMenu,
13+
hideDiscourseFloatingMenu,
14+
} from "~/components/DiscourseFloatingMenu";
1515
import { NodeSearchMenuTriggerSetting } from "../DiscourseNodeSearchMenu";
1616

1717
const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => {
@@ -127,9 +127,9 @@ const HomePersonalSettings = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => {
127127
extensionAPI.settings.set("hide-feedback-button", target.checked);
128128

129129
if (target.checked) {
130-
hideFeedbackButton();
130+
hideDiscourseFloatingMenu();
131131
} else {
132-
showFeedbackButton();
132+
showDiscourseFloatingMenu();
133133
}
134134
}}
135135
labelElement={

apps/roam/src/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@ import { addGraphViewNodeStyling } from "./utils/graphViewNodeStyling";
1717
import { setQueryPages } from "./utils/setQueryPages";
1818
import initializeDiscourseNodes from "./utils/initializeDiscourseNodes";
1919
import styles from "./styles/styles.css";
20+
import discourseFloatingMenuStyles from "./styles/discourseFloatingMenuStyles.css";
2021
import settingsStyles from "./styles/settingsStyles.css";
2122
import discourseGraphStyles from "./styles/discourseGraphStyles.css";
2223
import posthog from "posthog-js";
2324
import getDiscourseNodes from "./utils/getDiscourseNodes";
2425
import { initFeedbackWidget } from "./components/BirdEatsBugs";
26+
import {
27+
installDiscourseFloatingMenu,
28+
removeDiscourseFloatingMenu,
29+
} from "./components/DiscourseFloatingMenu";
2530

2631
const initPostHog = () => {
2732
posthog.init("phc_SNMmBqwNfcEpNduQ41dBUjtGNEUEKAy6jTn63Fzsrax", {
@@ -65,7 +70,7 @@ export default runExtension(async (onloadArgs) => {
6570
});
6671
}
6772

68-
initFeedbackWidget(onloadArgs.extensionAPI);
73+
initFeedbackWidget();
6974

7075
if (window?.roamjs?.loaded?.has("query-builder")) {
7176
renderToast({
@@ -94,6 +99,7 @@ export default runExtension(async (onloadArgs) => {
9499
const style = addStyle(styles);
95100
const discourseGraphStyle = addStyle(discourseGraphStyles);
96101
const settingsStyle = addStyle(settingsStyles);
102+
const discourseFloatingMenuStyle = addStyle(discourseFloatingMenuStyles);
97103

98104
const { observers, listeners } = await initObservers({ onloadArgs });
99105
const {
@@ -128,8 +134,15 @@ export default runExtension(async (onloadArgs) => {
128134
getDiscourseNodes: getDiscourseNodes,
129135
};
130136

137+
installDiscourseFloatingMenu(onloadArgs.extensionAPI);
138+
131139
return {
132-
elements: [style, settingsStyle, discourseGraphStyle],
140+
elements: [
141+
style,
142+
settingsStyle,
143+
discourseGraphStyle,
144+
discourseFloatingMenuStyle,
145+
],
133146
observers: observers,
134147
unload: () => {
135148
window.roamjs.extension?.smartblocks?.unregisterCommand("QUERYBUILDER");
@@ -146,6 +159,7 @@ export default runExtension(async (onloadArgs) => {
146159
"selectionchange",
147160
nodeCreationPopoverListener,
148161
);
162+
removeDiscourseFloatingMenu();
149163
window.roamAlphaAPI.ui.graphView.wholeGraph.removeCallback({
150164
label: "discourse-node-styling",
151165
});

0 commit comments

Comments
 (0)