diff options
Diffstat (limited to 'src/components')
24 files changed, 542 insertions, 137 deletions
diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 7c682ac1a..f88fbcbde 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -9,10 +9,11 @@ import { View, TextStyle, StyleSheet, + StyleProp, } from 'react-native' import LinearGradient from 'react-native-linear-gradient' -import {useTheme, atoms as a, tokens, web, native} from '#/alf' +import {useTheme, atoms as a, tokens, android, flatten} from '#/alf' import {Props as SVGIconProps} from '#/components/icons/common' export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient' @@ -27,6 +28,7 @@ export type ButtonColor = | 'gradient_nordic' | 'gradient_bonfire' export type ButtonSize = 'small' | 'large' +export type ButtonShape = 'round' | 'square' | 'default' export type VariantProps = { /** * The style variation of the button @@ -40,6 +42,10 @@ export type VariantProps = { * The size of the button */ size?: ButtonSize + /** + * The shape of the button + */ + shape?: ButtonShape } export type ButtonProps = React.PropsWithChildren< @@ -47,6 +53,7 @@ export type ButtonProps = React.PropsWithChildren< AccessibilityProps & VariantProps & { label: string + style?: StyleProp<ViewStyle> } > export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean} @@ -74,8 +81,10 @@ export function Button({ variant, color, size, + shape = 'default', label, disabled = false, + style, ...rest }: ButtonProps) { const t = useTheme() @@ -175,18 +184,18 @@ export function Button({ if (!disabled) { baseStyles.push({ backgroundColor: light - ? tokens.color.gray_100 + ? tokens.color.gray_50 : tokens.color.gray_900, }) hoverStyles.push({ backgroundColor: light - ? tokens.color.gray_200 + ? tokens.color.gray_100 : tokens.color.gray_950, }) } else { baseStyles.push({ backgroundColor: light - ? tokens.color.gray_300 + ? tokens.color.gray_200 : tokens.color.gray_950, }) } @@ -197,7 +206,7 @@ export function Button({ if (!disabled) { baseStyles.push(a.border, { - borderColor: light ? tokens.color.gray_500 : tokens.color.gray_500, + borderColor: light ? tokens.color.gray_300 : tokens.color.gray_700, }) hoverStyles.push(a.border, t.atoms.bg_contrast_50) } else { @@ -262,10 +271,28 @@ export function Button({ } } - if (size === 'large') { - baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_sm) - } else if (size === 'small') { - baseStyles.push({paddingVertical: 9}, a.px_md, a.rounded_sm, a.gap_sm) + if (shape === 'default') { + if (size === 'large') { + baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_md) + } else if (size === 'small') { + baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm) + } + } else if (shape === 'round' || shape === 'square') { + if (size === 'large') { + if (shape === 'round') { + baseStyles.push({height: 54, width: 54}) + } else { + baseStyles.push({height: 50, width: 50}) + } + } else if (size === 'small') { + baseStyles.push({height: 40, width: 40}) + } + + if (shape === 'round') { + baseStyles.push(a.rounded_full) + } else if (shape === 'square') { + baseStyles.push(a.rounded_sm) + } } return { @@ -278,7 +305,7 @@ export function Button({ } as ViewStyle, ], } - }, [t, variant, color, size, disabled]) + }, [t, variant, color, size, shape, disabled]) const {gradientColors, gradientHoverColors, gradientLocations} = React.useMemo(() => { @@ -334,8 +361,10 @@ export function Button({ disabled: disabled || false, }} style={[ + flatten(style), a.flex_row, a.align_center, + a.justify_center, a.overflow_hidden, a.justify_center, ...baseStyles, @@ -462,17 +491,9 @@ export function useSharedButtonTextStyles() { } if (size === 'large') { - baseStyles.push( - a.text_md, - web({paddingBottom: 1}), - native({marginTop: 2}), - ) + baseStyles.push(a.text_md, android({paddingBottom: 1})) } else { - baseStyles.push( - a.text_md, - web({paddingBottom: 1}), - native({marginTop: 2}), - ) + baseStyles.push(a.text_sm, android({paddingBottom: 1})) } return StyleSheet.flatten(baseStyles) @@ -491,14 +512,24 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) { export function ButtonIcon({ icon: Comp, + position, }: { icon: React.ComponentType<SVGIconProps> + position?: 'left' | 'right' }) { - const {size} = useButtonContext() + const {size, disabled} = useButtonContext() const textStyles = useSharedButtonTextStyles() return ( - <View style={[a.z_20]}> + <View + style={[ + a.z_20, + { + opacity: disabled ? 0.7 : 1, + marginLeft: position === 'left' ? -2 : 0, + marginRight: position === 'right' ? -2 : 0, + }, + ]}> <Comp size={size === 'large' ? 'md' : 'sm'} style={[{color: textStyles.color, pointerEvents: 'none'}]} diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx new file mode 100644 index 000000000..9b8f79fd0 --- /dev/null +++ b/src/components/Divider.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import {View} from 'react-native' +import {atoms as a, useTheme} from '#/alf' +import {ViewStyleProp} from '#/alf' + +export function Divider({style}: ViewStyleProp) { + const t = useTheme() + + return <View style={[a.w_full, a.border_t, t.atoms.border, style]} /> +} diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 8f686f3c4..63b0c73f1 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -1,10 +1,8 @@ import React from 'react' import { - Text, - TextStyle, - StyleProp, GestureResponderEvent, Linking, + TouchableWithoutFeedback, } from 'react-native' import { useLinkProps, @@ -13,9 +11,10 @@ import { } from '@react-navigation/native' import {sanitizeUrl} from '@braintree/sanitize-url' +import {useInteractionState} from '#/components/hooks/useInteractionState' import {isWeb} from '#/platform/detection' -import {useTheme, web, flatten} from '#/alf' -import {Button, ButtonProps, useButtonContext} from '#/components/Button' +import {useTheme, web, flatten, TextStyleProp} from '#/alf' +import {Button, ButtonProps} from '#/components/Button' import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types' import { convertBskyAppUrlIfNeeded, @@ -24,43 +23,39 @@ import { } from '#/lib/strings/url-helpers' import {useModalControls} from '#/state/modals' import {router} from '#/routes' +import {Text} from '#/components/Typography' -export type LinkProps = Omit< - ButtonProps, - 'style' | 'onPress' | 'disabled' | 'label' +/** + * Only available within a `Link`, since that inherits from `Button`. + * `InlineLink` provides no context. + */ +export {useButtonContext as useLinkContext} from '#/components/Button' + +type BaseLinkProps = Pick< + Parameters<typeof useLinkProps<AllNavigatorParams>>[0], + 'to' > & { /** - * `TextStyle` to apply to the anchor element itself. Does not apply to any children. - */ - style?: StyleProp<TextStyle> - /** * The React Navigation `StackAction` to perform when the link is pressed. */ action?: 'push' | 'replace' | 'navigate' + /** - * If true, will warn the user if the link text does not match the href. Only - * works for Links with children that are strings i.e. text links. + * If true, will warn the user if the link text does not match the href. + * + * Note: atm this only works for `InlineLink`s with a string child. */ warnOnMismatchingTextChild?: boolean - label?: ButtonProps['label'] -} & Pick<Parameters<typeof useLinkProps<AllNavigatorParams>>[0], 'to'> +} -/** - * A interactive element that renders as a `<a>` tag on the web. On mobile it - * will translate the `href` to navigator screens and params and dispatch a - * navigation action. - * - * Intended to behave as a web anchor tag. For more complex routing, use a - * `Button`. - */ -export function Link({ - children, +export function useLink({ to, + displayText, action = 'push', warnOnMismatchingTextChild, - style, - ...rest -}: LinkProps) { +}: BaseLinkProps & { + displayText: string +}) { const navigation = useNavigation<NavigationProp>() const {href} = useLinkProps<AllNavigatorParams>({ to: @@ -68,14 +63,14 @@ export function Link({ }) const isExternal = isExternalUrl(href) const {openModal, closeModal} = useModalControls() + const onPress = React.useCallback( (e: GestureResponderEvent) => { - const stringChildren = typeof children === 'string' ? children : '' const requiresWarning = Boolean( warnOnMismatchingTextChild && - stringChildren && + displayText && isExternal && - linkRequiresWarning(href, stringChildren), + linkRequiresWarning(href, displayText), ) if (requiresWarning) { @@ -83,7 +78,7 @@ export function Link({ openModal({ name: 'link-warning', - text: stringChildren, + text: displayText, href: href, }) } else { @@ -134,12 +129,42 @@ export function Link({ warnOnMismatchingTextChild, navigation, action, - children, + displayText, closeModal, openModal, ], ) + return { + isExternal, + href, + onPress, + } +} + +export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> & + Omit<ButtonProps, 'style' | 'onPress' | 'disabled' | 'label'> & { + /** + * Label for a11y. Defaults to the href. + */ + label?: string + } + +/** + * A interactive element that renders as a `<a>` tag on the web. On mobile it + * will translate the `href` to navigator screens and params and dispatch a + * navigation action. + * + * Intended to behave as a web anchor tag. For more complex routing, use a + * `Button`. + */ +export function Link({children, to, action = 'push', ...rest}: LinkProps) { + const {href, isExternal, onPress} = useLink({ + to, + displayText: typeof children === 'string' ? children : '', + action, + }) + return ( <Button label={href} @@ -158,34 +183,81 @@ export function Link({ noUnderline: '1', }, })}> - {typeof children === 'string' ? ( - <LinkText style={style}>{children}</LinkText> - ) : ( - children - )} + {children} </Button> ) } -function LinkText({ +export type InlineLinkProps = React.PropsWithChildren< + BaseLinkProps & + TextStyleProp & { + /** + * Label for a11y. Defaults to the href. + */ + label?: string + } +> + +export function InlineLink({ children, + to, + action = 'push', + warnOnMismatchingTextChild, style, -}: React.PropsWithChildren<{ - style?: StyleProp<TextStyle> -}>) { + ...rest +}: InlineLinkProps) { const t = useTheme() - const {hovered} = useButtonContext() + const stringChildren = typeof children === 'string' + const {href, isExternal, onPress} = useLink({ + to, + displayText: stringChildren ? children : '', + action, + warnOnMismatchingTextChild, + }) + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() + const { + state: pressed, + onIn: onPressIn, + onOut: onPressOut, + } = useInteractionState() + return ( - <Text - style={[ - {color: t.palette.primary_500}, - hovered && { - textDecorationLine: 'underline', - textDecorationColor: t.palette.primary_500, - }, - flatten(style), - ]}> - {children as string} - </Text> + <TouchableWithoutFeedback + accessibilityRole="button" + onPress={onPress} + onPressIn={onPressIn} + onPressOut={onPressOut} + onFocus={onFocus} + onBlur={onBlur}> + <Text + label={href} + {...rest} + style={[ + {color: t.palette.primary_500}, + (focused || pressed) && { + outline: 0, + textDecorationLine: 'underline', + textDecorationColor: t.palette.primary_500, + }, + flatten(style), + ]} + role="link" + accessibilityRole="link" + href={href} + {...web({ + hrefAttrs: { + target: isExternal ? 'blank' : undefined, + rel: isExternal ? 'noopener noreferrer' : undefined, + }, + dataSet: stringChildren + ? {} + : { + // default to no underline, apply this ourselves + noUnderline: '1', + }, + })}> + {children} + </Text> + </TouchableWithoutFeedback> ) } diff --git a/src/components/Portal.tsx b/src/components/Portal.tsx index 1813d9e05..d696f986b 100644 --- a/src/components/Portal.tsx +++ b/src/components/Portal.tsx @@ -12,45 +12,54 @@ type ComponentMap = { [id: string]: Component } -export const Context = React.createContext<ContextType>({ - outlet: null, - append: () => {}, - remove: () => {}, -}) - -export function Provider(props: React.PropsWithChildren<{}>) { - const map = React.useRef<ComponentMap>({}) - const [outlet, setOutlet] = React.useState<ContextType['outlet']>(null) - - const append = React.useCallback<ContextType['append']>((id, component) => { - if (map.current[id]) return - map.current[id] = <React.Fragment key={id}>{component}</React.Fragment> - setOutlet(<>{Object.values(map.current)}</>) - }, []) - - const remove = React.useCallback<ContextType['remove']>(id => { - delete map.current[id] - setOutlet(<>{Object.values(map.current)}</>) - }, []) - - return ( - <Context.Provider value={{outlet, append, remove}}> - {props.children} - </Context.Provider> - ) -} +export function createPortalGroup() { + const Context = React.createContext<ContextType>({ + outlet: null, + append: () => {}, + remove: () => {}, + }) -export function Outlet() { - const ctx = React.useContext(Context) - return ctx.outlet -} + function Provider(props: React.PropsWithChildren<{}>) { + const map = React.useRef<ComponentMap>({}) + const [outlet, setOutlet] = React.useState<ContextType['outlet']>(null) + + const append = React.useCallback<ContextType['append']>((id, component) => { + if (map.current[id]) return + map.current[id] = <React.Fragment key={id}>{component}</React.Fragment> + setOutlet(<>{Object.values(map.current)}</>) + }, []) + + const remove = React.useCallback<ContextType['remove']>(id => { + delete map.current[id] + setOutlet(<>{Object.values(map.current)}</>) + }, []) -export function Portal({children}: React.PropsWithChildren<{}>) { - const {append, remove} = React.useContext(Context) - const id = React.useId() - React.useEffect(() => { - append(id, children as Component) - return () => remove(id) - }, [id, children, append, remove]) - return null + return ( + <Context.Provider value={{outlet, append, remove}}> + {props.children} + </Context.Provider> + ) + } + + function Outlet() { + const ctx = React.useContext(Context) + return ctx.outlet + } + + function Portal({children}: React.PropsWithChildren<{}>) { + const {append, remove} = React.useContext(Context) + const id = React.useId() + React.useEffect(() => { + append(id, children as Component) + return () => remove(id) + }, [id, children, append, remove]) + return null + } + + return {Provider, Outlet, Portal} } + +const DefaultPortal = createPortalGroup() +export const Provider = DefaultPortal.Provider +export const Outlet = DefaultPortal.Outlet +export const Portal = DefaultPortal.Portal diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx new file mode 100644 index 000000000..068ee99e0 --- /dev/null +++ b/src/components/RichText.tsx @@ -0,0 +1,131 @@ +import React from 'react' +import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api' + +import {atoms as a, TextStyleProp} from '#/alf' +import {InlineLink} from '#/components/Link' +import {Text} from '#/components/Typography' +import {toShortUrl} from 'lib/strings/url-helpers' +import {getAgent} from '#/state/session' + +const WORD_WRAP = {wordWrap: 1} + +export function RichText({ + testID, + value, + style, + numberOfLines, + disableLinks, + resolveFacets = false, +}: TextStyleProp & { + value: RichTextAPI | string + testID?: string + numberOfLines?: number + disableLinks?: boolean + resolveFacets?: boolean +}) { + const detected = React.useRef(false) + const [richText, setRichText] = React.useState<RichTextAPI>(() => + value instanceof RichTextAPI ? value : new RichTextAPI({text: value}), + ) + const styles = [a.leading_normal, style] + + React.useEffect(() => { + if (!resolveFacets) return + + async function detectFacets() { + const rt = new RichTextAPI({text: richText.text}) + await rt.detectFacets(getAgent()) + setRichText(rt) + } + + if (!detected.current) { + detected.current = true + detectFacets() + } + }, [richText, setRichText, resolveFacets]) + + const {text, facets} = richText + + if (!facets?.length) { + if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) { + return ( + <Text + testID={testID} + style={[ + { + fontSize: 26, + lineHeight: 30, + }, + ]} + // @ts-ignore web only -prf + dataSet={WORD_WRAP}> + {text} + </Text> + ) + } + return ( + <Text + testID={testID} + style={styles} + numberOfLines={numberOfLines} + // @ts-ignore web only -prf + dataSet={WORD_WRAP}> + {text} + </Text> + ) + } + + const els = [] + let key = 0 + // N.B. must access segments via `richText.segments`, not via destructuring + for (const segment of richText.segments()) { + const link = segment.link + const mention = segment.mention + if ( + mention && + AppBskyRichtextFacet.validateMention(mention).success && + !disableLinks + ) { + els.push( + <InlineLink + key={key} + to={`/profile/${mention.did}`} + style={[...styles, {pointerEvents: 'auto'}]} + // @ts-ignore TODO + dataSet={WORD_WRAP}> + {segment.text} + </InlineLink>, + ) + } else if (link && AppBskyRichtextFacet.validateLink(link).success) { + if (disableLinks) { + els.push(toShortUrl(segment.text)) + } else { + els.push( + <InlineLink + key={key} + to={link.uri} + style={[...styles, {pointerEvents: 'auto'}]} + // @ts-ignore TODO + dataSet={WORD_WRAP} + warnOnMismatchingLabel> + {toShortUrl(segment.text)} + </InlineLink>, + ) + } + } else { + els.push(segment.text) + } + key++ + } + + return ( + <Text + testID={testID} + style={styles} + numberOfLines={numberOfLines} + // @ts-ignore web only -prf + dataSet={WORD_WRAP}> + {els} + </Text> + ) +} diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx index 66cf0720d..64aa6d1a4 100644 --- a/src/components/Typography.tsx +++ b/src/components/Typography.tsx @@ -1,11 +1,50 @@ import React from 'react' -import {Text as RNText, TextProps} from 'react-native' +import {Text as RNText, TextStyle, TextProps} from 'react-native' import {useTheme, atoms, web, flatten} from '#/alf' +/** + * Util to calculate lineHeight from a text size atom and a leading atom + * + * Example: + * `leading(atoms.text_md, atoms.leading_normal)` // => 24 + */ +export function leading< + Size extends {fontSize?: number}, + Leading extends {lineHeight?: number}, +>(textSize: Size, leading: Leading) { + const size = textSize?.fontSize || atoms.text_md.fontSize + const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight + return size * lineHeight +} + +/** + * Ensures that `lineHeight` defaults to a relative value of `1`, or applies + * other relative leading atoms. + * + * If the `lineHeight` value is > 2, we assume it's an absolute value and + * returns it as-is. + */ +function normalizeTextStyles(styles: TextStyle[]) { + const s = flatten(styles) + // should always be defined on these components + const fontSize = s.fontSize || atoms.text_md.fontSize + + if (s?.lineHeight) { + if (s.lineHeight <= 2) { + s.lineHeight = fontSize * s.lineHeight + } + } else { + s.lineHeight = fontSize + } + + return s +} + export function Text({style, ...rest}: TextProps) { const t = useTheme() - return <RNText style={[atoms.text_sm, t.atoms.text, style]} {...rest} /> + const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)]) + return <RNText style={s} {...rest} /> } export function H1({style, ...rest}: TextProps) { @@ -19,7 +58,12 @@ export function H1({style, ...rest}: TextProps) { <RNText {...attr} {...rest} - style={[atoms.text_5xl, atoms.font_bold, t.atoms.text, flatten(style)]} + style={normalizeTextStyles([ + atoms.text_5xl, + atoms.font_bold, + t.atoms.text, + flatten(style), + ])} /> ) } @@ -35,7 +79,12 @@ export function H2({style, ...rest}: TextProps) { <RNText {...attr} {...rest} - style={[atoms.text_4xl, atoms.font_bold, t.atoms.text, flatten(style)]} + style={normalizeTextStyles([ + atoms.text_4xl, + atoms.font_bold, + t.atoms.text, + flatten(style), + ])} /> ) } @@ -51,7 +100,12 @@ export function H3({style, ...rest}: TextProps) { <RNText {...attr} {...rest} - style={[atoms.text_3xl, atoms.font_bold, t.atoms.text, flatten(style)]} + style={normalizeTextStyles([ + atoms.text_3xl, + atoms.font_bold, + t.atoms.text, + flatten(style), + ])} /> ) } @@ -67,7 +121,12 @@ export function H4({style, ...rest}: TextProps) { <RNText {...attr} {...rest} - style={[atoms.text_2xl, atoms.font_bold, t.atoms.text, flatten(style)]} + style={normalizeTextStyles([ + atoms.text_2xl, + atoms.font_bold, + t.atoms.text, + flatten(style), + ])} /> ) } @@ -83,7 +142,12 @@ export function H5({style, ...rest}: TextProps) { <RNText {...attr} {...rest} - style={[atoms.text_xl, atoms.font_bold, t.atoms.text, flatten(style)]} + style={normalizeTextStyles([ + atoms.text_xl, + atoms.font_bold, + t.atoms.text, + flatten(style), + ])} /> ) } @@ -99,7 +163,12 @@ export function H6({style, ...rest}: TextProps) { <RNText {...attr} {...rest} - style={[atoms.text_lg, atoms.font_bold, t.atoms.text, flatten(style)]} + style={normalizeTextStyles([ + atoms.text_lg, + atoms.font_bold, + t.atoms.text, + flatten(style), + ])} /> ) } @@ -110,15 +179,16 @@ export function P({style, ...rest}: TextProps) { web({ role: 'paragraph', }) || {} - const _style = flatten(style) - const lineHeight = - (_style?.lineHeight || atoms.text_md.lineHeight) * - atoms.leading_normal.lineHeight return ( <RNText {...attr} {...rest} - style={[atoms.text_md, t.atoms.text, _style, {lineHeight}]} + style={normalizeTextStyles([ + atoms.text_md, + atoms.leading_normal, + t.atoms.text, + flatten(style), + ])} /> ) } diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index 1ee58303a..67515049c 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -208,7 +208,7 @@ export function createInput(Component: typeof TextInput) { paddingBottom: 2, }), { - lineHeight: a.text_md.lineHeight * 1.1875, + lineHeight: a.text_md.fontSize * 1.1875, textAlignVertical: rest.multiline ? 'top' : undefined, minHeight: rest.multiline ? 60 : undefined, }, diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx index ad82bdff5..d3c034246 100644 --- a/src/components/forms/Toggle.tsx +++ b/src/components/forms/Toggle.tsx @@ -2,7 +2,7 @@ import React from 'react' import {Pressable, View, ViewStyle} from 'react-native' import {HITSLOP_10} from 'lib/constants' -import {useTheme, atoms as a, web, native} from '#/alf' +import {useTheme, atoms as a, web, native, flatten, ViewStyleProp} from '#/alf' import {Text} from '#/components/Typography' import {useInteractionState} from '#/components/hooks/useInteractionState' @@ -49,7 +49,7 @@ export type GroupProps = React.PropsWithChildren<{ label: string }> -export type ItemProps = { +export type ItemProps = ViewStyleProp & { type?: 'radio' | 'checkbox' name: string label: string @@ -57,7 +57,6 @@ export type ItemProps = { disabled?: boolean onChange?: (selected: boolean) => void isInvalid?: boolean - style?: (state: ItemState) => ViewStyle children: ((props: ItemState) => React.ReactNode) | React.ReactNode } @@ -125,6 +124,7 @@ export function Group({ return ( <GroupContext.Provider value={context}> <View + style={[a.w_full]} role={groupRole} {...(groupRole === 'radiogroup' ? { @@ -224,7 +224,7 @@ export function Item({ a.align_center, a.gap_sm, focused ? web({outline: 'none'}) : {}, - style?.(state), + flatten(style), ]}> {typeof children === 'function' ? children(state) : children} </Pressable> diff --git a/src/components/forms/ToggleButton.tsx b/src/components/forms/ToggleButton.tsx index 615fedae8..5cd51d794 100644 --- a/src/components/forms/ToggleButton.tsx +++ b/src/components/forms/ToggleButton.tsx @@ -20,6 +20,7 @@ export function Group({children, multiple, ...props}: GroupProps) { <Toggle.Group type={multiple ? 'checkbox' : 'radio'} {...props}> <View style={[ + a.w_full, a.flex_row, a.border, a.rounded_sm, @@ -34,7 +35,7 @@ export function Group({children, multiple, ...props}: GroupProps) { export function Button({children, ...props}: ItemProps) { return ( - <Toggle.Item {...props}> + <Toggle.Item {...props} style={[a.flex_grow]}> <ButtonInner>{children}</ButtonInner> </Toggle.Item> ) @@ -95,11 +96,12 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) { borderLeftWidth: 1, marginLeft: -1, }, - a.px_lg, + a.flex_grow, a.py_md, native({ - paddingTop: 14, + paddingBottom: 10, }), + a.px_sm, t.atoms.bg, t.atoms.border, baseStyles, diff --git a/src/components/icons/ArrowRotateCounterClockwise.tsx b/src/components/icons/ArrowRotateCounterClockwise.tsx new file mode 100644 index 000000000..35cd23a97 --- /dev/null +++ b/src/components/icons/ArrowRotateCounterClockwise.tsx @@ -0,0 +1,6 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded = + createSinglePathSVG({ + path: 'M5 3a1 1 0 0 1 1 1v1.423c.498-.46 1.02-.869 1.58-1.213C8.863 3.423 10.302 3 12.028 3a9 9 0 1 1-8.487 12 1 1 0 0 1 1.885-.667A7 7 0 1 0 12.028 5c-1.37 0-2.444.327-3.402.915-.474.29-.93.652-1.383 1.085H9a1 1 0 0 1 0 2H5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z', + }) diff --git a/src/components/icons/At.tsx b/src/components/icons/At.tsx new file mode 100644 index 000000000..248725054 --- /dev/null +++ b/src/components/icons/At.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const At_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 4a8 8 0 1 0 4.21 14.804 1 1 0 0 1 1.054 1.7A9.958 9.958 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 1.104-.27 2.31-.949 3.243-.716.984-1.849 1.6-3.331 1.465a4.207 4.207 0 0 1-2.93-1.585c-.94 1.21-2.388 1.94-3.985 1.715-2.53-.356-4.04-2.91-3.682-5.458.358-2.547 2.514-4.586 5.044-4.23.905.127 1.68.536 2.286 1.126a1 1 0 0 1 1.964.368l-.515 3.545v.002a2.222 2.222 0 0 0 1.999 2.526c.75.068 1.212-.21 1.533-.65.358-.493.566-1.245.566-2.067a8 8 0 0 0-8-8Zm-.112 5.13c-1.195-.168-2.544.819-2.784 2.529-.24 1.71.784 3.03 1.98 3.198 1.195.168 2.543-.819 2.784-2.529.24-1.71-.784-3.03-1.98-3.198Z', +}) diff --git a/src/components/icons/Check.tsx b/src/components/icons/Check.tsx new file mode 100644 index 000000000..24316c784 --- /dev/null +++ b/src/components/icons/Check.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Check_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M21.59 3.193a1 1 0 0 1 .217 1.397l-11.706 16a1 1 0 0 1-1.429.193l-6.294-5a1 1 0 1 1 1.244-1.566l5.48 4.353 11.09-15.16a1 1 0 0 1 1.398-.217Z', +}) diff --git a/src/components/icons/Chevron.tsx b/src/components/icons/Chevron.tsx new file mode 100644 index 000000000..b1a9deea0 --- /dev/null +++ b/src/components/icons/Chevron.tsx @@ -0,0 +1,9 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ChevronLeft_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M15.707 3.293a1 1 0 0 1 0 1.414L8.414 12l7.293 7.293a1 1 0 0 1-1.414 1.414l-8-8a1 1 0 0 1 0-1.414l8-8a1 1 0 0 1 1.414 0Z', +}) + +export const ChevronRight_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M8.293 3.293a1 1 0 0 1 1.414 0l8 8a1 1 0 0 1 0 1.414l-8 8a1 1 0 0 1-1.414-1.414L15.586 12 8.293 4.707a1 1 0 0 1 0-1.414Z', +}) diff --git a/src/components/icons/CircleInfo.tsx b/src/components/icons/CircleInfo.tsx new file mode 100644 index 000000000..cc3813bf3 --- /dev/null +++ b/src/components/icons/CircleInfo.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const CircleInfo_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8-1a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0v-4a1 1 0 0 1-1-1Zm1-3a1 1 0 1 0 2 0 1 1 0 0 0-2 0Z', +}) diff --git a/src/components/icons/Emoji.tsx b/src/components/icons/Emoji.tsx new file mode 100644 index 000000000..568cd71e6 --- /dev/null +++ b/src/components/icons/Emoji.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const EmojiSad_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M6.343 6.343a8 8 0 1 1 11.314 11.314A8 8 0 0 1 6.343 6.343ZM19.071 4.93c-3.905-3.905-10.237-3.905-14.142 0-3.905 3.905-3.905 10.237 0 14.142 3.905 3.905 10.237 3.905 14.142 0 3.905-3.905 3.905-10.237 0-14.142Zm-3.537 9.535a5 5 0 0 0-7.07 0 1 1 0 1 0 1.413 1.415 3 3 0 0 1 4.243 0 1 1 0 0 0 1.414-1.415ZM16 9.5c0 .828-.56 1.5-1.25 1.5s-1.25-.672-1.25-1.5.56-1.5 1.25-1.5S16 8.672 16 9.5ZM9.25 11c.69 0 1.25-.672 1.25-1.5S9.94 8 9.25 8 8 8.672 8 9.5 8.56 11 9.25 11Z', +}) diff --git a/src/components/icons/EyeSlash.tsx b/src/components/icons/EyeSlash.tsx new file mode 100644 index 000000000..a936a1c71 --- /dev/null +++ b/src/components/icons/EyeSlash.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const EyeSlash_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M2.293 2.293a1 1 0 0 1 1.414 0L7.335 5.92l.03.03 3.22 3.222 4.243 4.242 3.22 3.22.03.03 3.63 3.629a1 1 0 0 1-1.415 1.414l-3.09-3.09c-2.65 1.478-5.625 1.778-8.421.869-3.039-.987-5.779-3.37-7.67-7.027a1 1 0 0 1 0-.918c1.086-2.1 2.452-3.78 3.996-5.019L2.293 3.707a1 1 0 0 1 0-1.414Zm4.24 5.654 2.021 2.021a4 4 0 0 0 5.478 5.478l1.688 1.688c-2.042.982-4.246 1.124-6.32.45-2.34-.76-4.594-2.586-6.265-5.584.97-1.739 2.135-3.083 3.398-4.053Zm3.535 3.535 2.45 2.45a2 2 0 0 1-2.45-2.45Zm.81-5.405c3.573-.49 7.45 1.369 9.987 5.923a14.797 14.797 0 0 1-1.347 2.02 1 1 0 1 0 1.564 1.247 17.078 17.078 0 0 0 1.806-2.808 1 1 0 0 0 0-.918c-2.833-5.479-7.584-8.088-12.281-7.446a1 1 0 0 0 .271 1.982Z', +}) diff --git a/src/components/icons/FilterTimeline.tsx b/src/components/icons/FilterTimeline.tsx new file mode 100644 index 000000000..ea11a429c --- /dev/null +++ b/src/components/icons/FilterTimeline.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const FilterTimeline_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M7.002 5a1 1 0 0 0-2 0v11.587l-1.295-1.294a1 1 0 0 0-1.414 1.414l3.002 3a1 1 0 0 0 1.414 0l2.998-3a1 1 0 0 0-1.414-1.414l-1.291 1.292V5ZM16 16a1 1 0 1 0 0 2h4a1 1 0 1 0 0-2h-4Zm-3-4a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2h-6a1 1 0 0 1-1-1Zm-1-6a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2h-8Z', +}) diff --git a/src/components/icons/Growth.tsx b/src/components/icons/Growth.tsx new file mode 100644 index 000000000..ab5684a57 --- /dev/null +++ b/src/components/icons/Growth.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Growth_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 4a1 1 0 0 1 1-1h1a8.003 8.003 0 0 1 7.75 6.006A7.985 7.985 0 0 1 19 6h1a1 1 0 0 1 1 1v1a8 8 0 0 1-8 8v4a1 1 0 1 1-2 0v-7a8 8 0 0 1-8-8V4Zm2 1a6 6 0 0 1 6 6 6 6 0 0 1-6-6Zm8 9a6 6 0 0 1 6-6 6 6 0 0 1-6 6Z', +}) diff --git a/src/components/icons/Hashtag.tsx b/src/components/icons/Hashtag.tsx new file mode 100644 index 000000000..668ed9256 --- /dev/null +++ b/src/components/icons/Hashtag.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Hashtag_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M9.124 3.008a1 1 0 0 1 .868 1.116L9.632 7h5.985l.39-3.124a1 1 0 0 1 1.985.248L17.632 7H20a1 1 0 1 1 0 2h-2.617l-.75 6H20a1 1 0 1 1 0 2h-3.617l-.39 3.124a1 1 0 1 1-1.985-.248l.36-2.876H8.382l-.39 3.124a1 1 0 1 1-1.985-.248L6.368 17H4a1 1 0 1 1 0-2h2.617l.75-6H4a1 1 0 1 1 0-2h3.617l.39-3.124a1 1 0 0 1 1.117-.868ZM9.383 9l-.75 6h5.984l.75-6H9.383Z', +}) diff --git a/src/components/icons/ListMagnifyingGlass.tsx b/src/components/icons/ListMagnifyingGlass.tsx new file mode 100644 index 000000000..a897fe853 --- /dev/null +++ b/src/components/icons/ListMagnifyingGlass.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ListMagnifyingGlass_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 4a1 1 0 0 1 1-1h13a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm1 4a1 1 0 0 0 0 2h5a1 1 0 0 0 0-2H4Zm-1 7a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm0 5a1 1 0 0 1 1-1h13a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm9-8a4 4 0 1 1 7.446 2.032l.99.989a1 1 0 1 1-1.415 1.414l-.99-.989A4 4 0 0 1 12 12Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z', +}) diff --git a/src/components/icons/ListSparkle.tsx b/src/components/icons/ListSparkle.tsx new file mode 100644 index 000000000..4d472465d --- /dev/null +++ b/src/components/icons/ListSparkle.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ListSparkle_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M4 5a1 1 0 0 0 0 2h16a1 1 0 1 0 0-2H4Zm0 12a1 1 0 1 0 0 2h3a1 1 0 1 0 0-2H4Zm-1-5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm14-3a1 1 0 0 1 .92.606l1.342 3.132 3.132 1.343a1 1 0 0 1 0 1.838l-3.132 1.343-1.343 3.132a1 1 0 0 1-1.838 0l-1.343-3.132-3.132-1.343a1 1 0 0 1 0-1.838l3.132-1.343 1.343-3.132A1 1 0 0 1 17 9Zm0 3.539-.58 1.355a1 1 0 0 1-.526.525L14.539 15l1.355.58a1 1 0 0 1 .525.526L17 17.461l.58-1.355a1 1 0 0 1 .526-.525L19.461 15l-1.355-.58a1 1 0 0 1-.525-.526L17 12.539Z', +}) diff --git a/src/components/icons/News2.tsx b/src/components/icons/News2.tsx new file mode 100644 index 000000000..f2124e7b8 --- /dev/null +++ b/src/components/icons/News2.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const News2_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M1 5a1 1 0 0 1 1-1h7a3.99 3.99 0 0 1 3 1.354A3.99 3.99 0 0 1 15 4h7a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-6.723c-.52 0-1 .125-1.4.372-.421.26-.761.633-.983 1.075a1 1 0 0 1-1.788 0 2.664 2.664 0 0 0-.983-1.075c-.4-.247-.88-.372-1.4-.372H2a1 1 0 0 1-1-1V5Zm10 3a2 2 0 0 0-2-2H3v12h5.723c.776 0 1.564.173 2.277.569V8Zm2 10.569V8a2 2 0 0 1 2-2h6v12h-5.723c-.776 0-1.564.173-2.277.569Z', +}) diff --git a/src/components/icons/Plus.tsx b/src/components/icons/Plus.tsx new file mode 100644 index 000000000..d0698f7f4 --- /dev/null +++ b/src/components/icons/Plus.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const PlusLarge_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M12 3a1 1 0 0 1 1 1v7h7a1 1 0 1 1 0 2h-7v7a1 1 0 1 1-2 0v-7H4a1 1 0 1 1 0-2h7V4a1 1 0 0 1 1-1Z', +}) diff --git a/src/components/icons/Trending2.tsx b/src/components/icons/Trending2.tsx new file mode 100644 index 000000000..5fba4167b --- /dev/null +++ b/src/components/icons/Trending2.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Trending2_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'm18.192 5.004 1.864 5.31a1 1 0 0 0 1.887-.662L20.08 4.34c-.665-1.893-3.378-1.741-3.834.207l-3.381 14.449-2.985-9.605C9.3 7.531 6.684 7.506 6.07 9.355l-1.18 3.56-.969-2.312a1 1 0 0 0-1.844.772l.97 2.315c.715 1.71 3.159 1.613 3.741-.144l1.18-3.56 2.985 9.605c.607 1.952 3.392 1.848 3.857-.138l3.381-14.449Z', +}) |