148 lines
4.3 KiB
TypeScript
148 lines
4.3 KiB
TypeScript
import { ElementIds } from '@/Constants/ElementIDs'
|
|
import { classNames } from '@standardnotes/utils'
|
|
import { ReactNode, useCallback, useState } from 'react'
|
|
import { IconType, PrefKey, PrefDefaults } from '@standardnotes/snjs'
|
|
import Icon from '../Icon/Icon'
|
|
import { useApplication } from '../ApplicationProvider'
|
|
|
|
export type NoteStatus = {
|
|
type: 'saving' | 'saved' | 'error'
|
|
message: string
|
|
desc?: string
|
|
}
|
|
|
|
const IndicatorWithTooltip = ({
|
|
className,
|
|
onClick,
|
|
onBlur,
|
|
icon,
|
|
isTooltipVisible,
|
|
children,
|
|
animateIcon = false,
|
|
}: {
|
|
className: string
|
|
onClick: () => void
|
|
onBlur: () => void
|
|
icon: IconType
|
|
isTooltipVisible: boolean
|
|
children: ReactNode
|
|
animateIcon?: boolean
|
|
}) => (
|
|
<div className="note-status-tooltip-container relative">
|
|
<button
|
|
className={classNames('peer flex h-5 w-5 items-center justify-center rounded-full', className)}
|
|
onClick={onClick}
|
|
onBlur={onBlur}
|
|
aria-describedby={ElementIds.NoteStatusTooltip}
|
|
>
|
|
<Icon className={animateIcon ? 'animate-spin' : ''} type={icon} size="small" />
|
|
<span className="sr-only">Note sync status</span>
|
|
</button>
|
|
<div
|
|
id={ElementIds.NoteStatusTooltip}
|
|
className={classNames(
|
|
isTooltipVisible ? '' : 'hidden',
|
|
'absolute right-0 top-full min-w-[90vw] translate-x-2 translate-y-1 select-none rounded border border-border',
|
|
'bg-default px-3 py-1.5 text-left peer-hover:block peer-focus:block md:min-w-max',
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
type Props = {
|
|
status: NoteStatus | undefined
|
|
syncTakingTooLong: boolean
|
|
updateSavingIndicator?: boolean
|
|
}
|
|
|
|
const NoteStatusIndicator = ({
|
|
status,
|
|
syncTakingTooLong,
|
|
updateSavingIndicator = PrefDefaults[PrefKey.UpdateSavingStatusIndicator],
|
|
}: Props) => {
|
|
const application = useApplication()
|
|
const [isTooltipVisible, setIsTooltipVisible] = useState(false)
|
|
|
|
const onBlur = () => setIsTooltipVisible(false)
|
|
|
|
const toggleShowPreference = useCallback(() => {
|
|
void application.setPreference(PrefKey.UpdateSavingStatusIndicator, !updateSavingIndicator)
|
|
}, [application, updateSavingIndicator])
|
|
|
|
if (updateSavingIndicator && !status) {
|
|
return null
|
|
}
|
|
|
|
if (status && status.type === 'error') {
|
|
return (
|
|
<IndicatorWithTooltip
|
|
className="bg-danger text-danger-contrast"
|
|
onClick={toggleShowPreference}
|
|
onBlur={onBlur}
|
|
icon="warning"
|
|
isTooltipVisible={isTooltipVisible}
|
|
>
|
|
<div className="text-sm font-bold text-danger">{status.message}</div>
|
|
{status.desc && <div className="mt-0.5">{status.desc}</div>}
|
|
</IndicatorWithTooltip>
|
|
)
|
|
}
|
|
|
|
if (syncTakingTooLong) {
|
|
return (
|
|
<IndicatorWithTooltip
|
|
className="bg-warning text-warning-contrast"
|
|
onClick={toggleShowPreference}
|
|
onBlur={onBlur}
|
|
icon={status && status.type === 'saving' ? 'sync' : 'warning'}
|
|
isTooltipVisible={isTooltipVisible}
|
|
>
|
|
{status ? (
|
|
<>
|
|
<div className="text-sm font-bold text-warning">{status.message}</div>
|
|
{status.desc && <div className="mt-0.5">{status.desc}</div>}
|
|
</>
|
|
) : (
|
|
<div className="text-sm font-bold text-warning">Sync taking too long</div>
|
|
)}
|
|
</IndicatorWithTooltip>
|
|
)
|
|
}
|
|
|
|
if (updateSavingIndicator && status) {
|
|
return (
|
|
<IndicatorWithTooltip
|
|
className={classNames(
|
|
status.type === 'saving' && 'bg-contrast',
|
|
status.type === 'saved' && 'bg-success text-success-contrast',
|
|
)}
|
|
onClick={toggleShowPreference}
|
|
onBlur={onBlur}
|
|
icon={status.type === 'saving' ? 'sync' : 'check'}
|
|
animateIcon={status.type === 'saving'}
|
|
isTooltipVisible={isTooltipVisible}
|
|
>
|
|
<div className="text-sm font-bold">{status.message}</div>
|
|
{status.desc && <div className="mt-0.5">{status.desc}</div>}
|
|
</IndicatorWithTooltip>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<IndicatorWithTooltip
|
|
className="bg-contrast text-passive-1"
|
|
onClick={toggleShowPreference}
|
|
onBlur={onBlur}
|
|
icon="info"
|
|
isTooltipVisible={isTooltipVisible}
|
|
>
|
|
<div className="text-sm font-bold">Note status updates are disabled</div>
|
|
<div className="mt-0.5">Click to enable.</div>
|
|
</IndicatorWithTooltip>
|
|
)
|
|
}
|
|
|
|
export default NoteStatusIndicator
|