Make the pager take full width (#7066)

* Wide tabs for web

* Wide tabs on mobile

* Tweak min for profile

* Driveby border fix

* Fix single tab indicator
This commit is contained in:
dan
2024-12-12 15:35:56 +00:00
committed by GitHub
parent 704e36c280
commit 88166926fa
3 changed files with 69 additions and 12 deletions
+1 -1
View File
@@ -107,7 +107,7 @@ export default function HashtagScreen({
return ( return (
<Layout.Screen> <Layout.Screen>
<Layout.Header.Outer> <Layout.Header.Outer noBottomBorder>
<Layout.Header.BackButton /> <Layout.Header.BackButton />
<Layout.Header.Content> <Layout.Header.Content>
<Layout.Header.TitleText>{headerTitle}</Layout.Header.TitleText> <Layout.Header.TitleText>{headerTitle}</Layout.Header.TitleText>
+48 -10
View File
@@ -51,6 +51,7 @@ export function TabBar({
const containerSize = useSharedValue(0) const containerSize = useSharedValue(0)
const scrollX = useSharedValue(0) const scrollX = useSharedValue(0)
const layouts = useSharedValue<{x: number; width: number}[]>([]) const layouts = useSharedValue<{x: number; width: number}[]>([])
const textLayouts = useSharedValue<{width: number}[]>([])
const itemsLength = items.length const itemsLength = items.length
const scrollToOffsetJS = useCallback( const scrollToOffsetJS = useCallback(
@@ -211,21 +212,40 @@ export function TabBar({
[layouts], [layouts],
) )
const onTextLayout = useCallback(
(i: number, layout: {width: number}) => {
'worklet'
textLayouts.modify(ls => {
ls[i] = layout
return ls
})
},
[textLayouts],
)
const indicatorStyle = useAnimatedStyle(() => { const indicatorStyle = useAnimatedStyle(() => {
if (!_WORKLET) { if (!_WORKLET) {
return {opacity: 0} return {opacity: 0}
} }
const layoutsValue = layouts.get() const layoutsValue = layouts.get()
const textLayoutsValue = textLayouts.get()
if ( if (
layoutsValue.length !== itemsLength || layoutsValue.length !== itemsLength ||
layoutsValue.some(l => l === undefined) textLayoutsValue.length !== itemsLength
) { ) {
return { return {
opacity: 0, opacity: 0,
} }
} }
if (layoutsValue.length === 1) { if (textLayoutsValue.length === 1) {
return {opacity: 1} return {
opacity: 1,
transform: [
{
scaleX: textLayoutsValue[0].width / contentSize.get(),
},
],
}
} }
return { return {
opacity: 1, opacity: 1,
@@ -240,10 +260,8 @@ export function TabBar({
{ {
scaleX: interpolate( scaleX: interpolate(
dragProgress.get(), dragProgress.get(),
layoutsValue.map((l, i) => i), textLayoutsValue.map((l, i) => i),
layoutsValue.map( textLayoutsValue.map(l => l.width / contentSize.get()),
l => (l.width - ITEM_PADDING * 2) / contentSize.get(),
),
), ),
}, },
], ],
@@ -287,7 +305,7 @@ export function TabBar({
onLayout={e => { onLayout={e => {
contentSize.set(e.nativeEvent.layout.width) contentSize.set(e.nativeEvent.layout.width)
}} }}
style={{flexDirection: 'row'}}> style={{flexDirection: 'row', flexGrow: 1}}>
{items.map((item, i) => { {items.map((item, i) => {
return ( return (
<TabBarItem <TabBarItem
@@ -298,6 +316,7 @@ export function TabBar({
item={item} item={item}
onPressItem={onPressItem} onPressItem={onPressItem}
onItemLayout={onItemLayout} onItemLayout={onItemLayout}
onTextLayout={onTextLayout}
/> />
) )
})} })}
@@ -328,6 +347,7 @@ function TabBarItem({
item, item,
onPressItem, onPressItem,
onItemLayout, onItemLayout,
onTextLayout,
}: { }: {
index: number index: number
testID: string | undefined testID: string | undefined
@@ -335,6 +355,7 @@ function TabBarItem({
item: string item: string
onPressItem: (index: number) => void onPressItem: (index: number) => void
onItemLayout: (index: number, layout: {x: number; width: number}) => void onItemLayout: (index: number, layout: {x: number; width: number}) => void
onTextLayout: (index: number, layout: {width: number}) => void
}) { }) {
const t = useTheme() const t = useTheme()
const style = useAnimatedStyle(() => { const style = useAnimatedStyle(() => {
@@ -358,8 +379,15 @@ function TabBarItem({
[index, onItemLayout], [index, onItemLayout],
) )
const handleTextLayout = useCallback(
(e: LayoutChangeEvent) => {
runOnUI(onTextLayout)(index, e.nativeEvent.layout)
},
[index, onTextLayout],
)
return ( return (
<View onLayout={handleLayout}> <View onLayout={handleLayout} style={{flexGrow: 1}}>
<PressableWithHover <PressableWithHover
testID={`${testID}-selector-${index}`} testID={`${testID}-selector-${index}`}
style={styles.item} style={styles.item}
@@ -370,7 +398,8 @@ function TabBarItem({
<Text <Text
emoji emoji
testID={testID ? `${testID}-${item}` : undefined} testID={testID ? `${testID}-${item}` : undefined}
style={[t.atoms.text, a.text_md, a.font_bold, {lineHeight: 20}]}> style={[styles.itemText, t.atoms.text, a.text_md, a.font_bold]}
onLayout={handleTextLayout}>
{item} {item}
</Text> </Text>
</Animated.View> </Animated.View>
@@ -381,19 +410,28 @@ function TabBarItem({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
contentContainer: { contentContainer: {
flexGrow: 1,
backgroundColor: 'transparent', backgroundColor: 'transparent',
paddingHorizontal: CONTENT_PADDING, paddingHorizontal: CONTENT_PADDING,
}, },
item: { item: {
flexGrow: 1,
paddingTop: 10, paddingTop: 10,
paddingHorizontal: ITEM_PADDING, paddingHorizontal: ITEM_PADDING,
justifyContent: 'center', justifyContent: 'center',
}, },
itemInner: { itemInner: {
alignItems: 'center',
flexGrow: 1,
paddingBottom: 10, paddingBottom: 10,
borderBottomWidth: 3, borderBottomWidth: 3,
borderBottomColor: 'transparent', borderBottomColor: 'transparent',
}, },
itemText: {
lineHeight: 20,
minWidth: 45,
textAlign: 'center',
},
outerBottomBorder: { outerBottomBorder: {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
+20 -1
View File
@@ -115,12 +115,14 @@ export function TabBar({
hoverStyle={t.atoms.bg_contrast_25} hoverStyle={t.atoms.bg_contrast_25}
onPress={() => onPressItem(i)} onPress={() => onPressItem(i)}
accessibilityRole="tab"> accessibilityRole="tab">
<View style={[styles.itemInner, selected && indicatorStyle]}> <View style={styles.itemInner}>
<Text <Text
emoji emoji
testID={testID ? `${testID}-${item}` : undefined} testID={testID ? `${testID}-${item}` : undefined}
style={[ style={[
styles.itemText,
selected ? t.atoms.text : t.atoms.text_contrast_medium, selected ? t.atoms.text : t.atoms.text_contrast_medium,
selected && indicatorStyle,
a.text_md, a.text_md,
a.font_bold, a.font_bold,
{lineHeight: 20}, {lineHeight: 20},
@@ -143,15 +145,23 @@ const desktopStyles = StyleSheet.create({
width: 598, width: 598,
}, },
contentContainer: { contentContainer: {
flexGrow: 1,
paddingHorizontal: 0, paddingHorizontal: 0,
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
item: { item: {
flexGrow: 1,
alignItems: 'stretch',
paddingTop: 14, paddingTop: 14,
paddingHorizontal: 14, paddingHorizontal: 14,
justifyContent: 'center', justifyContent: 'center',
}, },
itemInner: { itemInner: {
alignItems: 'center',
},
itemText: {
textAlign: 'center',
minWidth: 45,
paddingBottom: 12, paddingBottom: 12,
borderBottomWidth: 3, borderBottomWidth: 3,
borderBottomColor: 'transparent', borderBottomColor: 'transparent',
@@ -170,15 +180,24 @@ const mobileStyles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
}, },
contentContainer: { contentContainer: {
flexGrow: 1,
backgroundColor: 'transparent', backgroundColor: 'transparent',
paddingHorizontal: 6, paddingHorizontal: 6,
}, },
item: { item: {
flexGrow: 1,
alignItems: 'stretch',
paddingTop: 10, paddingTop: 10,
paddingHorizontal: 10, paddingHorizontal: 10,
justifyContent: 'center', justifyContent: 'center',
}, },
itemInner: { itemInner: {
flexGrow: 1,
alignItems: 'center',
},
itemText: {
textAlign: 'center',
minWidth: 45,
paddingBottom: 10, paddingBottom: 10,
borderBottomWidth: 2, borderBottomWidth: 2,
borderBottomColor: 'transparent', borderBottomColor: 'transparent',