Web
This commit is contained in:
+6
-3
@@ -60,6 +60,7 @@ import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialo
|
|||||||
import {Provider as PortalProvider} from '#/components/Portal'
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
import {Provider as ActiveVideoProvider} from '#/components/Post/Embed/VideoEmbed/ActiveVideoWebContext'
|
import {Provider as ActiveVideoProvider} from '#/components/Post/Embed/VideoEmbed/ActiveVideoWebContext'
|
||||||
import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
|
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 {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||||
import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder'
|
import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder'
|
||||||
|
|
||||||
@@ -194,9 +195,11 @@ function App() {
|
|||||||
<DialogStateProvider>
|
<DialogStateProvider>
|
||||||
<LightboxStateProvider>
|
<LightboxStateProvider>
|
||||||
<PortalProvider>
|
<PortalProvider>
|
||||||
<StarterPackProvider>
|
<TooltipProvider>
|
||||||
<InnerApp />
|
<StarterPackProvider>
|
||||||
</StarterPackProvider>
|
<InnerApp />
|
||||||
|
</StarterPackProvider>
|
||||||
|
</TooltipProvider>
|
||||||
</PortalProvider>
|
</PortalProvider>
|
||||||
</LightboxStateProvider>
|
</LightboxStateProvider>
|
||||||
</DialogStateProvider>
|
</DialogStateProvider>
|
||||||
|
|||||||
@@ -983,6 +983,10 @@ export const atoms = {
|
|||||||
transition_none: web({
|
transition_none: web({
|
||||||
transitionProperty: 'none',
|
transitionProperty: 'none',
|
||||||
}),
|
}),
|
||||||
|
transition_timing_default: web({
|
||||||
|
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
|
||||||
|
transitionDuration: '100ms',
|
||||||
|
}),
|
||||||
transition_all: web({
|
transition_all: web({
|
||||||
transitionProperty: 'all',
|
transitionProperty: 'all',
|
||||||
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
|
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
|
import {atoms as a} from '#/alf'
|
||||||
|
|
||||||
export const TIP_SIZE = 12
|
export const TIP_SIZE = 12
|
||||||
|
export const MIN_X_SPACE = a.px_lg.paddingLeft
|
||||||
|
|||||||
@@ -14,12 +14,19 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|||||||
import {atoms as a, select, useTheme} from '#/alf'
|
import {atoms as a, select, useTheme} from '#/alf'
|
||||||
import {useOnGesture} from '#/components/hooks/useOnGesture'
|
import {useOnGesture} from '#/components/hooks/useOnGesture'
|
||||||
import {Portal} from '#/components/Portal'
|
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'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
const HALF_TIP = TIP_SIZE / 2
|
const HALF_TIP = TIP_SIZE / 2
|
||||||
const TIP_VISUAL_OFFSET = TIP_SIZE / 3
|
const TIP_VISUAL_OFFSET = TIP_SIZE / 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only needed on web
|
||||||
|
*/
|
||||||
|
export function Provider({children}: {children: React.ReactNode}) {
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
type TooltipContextType = {
|
type TooltipContextType = {
|
||||||
position: 'top' | 'bottom'
|
position: 'top' | 'bottom'
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -205,7 +212,7 @@ function Bubble({
|
|||||||
const maxTop = insets.top
|
const maxTop = insets.top
|
||||||
const maxBottom = wh - insets.bottom
|
const maxBottom = wh - insets.bottom
|
||||||
const {width: cw, height: ch} = bubbleMeasurements
|
const {width: cw, height: ch} = bubbleMeasurements
|
||||||
const minLeft = a.px_xl.paddingLeft
|
const minLeft = MIN_EDGE_SPACE
|
||||||
const maxLeft = ww - minLeft
|
const maxLeft = ww - minLeft
|
||||||
|
|
||||||
let computedPosition: 'top' | 'bottom' = position
|
let computedPosition: 'top' | 'bottom' = position
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -334,3 +334,38 @@ input[type='range'][orient='vertical']::-moz-range-thumb {
|
|||||||
min-width: var(--radix-select-trigger-width);
|
min-width: var(--radix-select-trigger-width);
|
||||||
max-height: var(--radix-select-content-available-height);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user