Bluesky app fork with some witchin' additions 馃挮
at post-text-option 125 lines 3.4 kB view raw
1import {Children, createContext, useContext, useMemo} from 'react' 2import {View} from 'react-native' 3import {utils} from '@bsky.app/alf' 4import {Popover} from 'radix-ui' 5 6import {atoms as a, flatten, select, useTheme} from '#/alf' 7import { 8 ARROW_SIZE, 9 BUBBLE_MAX_WIDTH, 10 MIN_EDGE_SPACE, 11} from '#/components/Tooltip/const' 12import {Text} from '#/components/Typography' 13 14// Portal Provider on native, but we actually don't need to do anything here 15export function Provider({children}: {children: React.ReactNode}) { 16 return <>{children}</> 17} 18Provider.displayName = 'TooltipProvider' 19 20type TooltipContextType = { 21 position: 'top' | 'bottom' 22 onVisibleChange: (open: boolean) => void 23} 24 25const TooltipContext = createContext<Pick<TooltipContextType, 'position'>>({ 26 position: 'bottom', 27}) 28TooltipContext.displayName = 'TooltipContext' 29 30export function Outer({ 31 children, 32 position = 'bottom', 33 visible, 34 onVisibleChange, 35}: { 36 children: React.ReactNode 37 position?: 'top' | 'bottom' 38 visible: boolean 39 onVisibleChange: (visible: boolean) => void 40}) { 41 const ctx = useMemo(() => ({position}), [position]) 42 return ( 43 <Popover.Root open={visible} onOpenChange={onVisibleChange}> 44 <TooltipContext.Provider value={ctx}>{children}</TooltipContext.Provider> 45 </Popover.Root> 46 ) 47} 48 49export function Target({children}: {children: React.ReactNode}) { 50 return ( 51 <Popover.Trigger asChild> 52 <View collapsable={false}>{children}</View> 53 </Popover.Trigger> 54 ) 55} 56 57export function Content({ 58 children, 59 label, 60}: { 61 children: React.ReactNode 62 label: string 63}) { 64 const t = useTheme() 65 const {position} = useContext(TooltipContext) 66 return ( 67 <Popover.Portal> 68 <Popover.Content 69 className="radix-popover-content" 70 aria-label={label} 71 side={position} 72 sideOffset={4} 73 collisionPadding={MIN_EDGE_SPACE} 74 onInteractOutside={evt => { 75 if (evt.type === 'dismissableLayer.focusOutside') { 76 evt.preventDefault() 77 } 78 }} 79 style={flatten([ 80 a.rounded_sm, 81 select(t.name, { 82 light: t.atoms.bg, 83 dark: t.atoms.bg_contrast_100, 84 dim: t.atoms.bg_contrast_100, 85 }), 86 { 87 minWidth: 'max-content', 88 boxShadow: select(t.name, { 89 light: `0 0 24px ${utils.alpha(t.palette.black, 0.2)}`, 90 dark: `0 0 24px ${utils.alpha(t.palette.black, 0.2)}`, 91 dim: `0 0 24px ${utils.alpha(t.palette.black, 0.2)}`, 92 }), 93 }, 94 ])}> 95 <Popover.Arrow 96 width={ARROW_SIZE} 97 height={ARROW_SIZE / 2} 98 fill={select(t.name, { 99 light: t.atoms.bg.backgroundColor, 100 dark: t.atoms.bg_contrast_100.backgroundColor, 101 dim: t.atoms.bg_contrast_100.backgroundColor, 102 })} 103 /> 104 <View style={[a.px_md, a.py_sm, {maxWidth: BUBBLE_MAX_WIDTH}]}> 105 {children} 106 </View> 107 </Popover.Content> 108 </Popover.Portal> 109 ) 110} 111 112export function TextBubble({children}: {children: React.ReactNode}) { 113 const c = Children.toArray(children) 114 return ( 115 <Content label={c.join(' ')}> 116 <View style={[a.gap_xs]}> 117 {c.map((child, i) => ( 118 <Text key={i} style={[a.text_sm, a.leading_snug]}> 119 {child} 120 </Text> 121 ))} 122 </View> 123 </Content> 124 ) 125}