Compare commits

...

3 Commits

Author SHA1 Message Date
Eric Bailey 2078995daf refactor LeftNav 2023-10-19 11:58:24 -04:00
Eric Bailey f0e32ba1db refactor FAB 2023-10-19 11:57:36 -04:00
Eric Bailey 221add5aae setup 2023-10-19 11:57:15 -04:00
8 changed files with 827 additions and 101 deletions
+31 -38
View File
@@ -1,6 +1,6 @@
import React, {ComponentProps} from 'react'
import {observer} from 'mobx-react-lite'
import {StyleSheet, TouchableWithoutFeedback} from 'react-native'
import {TouchableWithoutFeedback} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {gradients} from 'lib/styles'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@@ -8,6 +8,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {clamp} from 'lib/numbers'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import Animated from 'react-native-reanimated'
import {useStyles} from 'view/nova'
export interface FABProps
extends ComponentProps<typeof TouchableWithoutFeedback> {
@@ -21,59 +22,51 @@ export const FABInner = observer(function FABInnerImpl({
...props
}: FABProps) {
const insets = useSafeAreaInsets()
const {isMobile, isTablet} = useWebMediaQueries()
const {isMobile} = useWebMediaQueries()
const {fabMinimalShellTransform} = useMinimalShellMode()
const size = React.useMemo(() => {
return isTablet ? styles.sizeLarge : styles.sizeRegular
}, [isTablet])
const tabletSpacing = React.useMemo(() => {
return isTablet
? {right: 50, bottom: 50}
: {
right: 24,
bottom: clamp(insets.bottom, 15, 60) + 15,
}
}, [insets.bottom, isTablet])
const styles = useStyles({
outer: {
position: 'absolute',
z: 1,
right: 24,
bottom: clamp(insets.bottom, 15, 60) + 15,
gtMobile: {
right: 50,
bottom: 50,
},
},
sizing: {
w: 60,
h: 60,
radius: 30,
gtMobile: {
w: 70,
h: 70,
radius: 35,
},
},
inner: {
justifyContent: 'center',
alignItems: 'center',
},
})
return (
<TouchableWithoutFeedback testID={testID} {...props}>
<Animated.View
style={[
styles.outer,
size,
tabletSpacing,
styles.sizing,
isMobile && fabMinimalShellTransform,
]}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.inner, size]}>
style={[styles.inner, styles.sizing]}>
{icon}
</LinearGradient>
</Animated.View>
</TouchableWithoutFeedback>
)
})
const styles = StyleSheet.create({
sizeRegular: {
width: 60,
height: 60,
borderRadius: 30,
},
sizeLarge: {
width: 70,
height: 70,
borderRadius: 35,
},
outer: {
position: 'absolute',
zIndex: 1,
},
inner: {
justifyContent: 'center',
alignItems: 'center',
},
})
+54
View File
@@ -0,0 +1,54 @@
import { View, Text as RNText, Pressable as RNPressable } from 'react-native'
import { createSystem } from './lib/system'
import { light, dark } from './themes'
import { web } from './lib/utils'
export * from './lib/utils'
const {
ThemeProvider,
useTheme,
useStyle,
useStyles,
styled,
} = createSystem({
light,
dark,
})
export {
ThemeProvider,
useTheme,
useStyle,
useStyles,
styled,
}
export const Box = styled(View)
export const Pressable = styled(RNPressable)
export const Text = styled(RNText, {
color: 'l8',
fontSize: 's',
})
/**
* @see https://necolas.github.io/react-native-web/docs/accessibility/#semantic-html
* @see https://docs.expo.dev/develop/user-interface/fonts/
*/
export const H1 = styled(RNText, {
role: web('heading'),
color: 'l8',
fontSize: 'l',
gtMobile: {
fontSize: 'xl',
},
...web({
'aria-level': 1,
}),
})
export const P = styled(RNText, {
role: web('paragraph'),
color: 'l8',
fontSize: 's',
})
+149
View File
@@ -0,0 +1,149 @@
import React from "react";
import { Dimensions } from "react-native";
import {
Theme,
ResponsiveStyles,
Tokens,
Properties,
Macros,
Breakpoints,
} from "./theme";
type ThemeConfig<
T extends Tokens,
P extends Properties,
M extends Macros<T>,
B extends Breakpoints,
> = {
[key: string]: Theme<T, P, M, B>;
};
export function createSystem<
T extends Tokens,
P extends Properties,
M extends Macros<T>,
B extends Breakpoints,
>(themes: ThemeConfig<T, P, M, B>) {
const theme = Object.values(themes)[0];
type SystemTheme = Theme<T, P, M, B>;
type ResponsiveStyleProps = ResponsiveStyles<T, P, M, B>;
type DebugProps = {
/**
* Debug mode will log the styles and props to the console
*/
debug?: boolean;
};
type ThemeName = keyof typeof themes;
const Context = React.createContext<{
themeName: ThemeName;
theme: SystemTheme;
themes: {
[key in ThemeName]: SystemTheme;
};
}>({
themeName: Object.keys(themes)[0],
theme,
themes,
});
const ThemeProvider = ({
children,
theme,
}: React.PropsWithChildren<{ theme: ThemeName }>) => (
<Context.Provider
value={{
themeName: theme,
theme: themes[theme],
themes,
}}
>
{children}
</Context.Provider>
);
function useTheme() {
return React.useContext(Context);
}
function useBreakpoints() {
const { theme } = useTheme();
const [breakpoints, setBreakpoints] = React.useState(
theme.getActiveBreakpoints({ width: Dimensions.get("window").width }),
);
React.useEffect(() => {
const listener = Dimensions.addEventListener("change", ({ window }) => {
const bp = theme.getActiveBreakpoints({ width: window.width });
if (bp.current !== breakpoints.current) setBreakpoints(bp);
});
return () => {
listener.remove();
};
}, [breakpoints, theme]);
return breakpoints;
}
function useStyle(props: ResponsiveStyleProps) {
const { theme } = useTheme();
const breakpoints = useBreakpoints();
return theme.style(props, breakpoints.active).styles;
}
function useStyles<O extends Record<string, ResponsiveStyleProps>>(
styles: Record<string, ResponsiveStyleProps>,
): {
[Name in keyof O]: ReturnType<SystemTheme["style"]>["styles"];
} {
const { theme } = useTheme();
const breakpoints = useBreakpoints();
return React.useMemo(() => {
return Object.entries(styles).reduce(
(acc, [key, style]) => {
acc[key as keyof O] = theme.style(style, breakpoints.active).styles;
return acc;
},
{} as { [Name in keyof O]: ReturnType<SystemTheme["style"]>["styles"] },
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [styles, breakpoints.current, theme]);
}
function styled<T extends Record<string, any>>(
Component: React.ComponentType<T>,
defaultProps: ResponsiveStyleProps = {},
) {
return React.forwardRef<
T,
T &
ResponsiveStyleProps &
DebugProps & { children?: React.ReactNode | React.ReactNodeArray }
>((props, ref) => {
const { theme } = useTheme();
const breakpoints = useBreakpoints();
const { styles, props: rest } = theme.style<T>(
{
...defaultProps,
...props,
},
breakpoints.active,
);
if (props.debug) console.log({ styles, props: rest });
return <Component {...rest} style={[styles, rest.style]} ref={ref} />;
});
}
return {
Context,
ThemeProvider,
useTheme,
useBreakpoints,
useStyle,
useStyles,
styled,
};
}
+384
View File
@@ -0,0 +1,384 @@
import type {
ViewStyle,
TextStyle,
ImageStyle,
DimensionValue,
ColorValue,
} from "react-native";
import { StyleSheet } from "react-native";
type StyleObject = ViewStyle & TextStyle & ImageStyle;
type StyleObjectProperties = keyof StyleObject;
type ColorProperties =
| "color"
| "backgroundColor"
| "borderColor"
| "borderTopColor"
| "borderRightColor"
| "borderBottomColor"
| "borderLeftColor";
type DimensionProperties =
| "padding"
| "paddingTop"
| "paddingBottom"
| "paddingLeft"
| "paddingRight"
| "paddingVertical"
| "paddingHorizontal"
| "margin"
| "marginTop"
| "marginBottom"
| "marginLeft"
| "marginRight"
| "top"
| "bottom"
| "left"
| "right"
| "gap"
| "rowGap"
| "columnGap";
export type Tokens = {
space?: DimensionValue[] | Record<string, DimensionValue>;
} & {
[Property in StyleObjectProperties]?: Record<
string,
Required<StyleObject>[Property]
>;
};
export type Properties = Record<string, Readonly<StyleObjectProperties[]>>;
export type Macros<T extends Tokens> = Record<
string,
(value: any, tokens: T) => StyleObject
>;
export type Breakpoints = {
[Breakpoint: string]: number;
};
export type Styles<T extends Tokens, P extends Properties, M extends Macros<T>> = {
[Property in ColorProperties]?: keyof T["color"] | Omit<ColorValue, keyof T["color"]>;
} & {
[Property in DimensionProperties]?: keyof T["space"] | DimensionValue;
} & {
[Property in Exclude<
StyleObjectProperties,
ColorProperties | DimensionProperties
>]?: keyof T[Property] | StyleObject[Property];
} & {
// this empty P is to avoid circular references back to Properties type
[Property in keyof P]?: Styles<T, {}, M>[P[Property][0]];
} & {
[Property in keyof M]?: M[Property] extends (
value: infer Value,
tokens: T,
) => StyleObject
? Value
: never;
};
export type ResponsiveStyles<
T extends Tokens, P extends Properties, M extends Macros<T>,
B extends Breakpoints,
> = Styles<T, P, M> | {
[Breakpoint in keyof B]?: Styles<T, P, M>;
};
export type Theme<
T extends Tokens,
P extends Properties,
M extends Macros<T>,
B extends Breakpoints,
> = {
config: {
tokens: T;
properties: P;
macros: M;
breakpoints: B;
};
style: <Props>(
props: ResponsiveStyles<T, P, M, B> | Omit<Props, keyof P>,
breakpoints?: (keyof B)[],
) => {
styles: StyleObject;
props: Props;
};
getActiveBreakpoints: ({ width }: { width: number }) => {
active: (keyof B)[];
current: keyof B;
};
};
const specialTokenMapping = {
color: "color",
backgroundColor: "color",
borderColor: "color",
borderTopColor: "color",
borderRightColor: "color",
borderBottomColor: "color",
borderLeftColor: "color",
padding: "space",
paddingTop: "space",
paddingBottom: "space",
paddingLeft: "space",
paddingRight: "space",
paddingVertical: "space",
paddingHorizontal: "space",
margin: "space",
marginTop: "space",
marginBottom: "space",
marginLeft: "space",
marginRight: "space",
top: "space",
bottom: "space",
left: "space",
right: "space",
gap: "space",
rowGap: "space",
columnGap: "space",
};
const propertyMapping: Properties = {
// FlexStyle
alignContent: ["alignContent"],
alignItems: ["alignItems"],
alignSelf: ["alignSelf"],
aspectRatio: ["aspectRatio"],
borderBottomWidth: ["borderBottomWidth"],
borderEndWidth: ["borderEndWidth"],
borderLeftWidth: ["borderLeftWidth"],
borderRightWidth: ["borderRightWidth"],
borderStartWidth: ["borderStartWidth"],
borderTopWidth: ["borderTopWidth"],
borderWidth: ["borderWidth"],
bottom: ["bottom"],
display: ["display"],
end: ["end"],
flex: ["flex"],
flexBasis: ["flexBasis"],
flexDirection: ["flexDirection"],
rowGap: ["rowGap"],
gap: ["gap"],
columnGap: ["columnGap"],
flexGrow: ["flexGrow"],
flexShrink: ["flexShrink"],
flexWrap: ["flexWrap"],
height: ["height"],
justifyContent: ["justifyContent"],
left: ["left"],
margin: ["marginTop", "marginBottom", "marginLeft", "marginRight"],
marginBottom: ["marginBottom"],
marginEnd: ["marginEnd"],
marginHorizontal: ["marginLeft", "marginRight"],
marginLeft: ["marginLeft"],
marginRight: ["marginRight"],
marginStart: ["marginStart"],
marginTop: ["marginTop"],
marginVertical: ["marginTop", "marginBottom"],
maxHeight: ["maxHeight"],
maxWidth: ["maxWidth"],
overflow: ["overflow"],
padding: ["paddingTop", "paddingBottom", "paddingLeft", "paddingRight"],
paddingBottom: ["paddingBottom"],
paddingEnd: ["paddingEnd"],
paddingHorizontal: ["paddingLeft", "paddingRight"],
paddingLeft: ["paddingLeft"],
paddingRight: ["paddingRight"],
paddingStart: ["paddingStart"],
paddingTop: ["paddingTop"],
paddingVertical: ["paddingTop", "paddingBottom"],
position: ["position"],
right: ["right"],
start: ["start"],
top: ["top"],
width: ["width"],
zIndex: ["zIndex"],
direction: ["direction"],
// ShadowStyle
shadowColor: ["shadowColor"],
shadowOffset: ["shadowOffset"],
shadowOpacity: ["shadowOpacity"],
shadowRadius: ["shadowRadius"],
// TransformStyle
transform: ["transform"],
transformMatrix: ["transformMatrix"],
rotation: ["rotation"],
scaleX: ["scaleX"],
scaleY: ["scaleY"],
translateX: ["translateX"],
translateY: ["translateY"],
// ViewStyle
backfaceVisibility: ["backfaceVisibility"],
backgroundColor: ["backgroundColor"],
borderBlockColor: ["borderBlockColor"],
borderBlockEndColor: ["borderBlockEndColor"],
borderBlockStartColor: ["borderBlockStartColor"],
borerBottomColor: ["borderBottomColor"],
borderBottomEndRadius: ["borderBottomEndRadius"],
borderBottomLeftRadius: ["borderBottomLeftRadius"],
borderBottomRightRadius: ["borderBottomRightRadius"],
borderBottomStartRadius: ["borderBottomStartRadius"],
borderColor: ["borderColor"],
borderCurve: ["borderCurve"],
borderEndColor: ["borderEndColor"],
borderEndEndRadius: ["borderEndEndRadius"],
borderEndStartRadius: ["borderEndStartRadius"],
borderLeftColor: ["borderLeftColor"],
borderRadius: ["borderRadius"],
borderRightColor: ["borderRightColor"],
borderStartColor: ["borderStartColor"],
borderStartEndRadius: ["borderStartEndRadius"],
borderStartStartRadius: ["borderStartStartRadius"],
borderStyle: ["borderStyle"],
borderTopColor: ["borderTopColor"],
borderTopEndRadius: ["borderTopEndRadius"],
borderTopLeftRadius: ["borderTopLeftRadius"],
borderTopRightRadius: ["borderTopRightRadius"],
borderTopStartRadius: ["borderTopStartRadius"],
opacity: ["opacity"],
elevation: ["elevation"],
pointerEvents: ["pointerEvents"],
// TextStyleIOS
fontVariant: ["fontVariant"],
textDecorationColor: ["textDecorationColor"],
textDecorationStyle: ["textDecorationStyle"],
writingDirection: ["writingDirection"],
// TextStyleAndroid
textAlignVertical: ["textAlignVertical"],
verticalAlign: ["verticalAlign"],
includeFontPadding: ["includeFontPadding"],
// TextStyle
color: ["color"],
fontFamily: ["fontFamily"],
fontSize: ["fontSize"],
fontStyle: ["fontStyle"],
fontWeight: ["fontWeight"],
letterSpacing: ["letterSpacing"],
lineHeight: ["lineHeight"],
textAlign: ["textAlign"],
textDecorationLine: ["textDecorationLine"],
textShadowColor: ["textShadowColor"],
textShadowOffset: ["textShadowOffset"],
textTransform: ["textTransform"],
// ImageStyle
resizeMode: ["resizeMode"],
overlayColor: ["overlayColor"],
tintColor: ["tintColor"],
objectFit: ["objectFit"],
};
export const createTheme = <
T extends Tokens,
P extends Properties,
M extends Macros<T>,
B extends Breakpoints,
>({
tokens,
properties: userProperties = {},
macros: userMacros = {},
breakpoints: userBreakpoints = {},
}: {
tokens: T;
properties?: Partial<P>;
macros?: Partial<M>;
breakpoints?: Partial<B>;
}): Theme<T, P, M, B> => {
const properties = Object.assign({}, propertyMapping, userProperties) as P;
const macros = userMacros as M;
const breakpoints = Object.entries(userBreakpoints)
.sort(([_a, a], [_b, b]) => {
return a - b;
})
.reduce((breakpoints, [key, value]) => {
breakpoints[key as keyof B] = value;
return breakpoints;
}, {} as B);
type InnerTheme = Theme<
typeof tokens,
typeof properties,
typeof macros,
typeof breakpoints
>;
function _applyStyleProperty(styles: StyleObject, prop: string, value: any) {
for (const _prop of properties[prop]) {
// @ts-ignore no index sig, it's fine
const t = specialTokenMapping[_prop]
? // @ts-ignore no index sig, it's fine
tokens[specialTokenMapping[_prop]]
: tokens[_prop];
styles[_prop] = t?.[value] || value;
}
}
const style: InnerTheme["style"] = (rawProps, activeBreakpoints = []) => {
let styles: StyleObject = {};
const props: any = {};
Object.keys(rawProps).forEach((prop) => {
// @ts-ignore no index sig, it's fine
const value = rawProps[prop];
if (value === undefined) return;
if (properties[prop]) {
_applyStyleProperty(styles, prop, value);
} else if (macros[prop]) {
styles = {
...styles,
// @ts-ignore no index sig, it's fine
...(rawProps[prop] === true ? macros[prop](value, tokens) : {}),
};
} else if (breakpoints[prop]) {
for (const b of Object.keys(breakpoints)) {
if (activeBreakpoints.includes(b)) {
// @ts-ignore no index sig, it's fine
const breakpointStyles = (rawProps[b] || {})
const r = style(breakpointStyles, activeBreakpoints);
styles = { ...styles, ...r.styles };
} else {
// @ts-ignore no index sig, it's fine
delete rawProps[b];
}
}
} else {
// @ts-ignore no index sig, it's fine
props[prop] = value;
}
});
return {
styles: StyleSheet.create({ sheet: styles }).sheet,
props: props,
};
};
function getActiveBreakpoints({ width }: { width: number }) {
const active: (keyof typeof breakpoints)[] = Object.keys(
breakpoints,
).filter((breakpoint) => width >= breakpoints[breakpoint]);
return {
active,
current: active[active.length - 1],
};
}
return {
config: {
tokens,
properties,
macros,
breakpoints,
},
style,
getActiveBreakpoints,
};
};
+25
View File
@@ -0,0 +1,25 @@
import { Platform } from "react-native";
export function web(value: any) {
return Platform.select({
web: value,
});
}
export function ios(value: any) {
return Platform.select({
ios: value,
});
}
export function android(value: any) {
return Platform.select({
android: value,
});
}
export function native(value: any) {
return Platform.select({
native: value,
});
}
+129
View File
@@ -0,0 +1,129 @@
import { createTheme } from './lib/theme'
const palette = {
white: '#FFFFFF',
black: '#0A0B0D',
gray1: '#F7F9FC',
gray2: '#E4E8EE',
gray3: '#CED5DE',
gray4: '#828A99',
gray5: '#4D5564',
gray6: '#1D2126',
blue: '#1185FE',
green: '#54D469',
red: '#FB4566',
}
export const light = createTheme({
tokens: {
color: {
primary: palette.blue,
l1: palette.white,
l2: palette.gray1,
l3: palette.gray2,
l4: palette.gray3,
l5: palette.gray4,
l6: palette.gray5,
l7: palette.gray6,
l8: palette.black,
},
fontSize: {
xs: 12,
s: 14,
m: 16,
l: 18,
xl: 22,
},
lineHeight: {
xs: 12,
s: 14,
m: 16,
l: 18,
xl: 22,
},
},
properties: {
d: ['display'],
w: ['width'],
h: ['height'],
c: ['color'],
bg: ['backgroundColor'],
ma: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
mt: ['marginTop'],
mb: ['marginBottom'],
ml: ['marginLeft'],
mr: ['marginRight'],
my: ['marginTop', 'marginBottom'],
mx: ['marginLeft', 'marginRight'],
/**
* Alias for `padding`, maps to all padding properties e.g. `paddingTop`,
* `paddingBottom`, etc.
*/
pa: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
pt: ['paddingTop'],
pb: ['paddingBottom'],
pl: ['paddingLeft'],
pr: ['paddingRight'],
py: ['paddingTop', 'paddingBottom'],
px: ['paddingLeft', 'paddingRight'],
z: ['zIndex'],
fs: ['fontSize'],
ff: ['fontFamily'],
fw: ['fontWeight'],
lh: ['lineHeight'],
ta: ['textAlign'],
radius: ['borderRadius'],
},
macros: {
row: (_: boolean) => ({ flexDirection: 'row' }),
column: (_: boolean) => ({ flex: 1 }),
aic: (_: boolean) => ({ alignItems: 'center' }),
aie: (_: boolean) => ({ alignItems: 'flex-end' }),
jcs: (_: boolean) => ({ justifyContent: 'flex-start' }),
jcc: (_: boolean) => ({ justifyContent: 'center' }),
jce: (_: boolean) => ({ justifyContent: 'flex-end' }),
jcb: (_: boolean) => ({ justifyContent: 'space-between' }),
fdr: (_: boolean) => ({ flexDirection: 'row' }),
rel: (_: boolean) => ({ position: 'relative' }),
abs: (_: boolean) => ({ position: 'absolute' }),
cover: (_: boolean) => ({
top: 0,
bottom: 0,
left: 0,
right: 0,
}),
tac: (_: boolean) => ({ textAlign: 'center' }),
tar: (_: boolean) => ({ textAlign: 'right' }),
mxa: (_: boolean) => ({ marginLeft: 'auto', marginRight: 'auto' }),
mya: (_: boolean) => ({ marginTop: 'auto', marginBottom: 'auto' }),
caps: (_: boolean) => ({ textTransform: 'uppercase' }),
fontSize(value: 'xs' | 's' | 'm' | 'l' | 'xl', tokens) {
return {
fontSize: tokens.fontSize[value],
lineHeight: tokens.lineHeight[value],
}
},
},
breakpoints: {
gtMobile: 800,
gtTablet: 1300,
}
})
export const dark = createTheme({
...light.config,
tokens: {
...light.config.tokens,
color: {
...light.config.tokens.color,
l1: palette.black,
l2: palette.gray6,
l3: palette.gray5,
l4: palette.gray4,
l5: palette.gray3,
l6: palette.gray2,
l7: palette.gray1,
l8: palette.white,
}
},
})
+44 -55
View File
@@ -1,7 +1,6 @@
import React from 'react'
import {observer} from 'mobx-react-lite'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {PressableWithHover} from 'view/com/util/PressableWithHover'
import {
useLinkProps,
useNavigation,
@@ -11,7 +10,6 @@ import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {Text} from 'view/com/util/text/Text'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {Link} from 'view/com/util/Link'
import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
@@ -38,6 +36,7 @@ import {getCurrentRoute, isTab, isStateAtTabRoot} from 'lib/routes/helpers'
import {NavigationProp, CommonNavigatorParams} from 'lib/routes/types'
import {router} from '../../../routes'
import {makeProfileLink} from 'lib/routes/links'
import {Box, Text, Pressable, useTheme} from 'view/nova'
const ProfileCard = observer(function ProfileCardImpl() {
const store = useStores()
@@ -110,6 +109,7 @@ const NavItem = observer(function NavItemImpl({
iconFilled,
label,
}: NavItemProps) {
const { theme } = useTheme()
const pal = usePalette('default')
const store = useStores()
const {isDesktop, isTablet} = useWebMediaQueries()
@@ -143,40 +143,62 @@ const NavItem = observer(function NavItemImpl({
)
return (
<PressableWithHover
style={styles.navItemWrapper}
hoverStyle={pal.viewLight}
<Pressable
fdr
aic
pa={12}
radius={8}
gap={10}
style={state => ({
// TODO
backgroundColor: state.hovered ? theme.config.tokens.color.l2 : 'transparent',
})}
// @ts-ignore the function signature differs on web -prf
onPress={onPressWrapped}
// @ts-ignore web only -prf
href={href}
// @ts-ignore web only -prf
dataSet={{noUnderline: 1}}
accessibilityRole="tab"
accessibilityLabel={label}
accessibilityHint="">
<View
style={[
styles.navItemIconWrapper,
isTablet && styles.navItemIconWrapperTablet,
]}>
<Box
aic
jcc
w={28}
h={28}
mt={2}
z={1}
gtMobile={{
w: 40,
h: 40,
}}
gtTablet={{
w: 28,
h: 28,
}}>
{isCurrent ? iconFilled : icon}
{typeof count === 'string' && count ? (
<Text
type="button"
style={[
styles.navItemCount,
isTablet && styles.navItemCountTablet,
]}>
abs
top={0}
left={15}
bg={colors.blue3}
c={colors.white}
fs='s'
fw={'bold'}
px={4}
radius={6}>
{count}
</Text>
) : null}
</View>
</Box>
{isDesktop && (
<Text type="title" style={[isCurrent ? s.bold : s.normal, pal.text]}>
<Text fs='xl' fw={isCurrent ? 'bold' : 'normal'}>
{label}
</Text>
)}
</PressableWithHover>
</Pressable>
)
})
@@ -230,7 +252,10 @@ function ComposeBtn() {
style={styles.newPostBtnLabel}
/>
</View>
<Text type="button" style={styles.newPostBtnLabel}>
<Text
c={colors.white}
fs='m'
fw={'bold'}>
New Post
</Text>
</TouchableOpacity>
@@ -418,42 +443,6 @@ const styles = StyleSheet.create({
height: 30,
},
navItemWrapper: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
padding: 12,
borderRadius: 8,
gap: 10,
},
navItemIconWrapper: {
alignItems: 'center',
justifyContent: 'center',
width: 28,
height: 28,
marginTop: 2,
zIndex: 1,
},
navItemIconWrapperTablet: {
width: 40,
height: 40,
},
navItemCount: {
position: 'absolute',
top: 0,
left: 15,
backgroundColor: colors.blue3,
color: colors.white,
fontSize: 12,
fontWeight: 'bold',
paddingHorizontal: 4,
borderRadius: 6,
},
navItemCountTablet: {
left: 18,
fontSize: 14,
},
newPostBtn: {
flexDirection: 'row',
alignItems: 'center',
+11 -8
View File
@@ -23,6 +23,7 @@ import {RoutesContainer, TabsNavigator} from '../../Navigation'
import {isStateAtTabRoot} from 'lib/routes/helpers'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {useOTAUpdate} from 'lib/hooks/useOTAUpdate'
import { ThemeProvider } from 'view/nova'
const ShellInner = observer(function ShellInnerImpl() {
const store = useStores()
@@ -87,14 +88,16 @@ export const Shell: React.FC = observer(function ShellImpl() {
const pal = usePalette('default')
const theme = useTheme()
return (
<SafeAreaProvider style={pal.view}>
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
<StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
<RoutesContainer>
<ShellInner />
</RoutesContainer>
</View>
</SafeAreaProvider>
<ThemeProvider theme='light'>
<SafeAreaProvider style={pal.view}>
<View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
<StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} />
<RoutesContainer>
<ShellInner />
</RoutesContainer>
</View>
</SafeAreaProvider>
</ThemeProvider>
)
})