diff --git a/frontend/app/notification/notificationbubbles.scss b/frontend/app/notification/notificationbubbles.scss index 345fa40e8..a3de7097b 100644 --- a/frontend/app/notification/notificationbubbles.scss +++ b/frontend/app/notification/notificationbubbles.scss @@ -3,8 +3,8 @@ .notification-bubbles { display: flex; - width: 380px; + width: 432px; flex-direction: column; - align-items: flex-start; + align-items: flex-end; row-gap: 8px; } diff --git a/frontend/app/notification/notificationbubbles.tsx b/frontend/app/notification/notificationbubbles.tsx index 0e439eb91..34097fe51 100644 --- a/frontend/app/notification/notificationbubbles.tsx +++ b/frontend/app/notification/notificationbubbles.tsx @@ -6,15 +6,18 @@ import { FloatingPortal, useFloating, useInteractions } from "@floating-ui/react import clsx from "clsx"; import { useAtomValue } from "jotai"; import { useEffect, useState } from "react"; -import "./notificationbubbles.scss"; import { NotificationItem } from "./notificationitem"; +import { RatingBubble } from "./ratingbubble"; import { useNotification } from "./usenotification"; +import "./notificationbubbles.scss"; + const NotificationBubbles = () => { const { notifications, hoveredId, hideNotification, + removeNotification, copyNotification, handleActionClick, formatTimestamp, @@ -59,6 +62,15 @@ const NotificationBubbles = () => { > {notifications.map((notif) => { if (notif.hidden) return null; + if (notif.componentType === "rating") { + return ( + + ); + } return ( { - const { id, title, message, icon, type, timestamp, persistent, actions } = notification; - const color = type === "error" ? "red" : type === "warning" ? "yellow" : "green"; + const { id, title, message, icon, statusType, timestamp, persistent, actions } = notification; + const color = statusType === "error" ? "red" : statusType === "warning" ? "yellow" : "green"; const nIcon = icon ? icon : "bell"; const renderCloseButton = () => { diff --git a/frontend/app/notification/notificationpopover.tsx b/frontend/app/notification/notificationpopover.tsx index 5c390524b..2c250c909 100644 --- a/frontend/app/notification/notificationpopover.tsx +++ b/frontend/app/notification/notificationpopover.tsx @@ -37,8 +37,10 @@ const NotificationPopover = () => { setNotificationPopoverMode(!notificationPopoverMode); }, [notificationPopoverMode]); - const hasErrors = notifications.some((n) => n.type === "error"); - const hasUpdate = notifications.some((n) => n.type === "update"); + const filteredNotifications = notifications.filter((n) => n.componentType == null); + + const hasErrors = filteredNotifications.some((n) => n.statusType === "error"); + const hasUpdate = filteredNotifications.some((n) => n.statusType === "update"); const addOnClassNames = hasUpdate ? "solid green" : hasErrors ? "solid red" : "ghost grey"; @@ -61,12 +63,12 @@ const NotificationPopover = () => { "notification-trigger-button horizontal-padding-6 vertical-padding-4 border-radius-", addOnClassNames )} - disabled={notifications.length === 0} + disabled={filteredNotifications.length === 0} onClick={handleTogglePopover} > {getIcon()} - {notifications.length > 0 && ( + {filteredNotifications.length > 0 && (
Notifications @@ -85,7 +87,7 @@ const NotificationPopover = () => { options={{ scrollbars: { autoHide: "leave" } }} style={{ maxHeight: window.innerHeight / 2 }} > - {notifications.map((notif, index) => ( + {filteredNotifications.map((notif, index) => ( { onMouseEnter={() => setHoveredId(notif.id)} onMouseLeave={() => setHoveredId(null)} /> - {index !== notifications.length - 1 &&
} + {index !== filteredNotifications.length - 1 &&
}
))} diff --git a/frontend/app/notification/ratingbubble.scss b/frontend/app/notification/ratingbubble.scss new file mode 100644 index 000000000..5049b3da5 --- /dev/null +++ b/frontend/app/notification/ratingbubble.scss @@ -0,0 +1,36 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +.notification-rating-bubble { + position: relative; + display: flex; + width: 432px; + padding: 16px 24px 16px 16px; + align-items: flex-start; + gap: 12px; + border-radius: 8px; + border: 0.5px solid rgba(255, 255, 255, 0.12); + background: #232323; + box-shadow: 0px 8px 32px 0px rgb(from var(--main-bg-color) r g b / 0.25); + + .notification-inner { + display: flex; + align-items: flex-start; + flex-direction: column; + column-gap: 6px; + + .actions-wrapper { + display: flex; + column-gap: 9px; + margin-top: 12px; + + button { + width: 32px; + height: 32px; + padding: 0; + text-align: center; + justify-content: center; + } + } + } +} diff --git a/frontend/app/notification/ratingbubble.tsx b/frontend/app/notification/ratingbubble.tsx new file mode 100644 index 000000000..0eb4afdc5 --- /dev/null +++ b/frontend/app/notification/ratingbubble.tsx @@ -0,0 +1,66 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { Button } from "@/element/button"; +import { makeIconClass } from "@/util/util"; +import clsx from "clsx"; +import { useState } from "react"; + +import "./ratingbubble.scss"; + +interface RatingBubbleProps { + notification: NotificationType; + onRemove: (id: string) => void; +} + +const RatingBubble = ({ notification, onRemove }: RatingBubbleProps) => { + const { id, title, message } = notification; + const [hoveredButtons, setHoveredButtons] = useState<{ [key: number]: boolean }>({}); + + const handleRatingClick = (id, rating: number) => { + console.log("rating clicked"); + onRemove(id); + }; + + const handleMouseEnter = (buttonIndex: number) => { + setHoveredButtons((prev) => ({ ...prev, [buttonIndex]: true })); + }; + + const handleMouseLeave = (buttonIndex: number) => { + setHoveredButtons((prev) => ({ ...prev, [buttonIndex]: false })); + }; + + return ( +
+ +
+ {title &&
{title}
} + {message &&
{message}
} +
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((rating) => ( + + ))} +
+
+
+ ); +}; + +export { RatingBubble }; diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 809f4b3d6..2df6c3537 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -320,7 +320,8 @@ declare global { hidden?: boolean; actions?: NotificationActionType[]; persistent?: boolean; - type?: "error" | "update" | "info" | "warning"; + componentType?: "rating"; + statusType?: "error" | "update" | "info" | "warning"; }; interface AbstractWshClient {