diff --git a/src/alf/breakpoints.ts b/src/alf/breakpoints.ts new file mode 100644 index 0000000000..9345856449 --- /dev/null +++ b/src/alf/breakpoints.ts @@ -0,0 +1,28 @@ +import {useMemo} from 'react' +import {useMediaQuery} from 'react-responsive' + +export type Breakpoint = 'gtPhone' | 'gtMobile' | 'gtTablet' + +export function useBreakpoints(): Record & { + activeBreakpoint: Breakpoint | undefined +} { + const gtPhone = useMediaQuery({minWidth: 500}) + const gtMobile = useMediaQuery({minWidth: 800}) + const gtTablet = useMediaQuery({minWidth: 1300}) + return useMemo(() => { + let active: Breakpoint | undefined + if (gtTablet) { + active = 'gtTablet' + } else if (gtMobile) { + active = 'gtMobile' + } else if (gtPhone) { + active = 'gtPhone' + } + return { + activeBreakpoint: active, + gtPhone, + gtMobile, + gtTablet, + } + }, [gtPhone, gtMobile, gtTablet]) +} diff --git a/src/alf/index.tsx b/src/alf/index.tsx index a96803c561..5443669c70 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -1,5 +1,4 @@ import React from 'react' -import {useMediaQuery} from 'react-responsive' import { computeFontScaleMultiplier, @@ -14,13 +13,14 @@ import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration' import {Device} from '#/storage' export {atoms} from '#/alf/atoms' +export * from '#/alf/breakpoints' export * from '#/alf/fonts' export * as tokens from '#/alf/tokens' export * from '#/alf/types' export * from '#/alf/util/flatten' export * from '#/alf/util/platform' export * from '#/alf/util/themeSelector' -export * from '#/alf/util/useGutterStyles' +export * from '#/alf/util/useGutters' export type Alf = { themeName: ThemeName @@ -142,14 +142,3 @@ export function useTheme(theme?: ThemeName) { return theme ? alf.themes[theme] : alf.theme }, [theme, alf]) } - -export function useBreakpoints() { - const gtPhone = useMediaQuery({minWidth: 500}) - const gtMobile = useMediaQuery({minWidth: 800}) - const gtTablet = useMediaQuery({minWidth: 1300}) - return { - gtPhone, - gtMobile, - gtTablet, - } -} diff --git a/src/alf/util/useGutterStyles.ts b/src/alf/util/useGutterStyles.ts deleted file mode 100644 index 64b246fdd2..0000000000 --- a/src/alf/util/useGutterStyles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' - -import {atoms as a, useBreakpoints, ViewStyleProp} from '#/alf' - -export function useGutterStyles({ - top, - bottom, -}: { - top?: boolean - bottom?: boolean -} = {}) { - const {gtMobile} = useBreakpoints() - return React.useMemo(() => { - return [ - a.px_lg, - top && a.pt_md, - bottom && a.pb_md, - gtMobile && [a.px_xl, top && a.pt_lg, bottom && a.pb_lg], - ] - }, [gtMobile, top, bottom]) -} diff --git a/src/alf/util/useGutters.ts b/src/alf/util/useGutters.ts new file mode 100644 index 0000000000..57dd4e80b4 --- /dev/null +++ b/src/alf/util/useGutters.ts @@ -0,0 +1,66 @@ +import React from 'react' + +import {Breakpoint, useBreakpoints} from '#/alf/breakpoints' +import * as tokens from '#/alf/tokens' + +type Gutter = 'compact' | 'base' | 'wide' | 0 + +const gutters: Record< + Exclude, + Record +> = { + compact: { + default: tokens.space.sm, + gtPhone: tokens.space.sm, + gtMobile: tokens.space.md, + gtTablet: tokens.space.md, + }, + base: { + default: tokens.space.lg, + gtPhone: tokens.space.lg, + gtMobile: tokens.space.xl, + gtTablet: tokens.space.xl, + }, + wide: { + default: tokens.space.xl, + gtPhone: tokens.space.xl, + gtMobile: tokens.space._3xl, + gtTablet: tokens.space._3xl, + }, +} + +type Gutters = { + paddingTop: number + paddingRight: number + paddingBottom: number + paddingLeft: number +} + +export function useGutters([all]: [Gutter]): Gutters +export function useGutters([vertical, horizontal]: [Gutter, Gutter]): Gutters +export function useGutters([top, right, bottom, left]: [ + Gutter, + Gutter, + Gutter, + Gutter, +]): Gutters +export function useGutters([top, right, bottom, left]: Gutter[]) { + const {activeBreakpoint} = useBreakpoints() + if (right === undefined) { + right = bottom = left = top + } else if (bottom === undefined) { + bottom = top + left = right + } + return React.useMemo(() => { + return { + paddingTop: top === 0 ? 0 : gutters[top][activeBreakpoint || 'default'], + paddingRight: + right === 0 ? 0 : gutters[right][activeBreakpoint || 'default'], + paddingBottom: + bottom === 0 ? 0 : gutters[bottom][activeBreakpoint || 'default'], + paddingLeft: + left === 0 ? 0 : gutters[left][activeBreakpoint || 'default'], + } + }, [activeBreakpoint, top, right, bottom, left]) +} diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index a35a095371..321f7201f7 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -13,7 +13,7 @@ import { platform, TextStyleProp, useBreakpoints, - useGutterStyles, + useGutters, useTheme, } from '#/alf' import {Button, ButtonIcon, ButtonProps} from '#/components/Button' @@ -34,7 +34,7 @@ export function Outer({ noBottomBorder?: boolean }) { const t = useTheme() - const gutter = useGutterStyles() + const gutters = useGutters([0, 'base']) const {gtMobile} = useBreakpoints() const {isWithinOffsetView} = useContext(ScrollbarOffsetContext) @@ -46,7 +46,7 @@ export function Outer({ a.flex_row, a.align_center, a.gap_sm, - gutter, + gutters, platform({ native: [a.pb_sm, a.pt_xs], web: [a.py_sm], diff --git a/src/components/Link.tsx b/src/components/Link.tsx index a5203b2521..3cd593a106 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -237,7 +237,9 @@ export function Link({ } export type InlineLinkProps = React.PropsWithChildren< - BaseLinkProps & TextStyleProp & Pick + BaseLinkProps & + TextStyleProp & + Pick > & Pick & { disableUnderline?: boolean @@ -273,7 +275,6 @@ export function InlineLinkText({ onIn: onHoverIn, onOut: onHoverOut, } = useInteractionState() - const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() const flattenedStyle = flatten(style) || {} return ( @@ -284,7 +285,7 @@ export function InlineLinkText({ {...rest} style={[ {color: t.palette.primary_500}, - (hovered || focused) && + hovered && !disableUnderline && { ...web({ outline: 0, @@ -298,8 +299,6 @@ export function InlineLinkText({ role="link" onPress={download ? undefined : onPress} onLongPress={onLongPress} - onFocus={onFocus} - onBlur={onBlur} onMouseEnter={onHoverIn} onMouseLeave={onHoverOut} accessibilityRole="link" diff --git a/src/view/com/home/HomeHeaderLayout.web.tsx b/src/view/com/home/HomeHeaderLayout.web.tsx index 1dc67b6c34..7a8a7671d7 100644 --- a/src/view/com/home/HomeHeaderLayout.web.tsx +++ b/src/view/com/home/HomeHeaderLayout.web.tsx @@ -10,7 +10,7 @@ import {useSession} from '#/state/session' import {useShellLayout} from '#/state/shell/shell-layout' import {HomeHeaderLayoutMobile} from '#/view/com/home/HomeHeaderLayoutMobile' import {Logo} from '#/view/icons/Logo' -import {atoms as a, useBreakpoints, useGutterStyles, useTheme} from '#/alf' +import {atoms as a, useBreakpoints, useGutters, useTheme} from '#/alf' import {ButtonIcon} from '#/components/Button' import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag' import * as Layout from '#/components/Layout' @@ -41,14 +41,14 @@ function HomeHeaderLayoutDesktopAndTablet({ const {hasSession} = useSession() const {_} = useLingui() const kawaii = useKawaiiMode() - const gutter = useGutterStyles() + const gutters = useGutters([0, 'base']) return ( <> {hasSession && ( + style={[a.flex_row, a.align_center, gutters, a.pt_md, t.atoms.bg]}> diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 383d8f953a..83b5420ce7 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -1,18 +1,18 @@ -import {StyleSheet, View} from 'react-native' +import {View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation, useNavigationState} from '@react-navigation/native' -import {usePalette} from '#/lib/hooks/usePalette' import {getCurrentRoute} from '#/lib/routes/helpers' import {NavigationProp} from '#/lib/routes/types' import {emitSoftReset} from '#/state/events' import {usePinnedFeedsInfos} from '#/state/queries/feed' import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' -import {TextLink} from '#/view/com/util/Link' +import {atoms as a, useTheme, web} from '#/alf' +import {createStaticClick, InlineLinkText} from '#/components/Link' export function DesktopFeeds() { - const pal = usePalette('default') + const t = useTheme() const {_} = useLingui() const {data: pinnedFeedInfos} = usePinnedFeedsInfos() const selectedFeed = useSelectedFeed() @@ -24,76 +24,60 @@ export function DesktopFeeds() { } return getCurrentRoute(state) }) + if (!pinnedFeedInfos) { return null } + return ( - + {pinnedFeedInfos.map(feedInfo => { const feed = feedInfo.feedDescriptor + const current = route.name === 'Home' && feed === selectedFeed + return ( - { + { setSelectedFeed(feed) navigation.navigate('Home') if (route.name === 'Home' && feed === selectedFeed) { emitSoftReset() } - }} - /> + })} + style={[ + a.text_md, + a.leading_snug, + current + ? [a.font_heavy, t.atoms.text] + : [t.atoms.text_contrast_medium], + ]} + numberOfLines={1}> + {feedInfo.displayName} + ) })} - - - - - ) -} -function FeedItem({ - title, - href, - current, - onPress, -}: { - title: string - href: string - current: boolean - onPress: () => void -}) { - const pal = usePalette('default') - return ( - - + + {_(msg`More feeds`)} + ) } - -const styles = StyleSheet.create({ - container: { - flex: 1, - // @ts-ignore web only -prf - overflowY: 'auto', - width: 300, - paddingHorizontal: 12, - paddingVertical: 18, - }, -}) diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 7814f35488..895d160211 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -1,26 +1,24 @@ -import {StyleSheet, View} from 'react-native' +import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' -import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {s} from '#/lib/styles' import {useKawaiiMode} from '#/state/preferences/kawaii' import {useSession} from '#/state/session' -import {TextLink} from '#/view/com/util/Link' -import {Text} from '#/view/com/util/text/Text' -import {atoms as a} from '#/alf' +import {DesktopFeeds} from '#/view/shell/desktop/Feeds' +import {DesktopSearch} from '#/view/shell/desktop/Search' +import {atoms as a, useGutters, useTheme, web} from '#/alf' +import {InlineLinkText} from '#/components/Link' import {ProgressGuideList} from '#/components/ProgressGuide/List' -import {DesktopFeeds} from './Feeds' -import {DesktopSearch} from './Search' +import {Text} from '#/components/Typography' export function DesktopRightNav({routeName}: {routeName: string}) { - const pal = usePalette('default') + const t = useTheme() const {_} = useLingui() const {hasSession, currentAccount} = useSession() - const kawaii = useKawaiiMode() + const gutters = useGutters(['base', 0, 'base', 'wide']) const {isTablet} = useWebMediaQueries() if (isTablet) { @@ -28,122 +26,81 @@ export function DesktopRightNav({routeName}: {routeName: string}) { } return ( - - - {routeName === 'Search' ? ( - + + {routeName !== 'Search' && ( + + + + )} + {hasSession && ( + <> + + - ) : ( - <> - + + )} - {hasSession && ( - <> - - - - - - )} + + {hasSession && ( + <> + + {_(msg`Feedback`)} + + {' • '} )} + + {_(msg`Privacy`)} + + {' • '} + + {_(msg`Terms`)} + + {' • '} + + {_(msg`Help`)} + + - - - {hasSession && ( - <> - - - · - - - )} - - - · - - - - · - - - - {kawaii && ( - - - Logo by{' '} - - - - )} - - + {kawaii && ( + + + Logo by{' '} + + @sawaratsuki.bsky.social + + + + )} ) } - -const styles = StyleSheet.create({ - rightNav: { - // @ts-ignore web only - position: 'fixed', - // @ts-ignore web only - left: '50%', - transform: [ - { - translateX: 300, - }, - ...a.scrollbar_offset.transform, - ], - maxHeight: '100%', - overflowY: 'auto', - }, - - message: { - paddingVertical: 18, - paddingHorizontal: 12, - }, - messageLine: { - marginBottom: 10, - }, - desktopFeedsContainer: { - borderTopWidth: StyleSheet.hairlineWidth, - borderBottomWidth: StyleSheet.hairlineWidth, - marginTop: 18, - marginBottom: 18, - }, -}) diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index 2780944f11..de3ccad39b 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -225,12 +225,12 @@ export function DesktopSearch() { const styles = StyleSheet.create({ container: { position: 'relative', - width: 300, + width: '100%', }, resultsContainer: { marginTop: 10, flexDirection: 'column', - width: 300, + width: '100%', borderWidth: 1, borderRadius: 6, },