Skip to content

Commit 8cd55bf

Browse files
authored
fix: Modal positioning to depend only on the icon's position
1 parent 1e50146 commit 8cd55bf

File tree

10 files changed

+392
-159
lines changed

10 files changed

+392
-159
lines changed

src/components/SirenInbox.tsx

Lines changed: 77 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import NotificationButton from "./SirenNotificationIcon";
44
import SirenPanel from "./SirenPanel";
55
import { useSirenContext } from "./SirenProvider";
66
import type { SirenProps } from "../types";
7-
import { applyTheme, calculateModalPosition } from "../utils/commonUtils";
7+
import { DefaultStyle } from "../utils";
8+
import {
9+
applyTheme,
10+
calculateModalPosition,
11+
calculateModalWidth,
12+
debounce,
13+
} from "../utils/commonUtils";
814
import {
915
BadgeType,
1016
MAXIMUM_ITEMS_PER_FETCH,
@@ -68,10 +74,12 @@ const SirenInbox: FC<SirenProps> = ({
6874
//ref for the modal
6975
const modalRef = useRef<HTMLDivElement>(null);
7076
const [modalPosition, setModalPosition] = useState<{
71-
top: string;
7277
right?: string;
73-
}>({ top: "0" });
74-
78+
left?: string;
79+
}>();
80+
const initialModalWidth =
81+
customStyles?.window?.width || DefaultStyle.window.width;
82+
const [updatedModalWidth, setUpdatedModalWidth] = useState(initialModalWidth);
7583
const styles = useMemo(
7684
() =>
7785
applyTheme(
@@ -96,18 +104,26 @@ const SirenInbox: FC<SirenProps> = ({
96104
}, []);
97105

98106
useEffect(() => {
99-
const updateModalPosition = () => {
100-
const containerWidth = styles.container.width || 400;
107+
const modalWidth = calculateModalWidth(initialModalWidth);
101108

109+
if (window.outerWidth <= modalWidth)
110+
// Subtract 40 pixels to account for padding within the window container
111+
setUpdatedModalWidth(window.outerWidth - 40);
112+
else setUpdatedModalWidth(initialModalWidth);
113+
}, [window.outerWidth, initialModalWidth]);
114+
115+
useEffect(() => {
116+
const containerWidth = styles.container.width || DefaultStyle.window.width;
117+
const updateWindowViewMode = () => {
102118
setModalPosition(calculateModalPosition(iconRef, window, containerWidth));
103119
};
104120

105-
updateModalPosition(); // Initial calculation
106-
window.addEventListener("resize", updateModalPosition); // Event listener for window resize
121+
const debouncedUpdate = debounce(updateWindowViewMode, 200);
107122

108-
return () => {
109-
window.removeEventListener("resize", updateModalPosition);
110-
};
123+
updateWindowViewMode();
124+
window.addEventListener("resize", debouncedUpdate);
125+
126+
return () => window.removeEventListener("resize", debouncedUpdate);
111127
}, [styles.container.width]);
112128

113129
const onMouseUp = (event: Event): void => {
@@ -124,59 +140,56 @@ const SirenInbox: FC<SirenProps> = ({
124140
};
125141

126142
return (
127-
<div
128-
ref={modalRef}
129-
className={`${!windowViewOnly && "siren-sdk-inbox-container"}`}
130-
>
131-
{!windowViewOnly && (
132-
<div ref={iconRef}>
133-
<NotificationButton
134-
notificationIcon={notificationIcon}
135-
styles={styles}
136-
onIconClick={onIconClick}
137-
badgeType={isModalOpen ? BadgeType.NONE : BadgeType.DEFAULT}
138-
darkMode={darkMode}
139-
hideBadge={hideBadge}
140-
/>
141-
</div>
142-
)}
143-
144-
{(isModalOpen || windowViewOnly) && (
145-
<div
146-
style={{
147-
...styles.container,
148-
...(!windowViewOnly && styles.windowShadow),
149-
width:
150-
windowViewOnly || window.innerWidth < 500
151-
? "100%"
152-
: styles.container.width,
153-
position:
154-
windowViewOnly || window.innerWidth < 500
155-
? "initial"
156-
: "absolute",
157-
...modalPosition,
158-
}}
159-
data-testid="siren-panel"
160-
>
161-
<SirenPanel
162-
styles={styles}
163-
noOfNotificationsPerFetch={notificationsPerPage}
164-
hideBadge={hideBadge}
165-
inboxHeaderProps={inboxHeaderProps}
166-
cardProps={cardProps}
167-
customFooter={customFooter}
168-
customNotificationCard={customNotificationCard}
169-
onNotificationCardClick={onNotificationCardClick}
170-
onError={onError}
171-
listEmptyComponent={listEmptyComponent}
172-
fullScreen={windowViewOnly}
173-
customLoader={customLoader}
174-
loadMoreComponent={loadMoreComponent}
175-
darkMode={darkMode}
176-
customErrorWindow={customErrorWindow}
177-
/>
178-
</div>
179-
)}
143+
<div className={!windowViewOnly? 'siren-sdk-inbox-root' : ''}>
144+
<div
145+
ref={modalRef}
146+
className={`${!windowViewOnly && "siren-sdk-inbox-container"}`}
147+
>
148+
{!windowViewOnly && (
149+
<div ref={iconRef}>
150+
<NotificationButton
151+
notificationIcon={notificationIcon}
152+
styles={styles}
153+
onIconClick={onIconClick}
154+
badgeType={isModalOpen ? BadgeType.NONE : BadgeType.DEFAULT}
155+
darkMode={darkMode}
156+
hideBadge={hideBadge}
157+
/>
158+
</div>
159+
)}
160+
161+
{(isModalOpen || windowViewOnly) && (
162+
<div
163+
style={{
164+
...styles.container,
165+
...(!windowViewOnly && styles.windowShadow),
166+
position: windowViewOnly ? "initial" : "absolute",
167+
width: windowViewOnly ? "100%" : updatedModalWidth,
168+
...modalPosition,
169+
}}
170+
data-testid="siren-panel"
171+
>
172+
<SirenPanel
173+
styles={styles}
174+
noOfNotificationsPerFetch={notificationsPerPage}
175+
hideBadge={hideBadge}
176+
inboxHeaderProps={inboxHeaderProps}
177+
cardProps={cardProps}
178+
customFooter={customFooter}
179+
customNotificationCard={customNotificationCard}
180+
onNotificationCardClick={onNotificationCardClick}
181+
onError={onError}
182+
listEmptyComponent={listEmptyComponent}
183+
fullScreen={windowViewOnly}
184+
customLoader={customLoader}
185+
loadMoreComponent={loadMoreComponent}
186+
darkMode={darkMode}
187+
customErrorWindow={customErrorWindow}
188+
modalWidth={updatedModalWidth}
189+
/>
190+
</div>
191+
)}
192+
</div>
180193
</div>
181194
);
182195
};

src/components/SirenPanel.tsx

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ import useSiren from "../utils/sirenHook";
5353
* @param {Function} props.renderListEmpty - Function to render content when the notification list is empty.
5454
* @param {ReactNode} props.customFooter - Custom footer component to be rendered below the notification list.
5555
* @param {ReactNode} pros.customLoader - Custom Loader component to be rendered while fetching notification list for the first time
56-
* @param {ReactNode} pros.loadMoreComponent -Custom load more component to be rendered
56+
* @param {ReactNode} pros.loadMoreComponent -Custom load more component to be rendered
5757
* @param {ReactNode} props.customErrorWindow -Custom error window component to be rendered when there is an error
5858
* @param {Function} props.customNotificationCard - Function to render custom notification cards.
5959
* @param {Function} props.onNotificationCardClick - Callback function executed when a notification card is clicked.
60+
* @param {DimensionValue} props.modalWidth - The width of the notification panel.
6061
* @returns {ReactElement} The rendered SirenInbox component.
6162
*/
6263

@@ -84,6 +85,7 @@ const SirenPanel: FC<SirenPanelProps> = ({
8485
customNotificationCard,
8586
onNotificationCardClick,
8687
onError,
88+
modalWidth,
8789
}) => {
8890
const {
8991
markNotificationsAsViewed,
@@ -97,7 +99,7 @@ const SirenPanel: FC<SirenPanelProps> = ({
9799
);
98100

99101
const [error, setError] = useState<string>("");
100-
const [isLoading, setIsLoading] = useState(false);
102+
const [isLoading, setIsLoading] = useState(true);
101103
const [endReached, setEndReached] = useState(false);
102104
const [eventListenerData, setEventListenerData] =
103105
useState<EventListenerDataType | null>(null);
@@ -139,7 +141,6 @@ const SirenPanel: FC<SirenPanelProps> = ({
139141
}
140142
}, [siren, verificationStatus, hideBadge]);
141143

142-
143144
const restartNotificationCountFetch = () => {
144145
try {
145146
siren?.startRealTimeUnviewedCountFetch();
@@ -173,7 +174,7 @@ const SirenPanel: FC<SirenPanelProps> = ({
173174
const response = await deleteNotificationsByDate(
174175
notifications[0].createdAt
175176
);
176-
177+
177178
response && triggerOnError(response);
178179

179180
if (response && isValidResponse(response)) {
@@ -197,7 +198,8 @@ const SirenPanel: FC<SirenPanelProps> = ({
197198

198199
setNotifications(updatedNotifications);
199200

200-
if(isRefresh)handleMarkNotificationsAsViewed(updatedNotifications[0].createdAt);
201+
if (isRefresh)
202+
handleMarkNotificationsAsViewed(updatedNotifications[0].createdAt);
201203
};
202204

203205
const fetchNotifications = async (isRefresh = false) => {
@@ -221,17 +223,15 @@ const SirenPanel: FC<SirenPanelProps> = ({
221223
if (!isEmptyArray(response.data ?? [])) {
222224
data = filterDataProperty(response);
223225
if (!data) return [];
224-
if(response?.meta) {
225-
226-
const isLastPage = response?.meta?.last === 'true';
226+
if (response?.meta) {
227+
const isLastPage = response?.meta?.last === "true";
227228

228-
if(isLastPage) setEndReached(true);
229+
if (isLastPage) setEndReached(true);
229230
else setEndReached(false);
230231
}
231232
updateNotificationList(data, isRefresh);
232233
}
233-
if (!data)
234-
setEndReached(true);
234+
if (!data) setEndReached(true);
235235
resetRealTimeFetch(isRefresh, data);
236236
} else {
237237
setEndReached(true);
@@ -318,26 +318,33 @@ const SirenPanel: FC<SirenPanelProps> = ({
318318
if (isLoading && isEmptyArray(notifications))
319319
return (
320320
<div className="siren-sdk-list-loader-container">
321-
{customLoader ||
322-
<>
323-
<AnimatedLoader styles={styles}/>
324-
<AnimatedLoader styles={styles}/>
325-
<AnimatedLoader styles={styles}/>
326-
<AnimatedLoader styles={styles}/>
327-
<AnimatedLoader styles={styles}/>
328-
</>}
321+
{customLoader || (
322+
<>
323+
<AnimatedLoader styles={styles} />
324+
<AnimatedLoader styles={styles} />
325+
<AnimatedLoader styles={styles} />
326+
<AnimatedLoader styles={styles} />
327+
<AnimatedLoader styles={styles} />
328+
</>
329+
)}
329330
</div>
330331
);
331332

332333
if (error)
333334
return (
334-
customErrorWindow || <ErrorWindow styles={styles} error={error} darkMode={darkMode} />
335+
customErrorWindow || (
336+
<ErrorWindow styles={styles} error={error} darkMode={darkMode} />
337+
)
335338
);
336339

337340
if (isEmptyArray(notifications))
338341
return (
339342
listEmptyComponent || (
340-
<EmptyList data-testid="empty-list" styles={styles} darkMode={darkMode} />
343+
<EmptyList
344+
data-testid="empty-list"
345+
styles={styles}
346+
darkMode={darkMode}
347+
/>
341348
)
342349
);
343350

@@ -359,19 +366,24 @@ const SirenPanel: FC<SirenPanelProps> = ({
359366
};
360367

361368
const renderListBottomComponent = () => {
362-
if (
363-
isEmptyArray(notifications)
364-
)
365-
return null;
366-
if (isLoading && !endReached)
369+
if (isEmptyArray(notifications)) return null;
370+
if (isLoading && !endReached)
367371
return (
368-
<div className="siren-sdk-panel-infinite-loader-container" >
369-
<div className="siren-sdk-panel-infinite-loader" style={styles.infiniteLoader} />
372+
<div className="siren-sdk-panel-infinite-loader-container">
373+
<div
374+
className="siren-sdk-panel-infinite-loader"
375+
style={styles.infiniteLoader}
376+
/>
370377
</div>
371-
)
378+
);
372379
if (!isLoading && !endReached)
373380
return (
374-
<ShowMoreButton styles={styles} customComponent={loadMoreComponent} onClick={handleLoadMore} loadMoreLabel={loadMoreLabel} />
381+
<ShowMoreButton
382+
styles={styles}
383+
customComponent={loadMoreComponent}
384+
onClick={handleLoadMore}
385+
loadMoreLabel={loadMoreLabel}
386+
/>
375387
);
376388

377389
return null;
@@ -383,12 +395,19 @@ const SirenPanel: FC<SirenPanelProps> = ({
383395
"siren-sdk-content-container"
384396
} ${customFooter ? "siren-sdk-panel-no-border" : ""}`;
385397

398+
const panelStyle = {
399+
...(!fullScreen && styles.windowTopBorder),
400+
...(!fullScreen && { width: `${modalWidth}px` }),
401+
...(!fullScreen && styles.windowBottomBorder),
402+
...styles.container,
403+
};
404+
386405
return (
387406
<div
388407
className={
389408
!fullScreen ? "siren-sdk-panel-modal" : "siren-sdk-panel-container"
390409
}
391-
style={{...(!fullScreen && styles.windowTopBorder),...(!fullScreen &&styles.windowBottomBorder),...styles.container}}
410+
style={panelStyle}
392411
data-testid="siren-panel"
393412
>
394413
<div>
@@ -404,11 +423,17 @@ const SirenPanel: FC<SirenPanelProps> = ({
404423
/>
405424
))}
406425
<div
407-
style={{...(!fullScreen && styles.windowBottomBorder),...styles.contentContainer}}
426+
style={{
427+
...(!fullScreen && styles.windowBottomBorder),
428+
...styles.contentContainer,
429+
}}
408430
>
409431
<div
410432
id="contentContainer"
411-
style={{...(!fullScreen && styles.windowBottomBorder),...styles.body}}
433+
style={{
434+
...(!fullScreen && styles.windowBottomBorder),
435+
...styles.body,
436+
}}
412437
className={containerClassNames}
413438
>
414439
{renderList()}

src/styles/header.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
font-weight: 500;
1717
font-size: 14px;
1818
line-height: 20px;
19-
margin-bottom: 12px;
2019
}
2120

2221
.siren-sdk-text-break {

src/styles/sirenInbox.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
.siren-sdk-inbox-root {
2+
position: fixed;
3+
}
4+
15
.siren-sdk-inbox-container {
26
width: fit-content;
7+
position: relative;
38
}

src/styles/sirenPanel.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
width: 100%;
33
}
44
.siren-sdk-panel-modal {
5-
position: absolute;
5+
position:absolute;
66
box-shadow: rgb(0 0 0 / 30%) 0px 8px 24px;
77
border-radius: 20px;
88
background-color: #FFFFFF;

0 commit comments

Comments
 (0)