New component library based on ALF (#2459)

* Install on native as well

* Add button and link components

* Comments

* Use new prop

* Add some form elements

* Add labels to input

* Fix line height, add suffix

* Date inputs

* Autofill styles

* Clean up InputDate types

* Improve types for InputText, value handling

* Enforce a11y props on buttons

* Add Dialog, Portal

* Dialog contents

* Native dialog

* Clean up

* Fix animations

* Improvements to web modal, exiting still broken

* Clean up dialog types

* Add Prompt, Dialog refinement, mobile refinement

* Integrate new design tokens, reorg storybook

* Button colors

* Dim mode

* Reorg

* Some styles

* Toggles

* Improve a11y

* Autosize dialog, handle max height, Dialog.ScrolLView not working

* Try to use BottomSheet's own APIs

* Scrollable dialogs

* Add web shadow

* Handle overscroll

* Styles

* Dialog text input

* Shadows

* Button focus states

* Button pressed states

* Gradient poc

* Gradient colors and hovers

* Add hrefAttrs to Link

* Some more a11y

* Toggle invalid states

* Update dialog descriptions for demo

* Icons

* WIP Toggle cleanup

* Refactor toggle to not rely on immediate children

* Make Toggle controlled

* Clean up Toggles storybook

* ToggleButton styles

* Improve a11y labels

* ToggleButton hover darkmode

* Some i18n

* Refactor input

* Allow extension of input

* Remove old input

* Improve icons, add CalendarDays

* Refactor DateField, web done

* Add label example

* Clean up old InputDate, DateField android, text area example

* Consistent imports

* Button context, icons

* Add todo

* Add closeAllDialogs control

* Alignment

* Expand color palette

* Hitslops, add shortcut to Storybook in dev

* Fix multiline on ios

* Mark dialog close button as unused
This commit is contained in:
Eric Bailey 2024-01-18 20:28:04 -06:00 committed by GitHub
parent 9cbd3c0937
commit 66b8774ecb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 4683 additions and 968 deletions

191
src/components/Link.tsx Normal file
View file

@ -0,0 +1,191 @@
import React from 'react'
import {
Text,
TextStyle,
StyleProp,
GestureResponderEvent,
Linking,
} from 'react-native'
import {
useLinkProps,
useNavigation,
StackActions,
} from '@react-navigation/native'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {isWeb} from '#/platform/detection'
import {useTheme, web, flatten} from '#/alf'
import {Button, ButtonProps, useButtonContext} from '#/components/Button'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {
convertBskyAppUrlIfNeeded,
isExternalUrl,
linkRequiresWarning,
} from '#/lib/strings/url-helpers'
import {useModalControls} from '#/state/modals'
import {router} from '#/routes'
export type LinkProps = Omit<
ButtonProps,
'style' | 'onPress' | 'disabled' | 'label'
> & {
/**
* `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.
*/
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,
to,
action = 'push',
warnOnMismatchingTextChild,
style,
...rest
}: LinkProps) {
const navigation = useNavigation<NavigationProp>()
const {href} = useLinkProps<AllNavigatorParams>({
to:
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
})
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 &&
isExternal &&
linkRequiresWarning(href, stringChildren),
)
if (requiresWarning) {
e.preventDefault()
openModal({
name: 'link-warning',
text: stringChildren,
href: href,
})
} else {
e.preventDefault()
if (isExternal) {
Linking.openURL(href)
} else {
/**
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
* of @ts-ignore below.
*/
const event = e as any
const isMiddleClick = isWeb && event.button === 1
const isMetaKey =
isWeb &&
(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
const shouldOpenInNewTab = isMetaKey || isMiddleClick
if (
shouldOpenInNewTab ||
href.startsWith('http') ||
href.startsWith('mailto')
) {
Linking.openURL(href)
} else {
closeModal() // close any active modals
if (action === 'push') {
navigation.dispatch(StackActions.push(...router.matchPath(href)))
} else if (action === 'replace') {
navigation.dispatch(
StackActions.replace(...router.matchPath(href)),
)
} else if (action === 'navigate') {
// @ts-ignore
navigation.navigate(...router.matchPath(href))
} else {
throw Error('Unsupported navigator action.')
}
}
}
}
},
[
href,
isExternal,
warnOnMismatchingTextChild,
navigation,
action,
children,
closeModal,
openModal,
],
)
return (
<Button
label={href}
{...rest}
role="link"
accessibilityRole="link"
href={href}
onPress={onPress}
{...web({
hrefAttrs: {
target: isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined,
},
dataSet: {
// default to no underline, apply this ourselves
noUnderline: '1',
},
})}>
{typeof children === 'string' ? (
<LinkText style={style}>{children}</LinkText>
) : (
children
)}
</Button>
)
}
function LinkText({
children,
style,
}: React.PropsWithChildren<{
style?: StyleProp<TextStyle>
}>) {
const t = useTheme()
const {hovered} = useButtonContext()
return (
<Text
style={[
{color: t.palette.primary_500},
hovered && {
textDecorationLine: 'underline',
textDecorationColor: t.palette.primary_500,
},
flatten(style),
]}>
{children as string}
</Text>
)
}