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

Configure Feed

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

at a876aae44ea07494ebea9727350aa060b81f317b 337 lines 8.6 kB view raw
1import {createContext, forwardRef, Fragment, useContext, useMemo} from 'react' 2import {View} from 'react-native' 3import {Select as RadixSelect} from 'radix-ui' 4 5import {useA11y} from '#/state/a11y' 6import {atoms as a, flatten, useTheme, web} from '#/alf' 7import {useInteractionState} from '#/components/hooks/useInteractionState' 8import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 9import { 10 ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon, 11 ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon, 12} from '#/components/icons/Chevron' 13import {Text} from '#/components/Typography' 14import { 15 type ContentProps, 16 type IconProps, 17 type ItemIndicatorProps, 18 type ItemProps, 19 type ItemTextProps, 20 type RadixPassThroughTriggerProps, 21 type RootProps, 22 type TriggerProps, 23 type ValueProps, 24} from './types' 25 26const SelectedValueContext = createContext<string | undefined | null>(null) 27SelectedValueContext.displayName = 'SelectSelectedValueContext' 28 29export function Root(props: RootProps) { 30 return ( 31 <SelectedValueContext.Provider value={props.value}> 32 <RadixSelect.Root {...props} /> 33 </SelectedValueContext.Provider> 34 ) 35} 36 37const RadixTriggerPassThrough = forwardRef( 38 ( 39 props: { 40 children: ( 41 props: RadixPassThroughTriggerProps & { 42 ref: React.Ref<any> 43 }, 44 ) => React.ReactNode 45 }, 46 ref, 47 ) => { 48 // @ts-expect-error Radix provides no types of this stuff 49 50 return props.children?.({...props, ref}) 51 }, 52) 53RadixTriggerPassThrough.displayName = 'RadixTriggerPassThrough' 54 55export function Trigger({children, label}: TriggerProps) { 56 const t = useTheme() 57 const { 58 state: hovered, 59 onIn: onMouseEnter, 60 onOut: onMouseLeave, 61 } = useInteractionState() 62 const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 63 64 if (typeof children === 'function') { 65 return ( 66 <RadixSelect.Trigger asChild> 67 <RadixTriggerPassThrough> 68 {props => 69 children({ 70 IS_NATIVE: false, 71 state: { 72 hovered, 73 focused, 74 pressed: false, 75 }, 76 props: { 77 ...props, 78 onPress: props.onClick, 79 onFocus: onFocus, 80 onBlur: onBlur, 81 onMouseEnter, 82 onMouseLeave, 83 accessibilityLabel: label, 84 }, 85 }) 86 } 87 </RadixTriggerPassThrough> 88 </RadixSelect.Trigger> 89 ) 90 } else { 91 return ( 92 <RadixSelect.Trigger 93 onFocus={onFocus} 94 onBlur={onBlur} 95 onMouseEnter={onMouseEnter} 96 onMouseLeave={onMouseLeave} 97 style={flatten([ 98 a.flex, 99 a.relative, 100 t.atoms.bg_contrast_50, 101 a.align_center, 102 a.gap_sm, 103 a.justify_between, 104 a.py_sm, 105 a.px_md, 106 a.pointer, 107 { 108 borderRadius: 10, 109 maxWidth: 400, 110 outline: 0, 111 borderWidth: 1, 112 borderStyle: 'solid', 113 borderColor: focused 114 ? t.palette.primary_500 115 : t.palette.contrast_50, 116 }, 117 ])}> 118 {children} 119 </RadixSelect.Trigger> 120 ) 121 } 122} 123 124export function ValueText({ 125 children, 126 webOverrideValue, 127 style, 128 ...props 129}: ValueProps) { 130 let content 131 132 if (webOverrideValue && children) { 133 content = children(webOverrideValue) 134 } 135 136 return ( 137 <Text style={style}> 138 <RadixSelect.Value {...props}>{content}</RadixSelect.Value> 139 </Text> 140 ) 141} 142 143export function Icon({style}: IconProps) { 144 const t = useTheme() 145 return ( 146 <RadixSelect.Icon> 147 <ChevronDownIcon style={[t.atoms.text, style]} size="xs" /> 148 </RadixSelect.Icon> 149 ) 150} 151 152export function Content<T>({ 153 items, 154 renderItem, 155 valueExtractor = defaultItemValueExtractor, 156}: ContentProps<T>) { 157 const t = useTheme() 158 const selectedValue = useContext(SelectedValueContext) 159 const {reduceMotionEnabled} = useA11y() 160 161 const scrollBtnStyles: React.CSSProperties[] = [ 162 a.absolute, 163 a.flex, 164 a.align_center, 165 a.justify_center, 166 a.rounded_sm, 167 a.z_10, 168 ] 169 const up: React.CSSProperties[] = [ 170 ...scrollBtnStyles, 171 a.pt_sm, 172 a.pb_lg, 173 { 174 top: 0, 175 left: 0, 176 right: 0, 177 borderBottomLeftRadius: 0, 178 borderBottomRightRadius: 0, 179 background: `linear-gradient(to bottom, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, 180 }, 181 ] 182 const down: React.CSSProperties[] = [ 183 ...scrollBtnStyles, 184 a.pt_lg, 185 a.pb_sm, 186 { 187 bottom: 0, 188 left: 0, 189 right: 0, 190 borderBottomLeftRadius: 0, 191 borderBottomRightRadius: 0, 192 background: `linear-gradient(to top, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, 193 }, 194 ] 195 196 return ( 197 <RadixSelect.Portal> 198 <RadixSelect.Content 199 style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])} 200 position="popper" 201 align="center" 202 sideOffset={5} 203 className="radix-select-content" 204 // prevent the keyboard shortcut for opening the composer 205 onKeyDown={evt => evt.stopPropagation()}> 206 <View 207 style={[ 208 a.flex_1, 209 a.border, 210 t.atoms.border_contrast_low, 211 a.rounded_sm, 212 a.overflow_hidden, 213 !reduceMotionEnabled && a.zoom_fade_in, 214 ]}> 215 <RadixSelect.ScrollUpButton style={flatten(up)}> 216 <ChevronUpIcon style={[t.atoms.text]} size="xs" /> 217 </RadixSelect.ScrollUpButton> 218 <RadixSelect.Viewport style={flatten([a.p_xs])}> 219 {items.map((item, index) => ( 220 <Fragment key={valueExtractor(item)}> 221 {renderItem(item, index, selectedValue)} 222 </Fragment> 223 ))} 224 </RadixSelect.Viewport> 225 <RadixSelect.ScrollDownButton style={flatten(down)}> 226 <ChevronDownIcon style={[t.atoms.text]} size="xs" /> 227 </RadixSelect.ScrollDownButton> 228 </View> 229 </RadixSelect.Content> 230 </RadixSelect.Portal> 231 ) 232} 233 234function defaultItemValueExtractor(item: any) { 235 return item.value 236} 237 238const ItemContext = createContext<{ 239 hovered: boolean 240 focused: boolean 241 pressed: boolean 242 selected: boolean 243}>({ 244 hovered: false, 245 focused: false, 246 pressed: false, 247 selected: false, 248}) 249ItemContext.displayName = 'SelectItemContext' 250 251export function useItemContext() { 252 return useContext(ItemContext) 253} 254 255export function Item({ref, value, style, children}: ItemProps) { 256 const t = useTheme() 257 const { 258 state: hovered, 259 onIn: onMouseEnter, 260 onOut: onMouseLeave, 261 } = useInteractionState() 262 const selected = useContext(SelectedValueContext) === value 263 const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 264 const ctx = useMemo( 265 () => ({hovered, focused, pressed: false, selected}), 266 [hovered, focused, selected], 267 ) 268 return ( 269 <RadixSelect.Item 270 ref={ref} 271 value={value} 272 onMouseEnter={onMouseEnter} 273 onMouseLeave={onMouseLeave} 274 onFocus={onFocus} 275 onBlur={onBlur} 276 style={flatten([ 277 t.atoms.text, 278 a.relative, 279 a.flex, 280 {minHeight: 25, paddingLeft: 30, paddingRight: 8}, 281 a.user_select_none, 282 a.align_center, 283 a.rounded_xs, 284 a.py_2xs, 285 a.text_sm, 286 {outline: 0}, 287 (hovered || focused) && {backgroundColor: t.palette.primary_50}, 288 selected && [a.font_semi_bold], 289 a.transition_color, 290 style, 291 ])}> 292 <ItemContext.Provider value={ctx}>{children}</ItemContext.Provider> 293 </RadixSelect.Item> 294 ) 295} 296 297export const ItemText = function ItemText({children, style}: ItemTextProps) { 298 return ( 299 <RadixSelect.ItemText asChild> 300 <Text style={flatten([style, web({pointerEvents: 'inherit'})])}> 301 {children} 302 </Text> 303 </RadixSelect.ItemText> 304 ) 305} 306 307export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) { 308 return ( 309 <RadixSelect.ItemIndicator 310 style={flatten([ 311 a.absolute, 312 {left: 0, width: 30}, 313 a.flex, 314 a.align_center, 315 a.justify_center, 316 ])}> 317 <Icon size="sm" /> 318 </RadixSelect.ItemIndicator> 319 ) 320} 321 322export function Separator() { 323 const t = useTheme() 324 325 return ( 326 <RadixSelect.Separator 327 style={flatten([ 328 { 329 height: 1, 330 backgroundColor: t.atoms.border_contrast_low.borderColor, 331 }, 332 a.my_xs, 333 a.w_full, 334 ])} 335 /> 336 ) 337}