This commit is contained in:
Eric Bailey
2025-06-23 10:57:07 -05:00
parent a2d8d3b90e
commit ec4cdf1189
6 changed files with 214 additions and 5 deletions
+6 -3
View File
@@ -60,6 +60,7 @@ import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialo
import {Provider as PortalProvider} from '#/components/Portal'
import {Provider as ActiveVideoProvider} from '#/components/Post/Embed/VideoEmbed/ActiveVideoWebContext'
import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
import {Provider as TooltipProvider} from '#/components/Tooltip'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder'
@@ -194,9 +195,11 @@ function App() {
<DialogStateProvider>
<LightboxStateProvider>
<PortalProvider>
<StarterPackProvider>
<InnerApp />
</StarterPackProvider>
<TooltipProvider>
<StarterPackProvider>
<InnerApp />
</StarterPackProvider>
</TooltipProvider>
</PortalProvider>
</LightboxStateProvider>
</DialogStateProvider>
+4
View File
@@ -983,6 +983,10 @@ export const atoms = {
transition_none: web({
transitionProperty: 'none',
}),
transition_timing_default: web({
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
transitionDuration: '100ms',
}),
transition_all: web({
transitionProperty: 'all',
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
+3
View File
@@ -1 +1,4 @@
import {atoms as a} from '#/alf'
export const TIP_SIZE = 12
export const MIN_X_SPACE = a.px_lg.paddingLeft
+9 -2
View File
@@ -14,12 +14,19 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {atoms as a, select, useTheme} from '#/alf'
import {useOnGesture} from '#/components/hooks/useOnGesture'
import {Portal} from '#/components/Portal'
import {TIP_SIZE} from '#/components/Tooltip/const'
import {MIN_EDGE_SPACE, TIP_SIZE} from '#/components/Tooltip/const'
import {Text} from '#/components/Typography'
const HALF_TIP = TIP_SIZE / 2
const TIP_VISUAL_OFFSET = TIP_SIZE / 3
/**
* Only needed on web
*/
export function Provider({children}: {children: React.ReactNode}) {
return children
}
type TooltipContextType = {
position: 'top' | 'bottom'
ready: boolean
@@ -205,7 +212,7 @@ function Bubble({
const maxTop = insets.top
const maxBottom = wh - insets.bottom
const {width: cw, height: ch} = bubbleMeasurements
const minLeft = a.px_xl.paddingLeft
const minLeft = MIN_EDGE_SPACE
const maxLeft = ww - minLeft
let computedPosition: 'top' | 'bottom' = position
+157
View File
@@ -0,0 +1,157 @@
import {Children, createContext, useContext, useMemo, useState} from 'react'
import {View} from 'react-native'
import {Popover} from 'radix-ui'
import {atoms as a, flatten, select, useTheme} from '#/alf'
import {transparentifyColor} from '#/alf/util/colorGeneration'
import {MIN_EDGE_SPACE, TIP_SIZE} from '#/components/Tooltip/const'
import {Text} from '#/components/Typography'
const TIP_VISUAL_OFFSET = TIP_SIZE / 3
type TooltipContextType = {
position: 'top' | 'bottom'
}
const TooltipContext = createContext<TooltipContextType>({
position: 'bottom',
})
export function Provider({children}: {children: React.ReactNode}) {
return children
}
export function Outer({
children,
position = 'bottom',
visible,
onVisibleChange,
}: {
children: React.ReactNode
position?: 'top' | 'bottom'
visible: boolean
onVisibleChange: (visible: boolean) => void
}) {
const ctx = useMemo(() => ({position}), [position])
return (
<Popover.Root open={visible} onOpenChange={onVisibleChange}>
<TooltipContext.Provider value={ctx}>{children}</TooltipContext.Provider>
</Popover.Root>
)
}
export function Target({
children,
}: {
children: (props: object) => React.ReactNode
}) {
const child = useMemo(() => children({}), [children])
return <Popover.Trigger asChild>{child}</Popover.Trigger>
}
export function Content({
children,
label,
}: {
children: React.ReactNode
label: string
}) {
const t = useTheme()
const {position} = useContext(TooltipContext)
const [bubbleMeasurements, setBubbleMeasurements] = useState<
| {
width: number
height: number
}
| undefined
>(undefined)
return (
<Popover.Portal>
<Popover.Content
className="radix-popover-content"
aria-label={label}
side={position}
sideOffset={(TIP_SIZE / 3) * -1}
collisionPadding={MIN_EDGE_SPACE}
style={flatten([
a.rounded_sm,
a.transition_timing_default,
{
transitionProperty: 'transform, opacity',
},
select(t.name, {
light: t.atoms.bg,
dark: t.atoms.bg_contrast_100,
dim: t.atoms.bg_contrast_100,
}),
{
boxShadow: select(t.name, {
light: `0 ${TIP_VISUAL_OFFSET}px 16px ${transparentifyColor(
t.palette.black,
0.2,
)}`,
dark: `0 ${TIP_VISUAL_OFFSET}px 16px ${transparentifyColor(
t.palette.black,
0.2,
)}`,
dim: `0 ${TIP_VISUAL_OFFSET}px 16px ${transparentifyColor(
t.palette.black,
0.2,
)}`,
}),
},
bubbleMeasurements
? {
width: Math.min(200, bubbleMeasurements.width),
height: bubbleMeasurements.height,
}
: {
width: 200,
opacity: 0,
},
])}>
<Popover.Arrow
width={TIP_SIZE}
height={TIP_SIZE / 2}
fill={select(t.name, {
light: t.atoms.bg.backgroundColor,
dark: t.atoms.bg_contrast_100.backgroundColor,
dim: t.atoms.bg_contrast_100.backgroundColor,
})}
/>
<View
style={[
a.absolute,
a.px_md,
a.py_sm,
{
maxWidth: 200,
},
]}
onLayout={e => {
setBubbleMeasurements({
width: Math.ceil(e.nativeEvent.layout.width),
height: Math.ceil(e.nativeEvent.layout.height),
})
}}>
{children}
</View>
</Popover.Content>
</Popover.Portal>
)
}
export function TextBubble({children}: {children: React.ReactNode}) {
const c = Children.toArray(children)
return (
<Content label={c.join(' ')}>
<View style={[a.gap_xs]}>
{c.map((child, i) => (
<Text key={i} style={[a.text_sm, a.leading_snug]}>
{child}
</Text>
))}
</View>
</Content>
)
}
+35
View File
@@ -334,3 +334,38 @@ input[type='range'][orient='vertical']::-moz-range-thumb {
min-width: var(--radix-select-trigger-width);
max-height: var(--radix-select-content-available-height);
}
/*
* #/components/Tooltip/index.web.tsx
*/
.radix-popover-content {
animation-duration: 300ms;
animation-timing-function: cubic-bezier(0.17, 0.73, 0.14, 1);
will-change: transform, opacity;
}
.radix-popover-content[data-state='open'][data-side='top'] {
animation-name: radixPopoverSlideUpAndFade;
}
.radix-popover-content[data-state='open'][data-side='bottom'] {
animation-name: radixPopoverSlideDownAndFade;
}
@keyframes radixPopoverSlideUpAndFade {
from {
opacity: 0;
transform: translateY(2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes radixPopoverSlideDownAndFade {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}