Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
117
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 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}