Skip to content

Commit 2a69a40

Browse files
committed
#45 Notification styling fixes. Fixed bug with sliding stuck
1 parent 6f031a3 commit 2a69a40

4 files changed

Lines changed: 99 additions & 117 deletions

File tree

src/webOS/components/Notification/Notification.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* Notification.css */
22
.notification-container {
3-
position: absolute; /* Relative to the parent container (e.g., your desktop div) */
3+
position: absolute;
44
top: 40px;
55
right: 20px;
66
display: flex;
@@ -19,10 +19,11 @@
1919
max-width: 300px;
2020
display: flex;
2121
align-items: center;
22-
font-family: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
22+
font-family: -apple-system, BlinkMacSystemFont, 'San Francisco',
23+
'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
2324
pointer-events: auto;
2425
cursor: pointer;
25-
transition: transform 0.3s ease, opacity 0.3s ease;
26+
transition: transform 0.3s ease; /* removed opacity transition */
2627
user-select: none;
2728
}
2829

Lines changed: 71 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,81 @@
11
// Notification.jsx
2-
import React, { useState, useEffect } from 'react';
2+
import React from 'react';
33
import { motion, AnimatePresence } from 'framer-motion';
4-
import './Notification.css';
54

6-
const Notification = () => {
7-
const [notifications, setNotifications] = useState([]);
5+
// 300px maxWidth + 20px container right offset + 50px extra
6+
const OFFSCREEN_X = 370;
87

9-
useEffect(() => {
10-
const handleNotification = (event) => {
11-
const { message, duration, icon } = event.detail;
12-
const id = Date.now();
13-
14-
setNotifications((prev) => {
15-
if (prev.some((n) => n.message === message && Math.abs(n.id - id) < 1000)) {
16-
return prev;
17-
}
18-
const iconSrc = typeof icon === 'string' ? icon : icon?.default;
19-
return [...prev, { id, message, icon: iconSrc }];
20-
});
21-
22-
setTimeout(() => {
23-
handleSwipe(id);
24-
}, duration || 3000);
25-
};
26-
27-
window.addEventListener('show-notification', handleNotification);
28-
return () => {
29-
window.removeEventListener('show-notification', handleNotification);
30-
};
31-
}, []);
8+
const containerStyle = {
9+
position: 'absolute',
10+
top: 40,
11+
right: 20,
12+
display: 'flex',
13+
flexDirection: 'column',
14+
gap: '10px',
15+
zIndex: 300,
16+
pointerEvents: 'none',
17+
};
3218

33-
const handleSwipe = (id) => {
34-
setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, closing: true } : n)));
19+
const notificationStyle = {
20+
background: 'rgba(255, 255, 255, 0.7)',
21+
backdropFilter: 'blur(10px)',
22+
borderRadius: '12px',
23+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
24+
padding: '16px 20px',
25+
maxWidth: '300px',
26+
display: 'flex',
27+
alignItems: 'center',
28+
fontFamily:
29+
"-apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Helvetica, Arial, sans-serif",
30+
pointerEvents: 'auto',
31+
cursor: 'pointer',
32+
userSelect: 'none',
33+
};
3534

36-
setTimeout(() => {
37-
setNotifications((prev) => prev.filter((n) => n.id !== id));
38-
}, 500);
39-
};
35+
const iconStyle = {
36+
width: '40px',
37+
height: '40px',
38+
marginRight: '20px',
39+
borderRadius: '8px',
40+
objectFit: 'cover',
41+
};
4042

41-
return (
42-
<div className="notification-container">
43-
<AnimatePresence>
44-
{notifications.map(({ id, message, icon, closing }) => (
45-
<motion.div
46-
key={id}
47-
className="notification"
48-
initial={{ x: 300, opacity: 0 }}
49-
animate={closing ? { x: 300, opacity: 0 } : { x: 0, opacity: 1 }}
50-
exit={{ x: 300, opacity: 0 }}
51-
drag="x"
52-
dragConstraints={{ left: 0, right: 300 }}
53-
onDragEnd={(event, info) => {
54-
if (info.offset.x > 150) {
55-
handleSwipe(id);
56-
}
57-
}}
58-
transition={{ duration: 0.5 }}
59-
>
60-
{icon && <img src={icon} alt="icon" className="notification-icon" />}
61-
<span className="notification-message">{message}</span>
62-
</motion.div>
63-
))}
64-
</AnimatePresence>
65-
</div>
66-
);
43+
const messageStyle = {
44+
fontSize: '14px',
45+
color: '#333',
6746
};
6847

69-
export default Notification;
48+
const Notification = ({ notifications, handleSwipe }) => (
49+
<div style={containerStyle}>
50+
<AnimatePresence>
51+
{notifications.map(({ id, message, icon, closing }) => (
52+
<motion.div
53+
key={id}
54+
style={notificationStyle}
55+
initial={{ x: OFFSCREEN_X, opacity: 0 }}
56+
animate={
57+
closing
58+
? { x: OFFSCREEN_X, opacity: 1 } // slide out fully
59+
: { x: 0, opacity: 1 } // slide in
60+
}
61+
exit={{ x: OFFSCREEN_X, opacity: 1 }} // keep full opacity
62+
drag="x"
63+
dragConstraints={{ left: 0, right: OFFSCREEN_X }}
64+
dragMomentum={false}
65+
onDragEnd={(e, info) => {
66+
// if dragged at all to the right, trigger full swipe
67+
if (info.offset.x > 0) {
68+
handleSwipe(id);
69+
}
70+
}}
71+
transition={{ duration: 0.5 }}
72+
>
73+
{icon && <img src={icon} alt="icon" style={iconStyle} />}
74+
<span style={messageStyle}>{message}</span>
75+
</motion.div>
76+
))}
77+
</AnimatePresence>
78+
</div>
79+
);
7080

