Add suggested follows experiment to onboarding (#8847)
* add new gated screen to onboarding * add tab bar, adjust layout * replace chevron with arrow * get suggested accounts working on native * tweaks for web * add metrics to follow all * rm non-functional link from card * ensure selected interests are passed through to interests query * fix logcontext * followed all accounts! toast * rm save interests function * Update src/screens/Onboarding/StepSuggestedAccounts/index.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * use admonition * rm comment * Better interest tabs (#8879) * make tabs draggable * move tab component to own file * rm focused state from tab, improve label * add focus styles, remove focus when dragging * rm log * add arrows to tabs * rename Tabs -> InterestTabs * try and simplify approach * rename ref * Update InterestTabs.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/InterestTabs.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/ProgressGuide/FollowDialog.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/ProgressGuide/FollowDialog.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * add newline --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * fix flex problem * Add value proposition screen experiment (#8898) * add assets * add value prop experiment * add alt text * add metrics * add transitions * add skip button * tweak copy Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * add borderless variant for web * rm pointer events --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Add slight delay, prevent layout shift * Handle layout shift, add Let's Go! text --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
import {useEffect, useRef, useState} from 'react'
|
||||
import {
|
||||
type ScrollView,
|
||||
type StyleProp,
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {DraggableScrollView} from '#/view/com/pager/DraggableScrollView'
|
||||
import {atoms as a, tokens, useTheme, web} from '#/alf'
|
||||
import {transparentifyColor} from '#/alf/util/colorGeneration'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {
|
||||
ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft,
|
||||
ArrowRight_Stroke2_Corner0_Rounded as ArrowRight,
|
||||
} from '#/components/icons/Arrow'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
/**
|
||||
* Tab component that automatically scrolls the selected tab into view - used for interests
|
||||
* in the Find Follows dialog, Explore screen, etc.
|
||||
*/
|
||||
export function InterestTabs({
|
||||
onSelectTab,
|
||||
interests,
|
||||
selectedInterest,
|
||||
disabled,
|
||||
interestsDisplayNames,
|
||||
TabComponent = Tab,
|
||||
contentContainerStyle,
|
||||
gutterWidth = tokens.space.lg,
|
||||
}: {
|
||||
onSelectTab: (tab: string) => void
|
||||
interests: string[]
|
||||
selectedInterest: string
|
||||
interestsDisplayNames: Record<string, string>
|
||||
/** still allows changing tab, but removes the active state from the selected tab */
|
||||
disabled?: boolean
|
||||
TabComponent?: React.ComponentType<React.ComponentProps<typeof Tab>>
|
||||
contentContainerStyle?: StyleProp<ViewStyle>
|
||||
gutterWidth?: number
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const listRef = useRef<ScrollView>(null)
|
||||
const [totalWidth, setTotalWidth] = useState(0)
|
||||
const [scrollX, setScrollX] = useState(0)
|
||||
const [contentWidth, setContentWidth] = useState(0)
|
||||
const pendingTabOffsets = useRef<{x: number; width: number}[]>([])
|
||||
const [tabOffsets, setTabOffsets] = useState<{x: number; width: number}[]>([])
|
||||
|
||||
const onInitialLayout = useNonReactiveCallback(() => {
|
||||
const index = interests.indexOf(selectedInterest)
|
||||
scrollIntoViewIfNeeded(index)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (tabOffsets) {
|
||||
onInitialLayout()
|
||||
}
|
||||
}, [tabOffsets, onInitialLayout])
|
||||
|
||||
function scrollIntoViewIfNeeded(index: number) {
|
||||
const btnLayout = tabOffsets[index]
|
||||
if (!btnLayout) return
|
||||
listRef.current?.scrollTo({
|
||||
// centered
|
||||
x: btnLayout.x - (totalWidth / 2 - btnLayout.width / 2),
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
|
||||
function handleSelectTab(index: number) {
|
||||
const tab = interests[index]
|
||||
onSelectTab(tab)
|
||||
scrollIntoViewIfNeeded(index)
|
||||
}
|
||||
|
||||
function handleTabLayout(index: number, x: number, width: number) {
|
||||
if (!tabOffsets.length) {
|
||||
pendingTabOffsets.current[index] = {x, width}
|
||||
if (pendingTabOffsets.current.length === interests.length) {
|
||||
setTabOffsets(pendingTabOffsets.current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const canScrollLeft = scrollX > 0
|
||||
const canScrollRight = scrollX < contentWidth - totalWidth
|
||||
|
||||
const cleanupRef = useRef<(() => void) | null>(null)
|
||||
|
||||
function scrollLeft() {
|
||||
if (isContinuouslyScrollingRef.current) {
|
||||
return
|
||||
}
|
||||
if (listRef.current && canScrollLeft) {
|
||||
const newScrollX = Math.max(0, scrollX - 200)
|
||||
listRef.current.scrollTo({x: newScrollX, animated: true})
|
||||
}
|
||||
}
|
||||
|
||||
function scrollRight() {
|
||||
if (isContinuouslyScrollingRef.current) {
|
||||
return
|
||||
}
|
||||
if (listRef.current && canScrollRight) {
|
||||
const maxScroll = contentWidth - totalWidth
|
||||
const newScrollX = Math.min(maxScroll, scrollX + 200)
|
||||
listRef.current.scrollTo({x: newScrollX, animated: true})
|
||||
}
|
||||
}
|
||||
|
||||
const isContinuouslyScrollingRef = useRef(false)
|
||||
|
||||
function startContinuousScroll(direction: 'left' | 'right') {
|
||||
// Clear any existing continuous scroll
|
||||
if (cleanupRef.current) {
|
||||
cleanupRef.current()
|
||||
}
|
||||
|
||||
let holdTimeout: NodeJS.Timeout | null = null
|
||||
let animationFrame: number | null = null
|
||||
let isActive = true
|
||||
isContinuouslyScrollingRef.current = false
|
||||
|
||||
const cleanup = () => {
|
||||
isActive = false
|
||||
if (holdTimeout) clearTimeout(holdTimeout)
|
||||
if (animationFrame) cancelAnimationFrame(animationFrame)
|
||||
cleanupRef.current = null
|
||||
// Reset flag after a delay to prevent onPress from firing
|
||||
setTimeout(() => {
|
||||
isContinuouslyScrollingRef.current = false
|
||||
}, 100)
|
||||
}
|
||||
|
||||
cleanupRef.current = cleanup
|
||||
|
||||
// Start continuous scrolling after hold delay
|
||||
holdTimeout = setTimeout(() => {
|
||||
if (!isActive) return
|
||||
|
||||
isContinuouslyScrollingRef.current = true
|
||||
let currentScrollPosition = scrollX
|
||||
|
||||
const scroll = () => {
|
||||
if (!isActive || !listRef.current) return
|
||||
|
||||
const scrollAmount = 3
|
||||
const maxScroll = contentWidth - totalWidth
|
||||
|
||||
let newScrollX: number
|
||||
let canContinue = false
|
||||
|
||||
if (direction === 'left' && currentScrollPosition > 0) {
|
||||
newScrollX = Math.max(0, currentScrollPosition - scrollAmount)
|
||||
canContinue = newScrollX > 0
|
||||
} else if (direction === 'right' && currentScrollPosition < maxScroll) {
|
||||
newScrollX = Math.min(maxScroll, currentScrollPosition + scrollAmount)
|
||||
canContinue = newScrollX < maxScroll
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
currentScrollPosition = newScrollX
|
||||
listRef.current.scrollTo({x: newScrollX, animated: false})
|
||||
|
||||
if (canContinue && isActive) {
|
||||
animationFrame = requestAnimationFrame(scroll)
|
||||
}
|
||||
}
|
||||
|
||||
scroll()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function stopContinuousScroll() {
|
||||
if (cleanupRef.current) {
|
||||
cleanupRef.current()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (cleanupRef.current) {
|
||||
cleanupRef.current()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={[a.relative, a.flex_row]}>
|
||||
<DraggableScrollView
|
||||
ref={listRef}
|
||||
contentContainerStyle={[
|
||||
a.gap_sm,
|
||||
{paddingHorizontal: gutterWidth},
|
||||
contentContainerStyle,
|
||||
]}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
decelerationRate="fast"
|
||||
snapToOffsets={
|
||||
tabOffsets.length === interests.length
|
||||
? tabOffsets.map(o => o.x - tokens.space.xl)
|
||||
: undefined
|
||||
}
|
||||
onLayout={evt => setTotalWidth(evt.nativeEvent.layout.width)}
|
||||
onContentSizeChange={width => setContentWidth(width)}
|
||||
onScroll={evt => {
|
||||
const newScrollX = evt.nativeEvent.contentOffset.x
|
||||
setScrollX(newScrollX)
|
||||
}}
|
||||
scrollEventThrottle={16}>
|
||||
{interests.map((interest, i) => {
|
||||
const active = interest === selectedInterest && !disabled
|
||||
return (
|
||||
<TabComponent
|
||||
key={interest}
|
||||
onSelectTab={handleSelectTab}
|
||||
active={active}
|
||||
index={i}
|
||||
interest={interest}
|
||||
interestsDisplayName={interestsDisplayNames[interest]}
|
||||
onLayout={handleTabLayout}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</DraggableScrollView>
|
||||
{isWeb && canScrollLeft && (
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.top_0,
|
||||
a.left_0,
|
||||
a.bottom_0,
|
||||
a.justify_center,
|
||||
{paddingLeft: gutterWidth},
|
||||
a.pr_md,
|
||||
a.z_10,
|
||||
web({
|
||||
background: `linear-gradient(to right, ${t.atoms.bg.backgroundColor} 0%, ${t.atoms.bg.backgroundColor} 70%, ${transparentifyColor(t.atoms.bg.backgroundColor, 0)} 100%)`,
|
||||
}),
|
||||
]}>
|
||||
<Button
|
||||
label={_(msg`Scroll left`)}
|
||||
onPress={scrollLeft}
|
||||
onPressIn={() => startContinuousScroll('left')}
|
||||
onPressOut={stopContinuousScroll}
|
||||
color="secondary"
|
||||
size="small"
|
||||
style={[
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg,
|
||||
a.h_full,
|
||||
{aspectRatio: 1},
|
||||
a.rounded_full,
|
||||
]}>
|
||||
<ButtonIcon icon={ArrowLeft} />
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
{isWeb && canScrollRight && (
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.top_0,
|
||||
a.right_0,
|
||||
a.bottom_0,
|
||||
a.justify_center,
|
||||
{paddingRight: gutterWidth},
|
||||
a.pl_md,
|
||||
a.z_10,
|
||||
web({
|
||||
background: `linear-gradient(to left, ${t.atoms.bg.backgroundColor} 0%, ${t.atoms.bg.backgroundColor} 70%, ${transparentifyColor(t.atoms.bg.backgroundColor, 0)} 100%)`,
|
||||
}),
|
||||
]}>
|
||||
<Button
|
||||
label={_(msg`Scroll right`)}
|
||||
onPress={scrollRight}
|
||||
onPressIn={() => startContinuousScroll('right')}
|
||||
onPressOut={stopContinuousScroll}
|
||||
color="secondary"
|
||||
size="small"
|
||||
style={[
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg,
|
||||
a.h_full,
|
||||
{aspectRatio: 1},
|
||||
a.rounded_full,
|
||||
]}>
|
||||
<ButtonIcon icon={ArrowRight} />
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function Tab({
|
||||
onSelectTab,
|
||||
interest,
|
||||
active,
|
||||
index,
|
||||
interestsDisplayName,
|
||||
onLayout,
|
||||
}: {
|
||||
onSelectTab: (index: number) => void
|
||||
interest: string
|
||||
active: boolean
|
||||
index: number
|
||||
interestsDisplayName: string
|
||||
onLayout: (index: number, x: number, width: number) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const label = active
|
||||
? _(
|
||||
msg({
|
||||
message: `"${interestsDisplayName}" category (active)`,
|
||||
comment:
|
||||
'Accessibility label for a category (e.g. Art, Video Games, Sports, etc.) that shows suggested accounts for the user to follow. The tab is currently selected.',
|
||||
}),
|
||||
)
|
||||
: _(
|
||||
msg({
|
||||
message: `Select "${interestsDisplayName}" category`,
|
||||
comment:
|
||||
'Accessibility label for a category (e.g. Art, Video Games, Sports, etc.) that shows suggested accounts for the user to follow. The tab is not currently active and can be selected.',
|
||||
}),
|
||||
)
|
||||
|
||||
return (
|
||||
<View
|
||||
key={interest}
|
||||
onLayout={e =>
|
||||
onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width)
|
||||
}>
|
||||
<Button
|
||||
label={label}
|
||||
onPress={() => onSelectTab(index)}
|
||||
// disable focus ring, we handle it
|
||||
style={web({outline: 'none'})}>
|
||||
{({hovered, pressed, focused}) => (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.px_lg,
|
||||
a.py_sm,
|
||||
a.border,
|
||||
active || hovered || pressed
|
||||
? [t.atoms.bg_contrast_25, t.atoms.border_contrast_medium]
|
||||
: focused
|
||||
? {
|
||||
borderColor: t.palette.primary_300,
|
||||
backgroundColor: t.palette.primary_25,
|
||||
}
|
||||
: [t.atoms.bg, t.atoms.border_contrast_low],
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
a.font_medium,
|
||||
active || hovered || pressed
|
||||
? t.atoms.text
|
||||
: t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{interestsDisplayName}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function boostInterests(boosts?: string[]) {
|
||||
return (_a: string, _b: string) => {
|
||||
const indexA = boosts?.indexOf(_a) ?? -1
|
||||
const indexB = boosts?.indexOf(_b) ?? -1
|
||||
const rankA = indexA === -1 ? Infinity : indexA
|
||||
const rankB = indexB === -1 ? Infinity : indexB
|
||||
return rankA - rankB
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
||||
import {
|
||||
ScrollView,
|
||||
type StyleProp,
|
||||
TextInput,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {TextInput, useWindowDimensions, View} from 'react-native'
|
||||
import {type ModerationOpts} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
@@ -28,7 +20,6 @@ import {
|
||||
import {
|
||||
atoms as a,
|
||||
native,
|
||||
tokens,
|
||||
useBreakpoints,
|
||||
useTheme,
|
||||
type ViewStyleProp,
|
||||
@@ -40,6 +31,7 @@ import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2'
|
||||
import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import {boostInterests, InterestTabs} from '#/components/InterestTabs'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {Text} from '#/components/Typography'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
@@ -337,12 +329,13 @@ let Header = ({
|
||||
}}
|
||||
onEscape={control.close}
|
||||
/>
|
||||
<Tabs
|
||||
<InterestTabs
|
||||
onSelectTab={onSelectTab}
|
||||
interests={interests}
|
||||
selectedInterest={selectedInterest}
|
||||
hasSearchText={!!searchText}
|
||||
disabled={!!searchText}
|
||||
interestsDisplayNames={interestsDisplayNames}
|
||||
TabComponent={Tab}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
@@ -403,99 +396,6 @@ function HeaderTop({guide}: {guide: Follow10ProgressGuide}) {
|
||||
)
|
||||
}
|
||||
|
||||
let Tabs = ({
|
||||
onSelectTab,
|
||||
interests,
|
||||
selectedInterest,
|
||||
hasSearchText,
|
||||
interestsDisplayNames,
|
||||
TabComponent = Tab,
|
||||
contentContainerStyle,
|
||||
}: {
|
||||
onSelectTab: (tab: string) => void
|
||||
interests: string[]
|
||||
selectedInterest: string
|
||||
hasSearchText: boolean
|
||||
interestsDisplayNames: Record<string, string>
|
||||
TabComponent?: React.ComponentType<React.ComponentProps<typeof Tab>>
|
||||
contentContainerStyle?: StyleProp<ViewStyle>
|
||||
}): React.ReactNode => {
|
||||
const listRef = useRef<ScrollView>(null)
|
||||
const [totalWidth, setTotalWidth] = useState(0)
|
||||
const pendingTabOffsets = useRef<{x: number; width: number}[]>([])
|
||||
const [tabOffsets, setTabOffsets] = useState<{x: number; width: number}[]>([])
|
||||
|
||||
const onInitialLayout = useNonReactiveCallback(() => {
|
||||
const index = interests.indexOf(selectedInterest)
|
||||
scrollIntoViewIfNeeded(index)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (tabOffsets) {
|
||||
onInitialLayout()
|
||||
}
|
||||
}, [tabOffsets, onInitialLayout])
|
||||
|
||||
function scrollIntoViewIfNeeded(index: number) {
|
||||
const btnLayout = tabOffsets[index]
|
||||
if (!btnLayout) return
|
||||
listRef.current?.scrollTo({
|
||||
// centered
|
||||
x: btnLayout.x - (totalWidth / 2 - btnLayout.width / 2),
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
|
||||
function handleSelectTab(index: number) {
|
||||
const tab = interests[index]
|
||||
onSelectTab(tab)
|
||||
scrollIntoViewIfNeeded(index)
|
||||
}
|
||||
|
||||
function handleTabLayout(index: number, x: number, width: number) {
|
||||
if (!tabOffsets.length) {
|
||||
pendingTabOffsets.current[index] = {x, width}
|
||||
if (pendingTabOffsets.current.length === interests.length) {
|
||||
setTabOffsets(pendingTabOffsets.current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
ref={listRef}
|
||||
horizontal
|
||||
contentContainerStyle={[a.gap_sm, a.px_lg, contentContainerStyle]}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
decelerationRate="fast"
|
||||
snapToOffsets={
|
||||
tabOffsets.length === interests.length
|
||||
? tabOffsets.map(o => o.x - tokens.space.xl)
|
||||
: undefined
|
||||
}
|
||||
onLayout={evt => setTotalWidth(evt.nativeEvent.layout.width)}
|
||||
scrollEventThrottle={200} // big throttle
|
||||
>
|
||||
{interests.map((interest, i) => {
|
||||
const active = interest === selectedInterest && !hasSearchText
|
||||
return (
|
||||
<TabComponent
|
||||
key={interest}
|
||||
onSelectTab={handleSelectTab}
|
||||
active={active}
|
||||
index={i}
|
||||
interest={interest}
|
||||
interestsDisplayName={interestsDisplayNames[interest]}
|
||||
onLayout={handleTabLayout}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
Tabs = memo(Tabs)
|
||||
export {Tabs}
|
||||
|
||||
let Tab = ({
|
||||
onSelectTab,
|
||||
interest,
|
||||
@@ -513,24 +413,36 @@ let Tab = ({
|
||||
}): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const activeText = active ? _(msg` (active)`) : ''
|
||||
const label = active
|
||||
? _(
|
||||
msg({
|
||||
message: `Search for "${interestsDisplayName}" (active)`,
|
||||
comment:
|
||||
'Accessibility label for a tab that searches for accounts in a category (e.g. Art, Video Games, Sports, etc.) that are suggested for the user to follow. The tab is currently selected.',
|
||||
}),
|
||||
)
|
||||
: _(
|
||||
msg({
|
||||
message: `Search for "${interestsDisplayName}`,
|
||||
comment:
|
||||
'Accessibility label for a tab that searches for accounts in a category (e.g. Art, Video Games, Sports, etc.) that are suggested for the user to follow. The tab is not currently active and can be selected.',
|
||||
}),
|
||||
)
|
||||
return (
|
||||
<View
|
||||
key={interest}
|
||||
onLayout={e =>
|
||||
onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width)
|
||||
}>
|
||||
<Button
|
||||
label={_(msg`Search for "${interestsDisplayName}"${activeText}`)}
|
||||
onPress={() => onSelectTab(index)}>
|
||||
{({hovered, pressed, focused}) => (
|
||||
<Button label={label} onPress={() => onSelectTab(index)}>
|
||||
{({hovered, pressed}) => (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.px_lg,
|
||||
a.py_sm,
|
||||
a.border,
|
||||
active || hovered || pressed || focused
|
||||
active || hovered || pressed
|
||||
? [
|
||||
t.atoms.bg_contrast_25,
|
||||
{borderColor: t.atoms.bg_contrast_25.backgroundColor},
|
||||
@@ -540,7 +452,7 @@ let Tab = ({
|
||||
<Text
|
||||
style={[
|
||||
a.font_medium,
|
||||
active || hovered || pressed || focused
|
||||
active || hovered || pressed
|
||||
? t.atoms.text
|
||||
: t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
@@ -759,13 +671,3 @@ function Empty({message}: {message: string}) {
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function boostInterests(boosts?: string[]) {
|
||||
return (_a: string, _b: string) => {
|
||||
const indexA = boosts?.indexOf(_a) ?? -1
|
||||
const indexB = boosts?.indexOf(_b) ?? -1
|
||||
const rankA = indexA === -1 ? Infinity : indexA
|
||||
const rankB = indexB === -1 ? Infinity : indexB
|
||||
return rankA - rankB
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user