Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at 2d8e855e25e8722ee37c90a5413acd7a36d43318 296 lines 7.7 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 {flatten, useTheme} from '#/alf' 6import {atoms as a} 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 RadixPassThroughTriggerProps, 20 type RootProps, 21 type TriggerProps, 22 type ValueProps, 23} from './types' 24 25const SelectedValueContext = createContext<string | undefined | null>(null) 26SelectedValueContext.displayName = 'SelectSelectedValueContext' 27 28export function Root(props: RootProps) { 29 return ( 30 <SelectedValueContext.Provider value={props.value}> 31 <RadixSelect.Root {...props} /> 32 </SelectedValueContext.Provider> 33 ) 34} 35 36const RadixTriggerPassThrough = forwardRef( 37 ( 38 props: { 39 children: ( 40 props: RadixPassThroughTriggerProps & { 41 ref: React.Ref<any> 42 }, 43 ) => React.ReactNode 44 }, 45 ref, 46 ) => { 47 // @ts-expect-error Radix provides no types of this stuff 48 49 return props.children?.({...props, ref}) 50 }, 51) 52RadixTriggerPassThrough.displayName = 'RadixTriggerPassThrough' 53 54export function Trigger({children, label}: TriggerProps) { 55 const t = useTheme() 56 const { 57 state: hovered, 58 onIn: onMouseEnter, 59 onOut: onMouseLeave, 60 } = useInteractionState() 61 const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 62 63 if (typeof children === 'function') { 64 return ( 65 <RadixSelect.Trigger asChild> 66 <RadixTriggerPassThrough> 67 {props => 68 children({ 69 isNative: false, 70 state: { 71 hovered, 72 focused, 73 pressed: false, 74 }, 75 props: { 76 ...props, 77 onPress: props.onClick, 78 onFocus: onFocus, 79 onBlur: onBlur, 80 onMouseEnter, 81 onMouseLeave, 82 accessibilityLabel: label, 83 }, 84 }) 85 } 86 </RadixTriggerPassThrough> 87 </RadixSelect.Trigger> 88 ) 89 } else { 90 return ( 91 <RadixSelect.Trigger 92 onFocus={onFocus} 93 onBlur={onBlur} 94 onMouseEnter={onMouseEnter} 95 onMouseLeave={onMouseLeave} 96 style={flatten([ 97 a.flex, 98 a.relative, 99 t.atoms.bg_contrast_50, 100 a.w_full, 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: 2, 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({children: _, style, ...props}: ValueProps) { 125 return ( 126 <Text style={style}> 127 <RadixSelect.Value {...props} /> 128 </Text> 129 ) 130} 131 132export function Icon({style}: IconProps) { 133 const t = useTheme() 134 return ( 135 <RadixSelect.Icon> 136 <ChevronDownIcon style={[t.atoms.text, style]} size="xs" /> 137 </RadixSelect.Icon> 138 ) 139} 140 141export function Content<T>({ 142 items, 143 renderItem, 144 valueExtractor = defaultItemValueExtractor, 145}: ContentProps<T>) { 146 const t = useTheme() 147 const selectedValue = useContext(SelectedValueContext) 148 149 const scrollBtnStyles: React.CSSProperties[] = [ 150 a.absolute, 151 a.flex, 152 a.align_center, 153 a.justify_center, 154 a.rounded_sm, 155 a.z_10, 156 ] 157 const up: React.CSSProperties[] = [ 158 ...scrollBtnStyles, 159 a.pt_sm, 160 a.pb_lg, 161 { 162 top: 0, 163 left: 0, 164 right: 0, 165 borderBottomLeftRadius: 0, 166 borderBottomRightRadius: 0, 167 background: `linear-gradient(to bottom, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, 168 }, 169 ] 170 const down: React.CSSProperties[] = [ 171 ...scrollBtnStyles, 172 a.pt_lg, 173 a.pb_sm, 174 { 175 bottom: 0, 176 left: 0, 177 right: 0, 178 borderBottomLeftRadius: 0, 179 borderBottomRightRadius: 0, 180 background: `linear-gradient(to top, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, 181 }, 182 ] 183 184 return ( 185 <RadixSelect.Portal> 186 <RadixSelect.Content 187 style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])} 188 position="popper" 189 sideOffset={5} 190 className="radix-select-content"> 191 <View 192 style={[ 193 a.flex_1, 194 a.border, 195 t.atoms.border_contrast_low, 196 a.rounded_sm, 197 a.overflow_hidden, 198 ]}> 199 <RadixSelect.ScrollUpButton style={flatten(up)}> 200 <ChevronUpIcon style={[t.atoms.text]} size="xs" /> 201 </RadixSelect.ScrollUpButton> 202 <RadixSelect.Viewport style={flatten([a.p_xs])}> 203 {items.map((item, index) => ( 204 <Fragment key={valueExtractor(item)}> 205 {renderItem(item, index, selectedValue)} 206 </Fragment> 207 ))} 208 </RadixSelect.Viewport> 209 <RadixSelect.ScrollDownButton style={flatten(down)}> 210 <ChevronDownIcon style={[t.atoms.text]} size="xs" /> 211 </RadixSelect.ScrollDownButton> 212 </View> 213 </RadixSelect.Content> 214 </RadixSelect.Portal> 215 ) 216} 217 218function defaultItemValueExtractor(item: any) { 219 return item.value 220} 221 222const ItemContext = createContext<{ 223 hovered: boolean 224 focused: boolean 225 pressed: boolean 226 selected: boolean 227}>({ 228 hovered: false, 229 focused: false, 230 pressed: false, 231 selected: false, 232}) 233ItemContext.displayName = 'SelectItemContext' 234 235export function useItemContext() { 236 return useContext(ItemContext) 237} 238 239export function Item({ref, value, style, children}: ItemProps) { 240 const t = useTheme() 241 const { 242 state: hovered, 243 onIn: onMouseEnter, 244 onOut: onMouseLeave, 245 } = useInteractionState() 246 const selected = useContext(SelectedValueContext) === value 247 const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 248 const ctx = useMemo( 249 () => ({hovered, focused, pressed: false, selected}), 250 [hovered, focused, selected], 251 ) 252 return ( 253 <RadixSelect.Item 254 ref={ref} 255 value={value} 256 onMouseEnter={onMouseEnter} 257 onMouseLeave={onMouseLeave} 258 onFocus={onFocus} 259 onBlur={onBlur} 260 style={flatten([ 261 t.atoms.text, 262 a.relative, 263 a.flex, 264 {minHeight: 25, paddingLeft: 30, paddingRight: 35}, 265 a.user_select_none, 266 a.align_center, 267 a.rounded_xs, 268 a.py_2xs, 269 a.text_sm, 270 {outline: 0}, 271 (hovered || focused) && {backgroundColor: t.palette.primary_50}, 272 selected && [a.font_semi_bold], 273 a.transition_color, 274 style, 275 ])}> 276 <ItemContext.Provider value={ctx}>{children}</ItemContext.Provider> 277 </RadixSelect.Item> 278 ) 279} 280 281export const ItemText = RadixSelect.ItemText 282 283export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) { 284 return ( 285 <RadixSelect.ItemIndicator 286 style={flatten([ 287 a.absolute, 288 {left: 0, width: 30}, 289 a.flex, 290 a.align_center, 291 a.justify_center, 292 ])}> 293 <Icon size="sm" /> 294 </RadixSelect.ItemIndicator> 295 ) 296}