parent
a8a9b5ef05
commit
a55a34d48f
4 changed files with 59 additions and 39 deletions
|
@ -11,6 +11,8 @@ import type {
|
||||||
import {useVal} from '@theatre/react'
|
import {useVal} from '@theatre/react'
|
||||||
import getStudio from './getStudio'
|
import getStudio from './getStudio'
|
||||||
import {marked} from 'marked'
|
import {marked} from 'marked'
|
||||||
|
import useTooltip from './uiComponents/Popover/useTooltip'
|
||||||
|
import MinimalTooltip from './uiComponents/Popover/MinimalTooltip'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a string key unique to a notification with a certain title and message.
|
* Creates a string key unique to a notification with a certain title and message.
|
||||||
|
@ -181,9 +183,9 @@ const COLORS = {
|
||||||
|
|
||||||
const IndicatorDot = styled.div<{type: NotificationType}>`
|
const IndicatorDot = styled.div<{type: NotificationType}>`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
|
padding-top: 21px;
|
||||||
|
|
||||||
::before {
|
::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -308,7 +310,7 @@ const NotifierContainer = styled.div`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 8px;
|
right: 92px;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 85vh;
|
height: 85vh;
|
||||||
|
@ -330,27 +332,31 @@ const NotificationScroller = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const EmptyState = styled.div`
|
const EmptyState = styled.div`
|
||||||
align-self: flex-end;
|
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
padding: 12px;
|
padding: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
${pointerEventsAutoInNormalMode};
|
|
||||||
background-color: rgba(40, 43, 47, 0.8);
|
|
||||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
||||||
backdrop-filter: blur(14px);
|
|
||||||
color: #b4b4b4;
|
color: #b4b4b4;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
|
||||||
@supports not (backdrop-filter: blur()) {
|
|
||||||
background: rgba(40, 43, 47, 0.95);
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
export const useEmptyNotificationsTooltip = () => {
|
||||||
|
const {hasNotifications} = useNotifications()
|
||||||
|
|
||||||
|
return useTooltip({enabled: !hasNotifications}, () => (
|
||||||
|
<MinimalTooltip>
|
||||||
|
<EmptyState>
|
||||||
|
<NotificationTitle>No notifications</NotificationTitle>
|
||||||
|
Notifications will appear here when you get them.
|
||||||
|
</EmptyState>
|
||||||
|
</MinimalTooltip>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The component responsible for rendering the notifications.
|
* The component responsible for rendering the notifications.
|
||||||
*/
|
*/
|
||||||
|
@ -363,26 +369,26 @@ export const Notifier = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotifierContainer>
|
<NotifierContainer>
|
||||||
{!pinNotifications ? null : toasts.length > 0 ? (
|
{!pinNotifications
|
||||||
<NotificationScroller onMouseEnter={startPause} onMouseLeave={endPause}>
|
? null
|
||||||
<div>
|
: toasts.length > 0 && (
|
||||||
{toasts.map((toast) => {
|
<NotificationScroller
|
||||||
return (
|
onMouseEnter={startPause}
|
||||||
<div key={toast.id}>
|
onMouseLeave={endPause}
|
||||||
{/* message is always a function in our case */}
|
>
|
||||||
{/* @ts-ignore */}
|
<div>
|
||||||
{toast.message(toast)}
|
{toasts.map((toast) => {
|
||||||
</div>
|
return (
|
||||||
)
|
<div key={toast.id}>
|
||||||
})}
|
{/* message is always a function in our case */}
|
||||||
</div>
|
{/* @ts-ignore */}
|
||||||
</NotificationScroller>
|
{toast.message(toast)}
|
||||||
) : (
|
</div>
|
||||||
<EmptyState>
|
)
|
||||||
<NotificationTitle>No notifications</NotificationTitle>
|
})}
|
||||||
Notifications will appear here when you get them.
|
</div>
|
||||||
</EmptyState>
|
</NotificationScroller>
|
||||||
)}
|
)}
|
||||||
<ButtonContainer align="side">
|
<ButtonContainer align="side">
|
||||||
{pinNotifications && toasts.length > 0 && (
|
{pinNotifications && toasts.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -20,7 +20,10 @@ import DoubleChevronRight from '@theatre/studio/uiComponents/icons/DoubleChevron
|
||||||
import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton'
|
import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton'
|
||||||
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
||||||
import MoreMenu from './MoreMenu/MoreMenu'
|
import MoreMenu from './MoreMenu/MoreMenu'
|
||||||
import {useNotifications} from '@theatre/studio/notify'
|
import {
|
||||||
|
useNotifications,
|
||||||
|
useEmptyNotificationsTooltip,
|
||||||
|
} from '@theatre/studio/notify'
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
height: 36px;
|
height: 36px;
|
||||||
|
@ -147,6 +150,9 @@ const GlobalToolbar: React.FC = () => {
|
||||||
|
|
||||||
const {hasNotifications} = useNotifications()
|
const {hasNotifications} = useNotifications()
|
||||||
|
|
||||||
|
const [notificationsTooltip, notificationsTriggerRef] =
|
||||||
|
useEmptyNotificationsTooltip()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<SubContainer>
|
<SubContainer>
|
||||||
|
@ -174,7 +180,9 @@ const GlobalToolbar: React.FC = () => {
|
||||||
<ExtensionToolbar showLeftDivider toolbarId="global" />
|
<ExtensionToolbar showLeftDivider toolbarId="global" />
|
||||||
</SubContainer>
|
</SubContainer>
|
||||||
<SubContainer>
|
<SubContainer>
|
||||||
<ToolbarIconButton
|
{notificationsTooltip}
|
||||||
|
<PinButton
|
||||||
|
ref={notificationsTriggerRef as $IntentionalAny}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
getStudio().transaction(({stateEditors, drafts}) => {
|
getStudio().transaction(({stateEditors, drafts}) => {
|
||||||
stateEditors.studio.ahistoric.setPinNotifications(
|
stateEditors.studio.ahistoric.setPinNotifications(
|
||||||
|
@ -182,10 +190,13 @@ const GlobalToolbar: React.FC = () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
icon={<Bell />}
|
||||||
|
pinHintIcon={<Bell />}
|
||||||
|
unpinHintIcon={<Bell />}
|
||||||
|
pinned={useVal(getStudio().atomP.ahistoric.pinNotifications) ?? false}
|
||||||
>
|
>
|
||||||
<Bell />
|
|
||||||
{hasNotifications && <HasUpdatesBadge type="warning" />}
|
{hasNotifications && <HasUpdatesBadge type="warning" />}
|
||||||
</ToolbarIconButton>
|
</PinButton>
|
||||||
{moreMenu.node}
|
{moreMenu.node}
|
||||||
<ToolbarIconButton
|
<ToolbarIconButton
|
||||||
ref={moreMenuTriggerRef}
|
ref={moreMenuTriggerRef}
|
||||||
|
|
|
@ -20,7 +20,10 @@ interface PinButtonProps extends ComponentPropsWithRef<'button'> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const PinButton = forwardRef<HTMLButtonElement, PinButtonProps>(
|
const PinButton = forwardRef<HTMLButtonElement, PinButtonProps>(
|
||||||
({hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props}, ref) => {
|
(
|
||||||
|
{children, hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
const [hovered, setHovered] = useState(false)
|
const [hovered, setHovered] = useState(false)
|
||||||
|
|
||||||
const showHint = hovered || hint
|
const showHint = hovered || hint
|
||||||
|
@ -48,6 +51,7 @@ const PinButton = forwardRef<HTMLButtonElement, PinButtonProps>(
|
||||||
? unpinHintIcon
|
? unpinHintIcon
|
||||||
: icon}
|
: icon}
|
||||||
</div>
|
</div>
|
||||||
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,8 +12,7 @@ function Bell(props: React.SVGProps<SVGSVGElement>) {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M8 1.57c-.416 0-.752.36-.752.804v.482c-1.715.372-3.006 1.994-3.006 3.938v.473c0 1.18-.407 2.32-1.14 3.205l-.173.208a.85.85 0 00-.125.864.75.75 0 00.686.475h9.019a.752.752 0 00.686-.475.845.845 0 00-.125-.864l-.174-.208a5.026 5.026 0 01-1.139-3.205v-.473c0-1.944-1.291-3.566-3.006-3.938v-.482c0-.445-.336-.804-.752-.804zm1.063 12.39c.282-.301.44-.71.44-1.138H6.496c0 .428.158.837.44 1.138.281.302.664.47 1.063.47.4 0 .783-.168 1.064-.47z"
|
d="M8 1.57c-.416 0-.752.36-.752.804v.482c-1.715.372-3.006 1.994-3.006 3.938v.473c0 1.18-.407 2.32-1.14 3.205l-.173.208a.85.85 0 00-.125.864.75.75 0 00.686.475h9.019a.752.752 0 00.686-.475.845.845 0 00-.125-.864l-.174-.208a5.026 5.026 0 01-1.139-3.205v-.473c0-1.944-1.291-3.566-3.006-3.938v-.482c0-.445-.336-.804-.752-.804zm1.063 12.39c.282-.301.44-.71.44-1.138H6.496c0 .428.158.837.44 1.138.281.302.664.47 1.063.47.4 0 .783-.168 1.064-.47z"
|
||||||
fill="#fff"
|
fill="currentColor"
|
||||||
fillOpacity={0.6}
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue