Activity notification settings (#8485)

Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: hailey <me@haileyok.com>
This commit is contained in:
Samuel Newman
2025-07-02 00:36:04 +03:00
committed by GitHub
parent 8f9a8ddce0
commit bc072570d2
53 changed files with 1810 additions and 288 deletions
+46 -48
View File
@@ -3,6 +3,7 @@ import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
@@ -30,31 +31,33 @@ const BUBBLE_SHADOW_OFFSET = ARROW_SIZE / 3 // vibes-based, provide more shadow
type TooltipContextType = {
position: 'top' | 'bottom'
ready: boolean
visible: boolean
onVisibleChange: (visible: boolean) => void
}
type TargetMeasurements = {
x: number
y: number
width: number
height: number
}
type TargetContextType = {
targetMeasurements:
| {
x: number
y: number
width: number
height: number
}
| undefined
targetRef: React.RefObject<View>
targetMeasurements: TargetMeasurements | undefined
setTargetMeasurements: (measurements: TargetMeasurements) => void
shouldMeasure: boolean
}
const TooltipContext = createContext<TooltipContextType>({
position: 'bottom',
ready: false,
visible: false,
onVisibleChange: () => {},
})
const TargetContext = createContext<TargetContextType>({
targetMeasurements: undefined,
targetRef: {current: null},
setTargetMeasurements: () => {},
shouldMeasure: false,
})
export function Outer({
@@ -68,21 +71,12 @@ export function Outer({
visible: boolean
onVisibleChange: (visible: boolean) => void
}) {
/**
* Whether we have measured the target and are ready to show the tooltip.
*/
const [ready, setReady] = useState(false)
/**
* Lagging state to track the externally-controlled visibility of the
* tooltip.
* tooltip, which needs to wait for the target to be measured before
* actually being shown.
*/
const [prevRequestVisible, setPrevRequestVisible] = useState<
boolean | undefined
>()
/**
* Needs to reference the element this Tooltip is attached to.
*/
const targetRef = useRef<View>(null)
const [visible, setVisible] = useState<boolean>(false)
const [targetMeasurements, setTargetMeasurements] = useState<
| {
x: number
@@ -93,33 +87,24 @@ export function Outer({
| undefined
>(undefined)
if (requestVisible && !prevRequestVisible) {
setPrevRequestVisible(true)
if (targetRef.current) {
/*
* Once opened, measure the dimensions and position of the target
*/
targetRef.current.measure((_x, _y, width, height, pageX, pageY) => {
if (pageX !== undefined && pageY !== undefined && width && height) {
setTargetMeasurements({x: pageX, y: pageY, width, height})
setReady(true)
}
})
}
} else if (!requestVisible && prevRequestVisible) {
setPrevRequestVisible(false)
if (requestVisible && !visible && targetMeasurements) {
setVisible(true)
} else if (!requestVisible && visible) {
setVisible(false)
setTargetMeasurements(undefined)
setReady(false)
}
const ctx = useMemo(
() => ({position, ready, onVisibleChange}),
[position, ready, onVisibleChange],
() => ({position, visible, onVisibleChange}),
[position, visible, onVisibleChange],
)
const targetCtx = useMemo(
() => ({targetMeasurements, targetRef}),
[targetMeasurements, targetRef],
() => ({
targetMeasurements,
setTargetMeasurements,
shouldMeasure: requestVisible,
}),
[requestVisible, targetMeasurements, setTargetMeasurements],
)
return (
@@ -132,7 +117,20 @@ export function Outer({
}
export function Target({children}: {children: React.ReactNode}) {
const {targetRef} = useContext(TargetContext)
const {shouldMeasure, setTargetMeasurements} = useContext(TargetContext)
const targetRef = useRef<View>(null)
useEffect(() => {
if (!shouldMeasure) return
/*
* Once opened, measure the dimensions and position of the target
*/
targetRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
if (pageX !== undefined && pageY !== undefined && width && height) {
setTargetMeasurements({x: pageX, y: pageY, width, height})
}
})
}, [shouldMeasure, setTargetMeasurements])
return (
<View collapsable={false} ref={targetRef}>
@@ -148,13 +146,13 @@ export function Content({
children: React.ReactNode
label: string
}) {
const {position, ready, onVisibleChange} = useContext(TooltipContext)
const {position, visible, onVisibleChange} = useContext(TooltipContext)
const {targetMeasurements} = useContext(TargetContext)
const requestClose = useCallback(() => {
onVisibleChange(false)
}, [onVisibleChange])
if (!ready || !targetMeasurements) return null
if (!visible || !targetMeasurements) return null
return (
<Portal>
+8 -2
View File
@@ -13,10 +13,12 @@ import {Text} from '#/components/Typography'
type TooltipContextType = {
position: 'top' | 'bottom'
onVisibleChange: (open: boolean) => void
}
const TooltipContext = createContext<TooltipContextType>({
position: 'bottom',
onVisibleChange: () => {},
})
export function Outer({
@@ -30,7 +32,10 @@ export function Outer({
visible: boolean
onVisibleChange: (visible: boolean) => void
}) {
const ctx = useMemo(() => ({position}), [position])
const ctx = useMemo(
() => ({position, onVisibleChange}),
[position, onVisibleChange],
)
return (
<Popover.Root open={visible} onOpenChange={onVisibleChange}>
<TooltipContext.Provider value={ctx}>{children}</TooltipContext.Provider>
@@ -54,7 +59,7 @@ export function Content({
label: string
}) {
const t = useTheme()
const {position} = useContext(TooltipContext)
const {position, onVisibleChange} = useContext(TooltipContext)
return (
<Popover.Portal>
<Popover.Content
@@ -63,6 +68,7 @@ export function Content({
side={position}
sideOffset={4}
collisionPadding={MIN_EDGE_SPACE}
onInteractOutside={() => onVisibleChange(false)}
style={flatten([
a.rounded_sm,
select(t.name, {