71-
export const notify = (message, duration = 3000, icon = '') => {
72-
const iconSrc = typeof icon === 'string' ? icon : icon?.default;
73-
const event = new CustomEvent('show-notification', {
74-
detail: { message, duration, icon: iconSrc }
75-
});
76-
window.dispatchEvent(event);
77-
};
81+
export default Notification;
Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,55 @@
11
// NotificationProvider.jsx
22
import React, { createContext, useContext, useState } from 'react';
3-
import { motion, AnimatePresence } from 'framer-motion';
4-
import './Notification.css';
3+
import Notification from './Notification';
54

65
const NotificationContext = createContext();
76

87
/**
9-
* NotificationProvider wraps your application and renders notifications.
10-
* It also provides an imperative notify() function via context.
8+
* Wrap your app in <NotificationProvider>.
9+
* Call `useNotification().notify(...)` anywhere below to fire.
1110
*/
1211
export const NotificationProvider = ({ children }) => {
1312
const [notifications, setNotifications] = useState([]);
1413

1514
const notify = (message, duration = 3000, icon = '') => {
1615
const id = Date.now();
1716
setNotifications((prev) => {
18-
// Prevent duplicate notifications within a short time span.
19-
if (prev.some((n) => n.message === message && Math.abs(n.id - id) < 1000)) {
17+
if (
18+
prev.some(
19+
(n) => n.message === message && Math.abs(n.id - id) < 1000
20+
)
21+
) {
2022
return prev;
2123
}
2224
const iconSrc = typeof icon === 'string' ? icon : icon?.default;
2325
return [...prev, { id, message, icon: iconSrc }];
2426
});
2527

26-
// Remove the notification after the specified duration.
27-
setTimeout(() => {
28-
handleSwipe(id);
29-
}, duration);
28+
setTimeout(() => handleSwipe(id), duration);
3029
};
3130

3231
const handleSwipe = (id) => {
32+
// mark closing → triggers slide-out only
3333
setNotifications((prev) =>
3434
prev.map((n) => (n.id === id ? { ...n, closing: true } : n))
3535
);
36-
setTimeout(() => {
37-
setNotifications((prev) => prev.filter((n) => n.id !== id));
38-
}, 500);
36+
// remove after animation
37+
setTimeout(
38+
() => setNotifications((prev) => prev.filter((n) => n.id !== id)),
39+
500
40+
);
3941
};
4042

41-
const contextValue = { notify };
42-
4343
return (
44-
<NotificationContext.Provider value={contextValue}>
44+
<NotificationContext.Provider value={{ notify }}>
4545
{children}
46-
<div className="notification-container">
47-
<AnimatePresence>
48-
{notifications.map(({ id, message, icon, closing }) => (
49-
<motion.div
50-
key={id}
51-
className="notification"
52-
initial={{ x: 300, opacity: 0 }}
53-
animate={closing ? { x: 300, opacity: 0 } : { x: 0, opacity: 1 }}
54-
exit={{ x: 300, opacity: 0 }}
55-
drag="x"
56-
dragConstraints={{ left: 0, right: 300 }}
57-
onDragEnd={(event, info) => {
58-
if (info.offset.x > 150) {
59-
handleSwipe(id);
60-
}
61-
}}
62-
transition={{ duration: 0.5 }}
63-
>
64-
{icon && <img src={icon} alt="icon" className="notification-icon" />}
65-
<span className="notification-message">{message}</span>
66-
</motion.div>
67-
))}
68-
</AnimatePresence>
69-
</div>
46+
<Notification
47+
notifications={notifications}
48+
handleSwipe={handleSwipe}
49+
/>
7050
</NotificationContext.Provider>
7151
);
7252
};
7353

74-
/**
75-
* Hook to use the notification context.
76-
*/
77-
export const useNotification = () => {
78-
return useContext(NotificationContext);
79-
};
54+
/** Hook to fire notifications: */
55+
export const useNotification = () => useContext(NotificationContext);

src/webOS/coreservices/DesktopAssembler/DesktopAssembler.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import MenuBar from '../../components/MenuBar/MenuBar.jsx';
88
import Wallpaper from '../../components/Wallpaper/Wallpaper.jsx';
99
import WelcomeWrap from '../../components/WelcomeWrap/WelcomeWrap.jsx';
1010
import IconGrid from '../../components/IconGrid/IconGrid.jsx';
11+
import icon from '../../media/icons/finder.png';
1112

1213
/**
1314
* desktopId: unique string for this instance
@@ -18,7 +19,7 @@ function DesktopAssembler({ desktopId = 'default' }) {
1819
const { notify } = useNotification();
1920

2021
useEffect(() => {
21-
notify('Test Notification: App has loaded!', 3000, '');
22+
notify('Test Notification: App has loaded!', 3000, icon);
2223
}, [notify]);
2324

2425
const handleOpenApp = (appId) => {

0 commit comments

Comments
 (0)