From 6ee1aedf802ac0d05014bd910a986f58d12b1346 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 9 Jan 2024 14:20:06 -0600
Subject: [PATCH 01/68] Install on native as well
---
src/App.native.tsx | 43 ++++++++++++++++++++++++-------------------
src/App.web.tsx | 2 +-
2 files changed, 25 insertions(+), 20 deletions(-)
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 6402b4a898..a9dfafe36f 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -13,6 +13,8 @@ import {
import 'view/icons'
+import {ThemeProvider as Alf} from '#/alf'
+import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {init as initPersistedState} from '#/state/persisted'
import {listenSessionDropped} from './state/events'
import {useColorMode} from 'state/shell'
@@ -46,6 +48,7 @@ function InnerApp() {
const colorMode = useColorMode()
const {isInitialLoad, currentAccount} = useSession()
const {resumeSession} = useSessionApi()
+ const theme = useColorModeTheme(colorMode)
// init
useEffect(() => {
@@ -60,25 +63,27 @@ function InnerApp() {
return (
-
-
-
-
-
- {/* All components should be within this provider */}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ {/* All components should be within this provider */}
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 1bdb3c208c..9ee9a4fd71 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -8,6 +8,7 @@ import {RootSiblingParent} from 'react-native-root-siblings'
import 'view/icons'
import {ThemeProvider as Alf} from '#/alf'
+import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {init as initPersistedState} from '#/state/persisted'
import {useColorMode} from 'state/shell'
import {Shell} from 'view/shell/index'
@@ -29,7 +30,6 @@ import {
} from 'state/session'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
-import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
function InnerApp() {
const {isInitialLoad, currentAccount} = useSession()
From 3e055025c762d08722d5269b4bea780c8d1d7ad4 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 9 Jan 2024 14:23:18 -0600
Subject: [PATCH 02/68] Add button and link components
---
src/view/com/Button.tsx | 239 ++++++++++++++++++++++------------
src/view/com/Link.tsx | 162 +++++++++++++++++++++++
src/view/screens/DebugNew.tsx | 115 ++++++++++------
3 files changed, 391 insertions(+), 125 deletions(-)
create mode 100644 src/view/com/Link.tsx
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
index d1f70d4aeb..259ae59771 100644
--- a/src/view/com/Button.tsx
+++ b/src/view/com/Button.tsx
@@ -1,86 +1,47 @@
import React from 'react'
-import {Pressable, Text, PressableProps, TextProps} from 'react-native'
-import * as tokens from '#/alf/tokens'
-import {atoms} from '#/alf'
-
-export type ButtonType =
- | 'primary'
- | 'secondary'
- | 'tertiary'
- | 'positive'
- | 'negative'
+import {
+ Pressable,
+ Text,
+ PressableProps,
+ TextProps,
+ ViewStyle,
+} from 'react-native'
+
+import {atoms, tokens, web, native} from '#/alf'
+
+export type ButtonType = 'primary' | 'secondary' | 'negative'
export type ButtonSize = 'small' | 'large'
export type VariantProps = {
type?: ButtonType
size?: ButtonSize
}
-type ButtonState = {
- pressed: boolean
- hovered: boolean
- focused: boolean
-}
export type ButtonProps = Omit &
VariantProps & {
children:
| ((props: {
- state: ButtonState
- type?: ButtonType
- size?: ButtonSize
+ state: {
+ pressed: boolean
+ hovered: boolean
+ focused: boolean
+ }
+ props: VariantProps & {
+ disabled?: boolean
+ }
}) => React.ReactNode)
| React.ReactNode
| string
}
-export type ButtonTextProps = TextProps & VariantProps
-
-export function Button({children, style, type, size, ...rest}: ButtonProps) {
- const {baseStyles, hoverStyles} = React.useMemo(() => {
- const baseStyles = []
- const hoverStyles = []
-
- switch (type) {
- case 'primary':
- baseStyles.push({
- backgroundColor: tokens.color.blue_500,
- })
- break
- case 'secondary':
- baseStyles.push({
- backgroundColor: tokens.color.gray_200,
- })
- hoverStyles.push({
- backgroundColor: tokens.color.gray_100,
- })
- break
- default:
- }
-
- switch (size) {
- case 'large':
- baseStyles.push(
- atoms.py_md,
- atoms.px_xl,
- atoms.rounded_md,
- atoms.gap_sm,
- )
- break
- case 'small':
- baseStyles.push(
- atoms.py_sm,
- atoms.px_md,
- atoms.rounded_sm,
- atoms.gap_xs,
- )
- break
- default:
- }
-
- return {
- baseStyles,
- hoverStyles,
- }
- }, [type, size])
+export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
+export function Button({
+ children,
+ style,
+ type,
+ size,
+ disabled = false,
+ ...rest
+}: ButtonProps) {
const [state, setState] = React.useState({
pressed: false,
hovered: false,
@@ -124,10 +85,99 @@ export function Button({children, style, type, size, ...rest}: ButtonProps) {
}))
}, [setState])
+ const {baseStyles, hoverStyles} = React.useMemo(() => {
+ const baseStyles: ViewStyle[] = []
+ const hoverStyles: ViewStyle[] = []
+
+ switch (type) {
+ case 'primary': {
+ if (disabled) {
+ baseStyles.push({
+ backgroundColor: tokens.color.blue_300,
+ })
+ } else {
+ baseStyles.push({
+ backgroundColor: tokens.color.blue_500,
+ })
+ }
+ break
+ }
+ case 'secondary': {
+ if (disabled) {
+ baseStyles.push({
+ backgroundColor: tokens.color.gray_100,
+ })
+ } else {
+ baseStyles.push({
+ backgroundColor: tokens.color.gray_200,
+ })
+ }
+ break
+ }
+ case 'negative': {
+ if (disabled) {
+ baseStyles.push({
+ backgroundColor: tokens.color.red_400,
+ })
+ } else {
+ baseStyles.push({
+ backgroundColor: tokens.color.red_500,
+ })
+ }
+ break
+ }
+ default:
+ }
+
+ switch (size) {
+ case 'large': {
+ baseStyles.push(
+ atoms.py_md,
+ atoms.px_xl,
+ atoms.rounded_md,
+ atoms.gap_sm,
+ )
+ break
+ }
+ case 'small': {
+ baseStyles.push(
+ atoms.py_sm,
+ atoms.px_md,
+ atoms.rounded_sm,
+ atoms.gap_xs,
+ )
+ break
+ }
+ default:
+ }
+
+ return {
+ baseStyles,
+ hoverStyles,
+ }
+ }, [type, size, disabled])
+
+ const childProps = React.useMemo(
+ () => ({
+ state,
+ props: {
+ type,
+ size,
+ disabled: disabled || false,
+ },
+ }),
+ [state, type, size, disabled],
+ )
+
return (
[
+ disabled={disabled || false}
+ accessibilityState={{
+ disabled: disabled || false,
+ }}
+ style={[
atoms.flex_row,
atoms.align_center,
...baseStyles,
@@ -141,11 +191,11 @@ export function Button({children, style, type, size, ...rest}: ButtonProps) {
onFocus={onFocus}
onBlur={onBlur}>
{typeof children === 'string' ? (
-
+
{children}
) : typeof children === 'function' ? (
- children({state, type, size})
+ children(childProps)
) : (
children
)}
@@ -158,35 +208,60 @@ export function ButtonText({
style,
type,
size,
+ disabled,
...rest
}: ButtonTextProps) {
const textStyles = React.useMemo(() => {
- const base = []
+ const baseStyles = []
switch (type) {
- case 'primary':
- base.push({color: tokens.color.white})
+ case 'primary': {
+ baseStyles.push({color: tokens.color.white})
+ break
+ }
+ case 'secondary': {
+ if (disabled) {
+ baseStyles.push({
+ color: tokens.color.gray_500,
+ })
+ } else {
+ baseStyles.push({
+ color: tokens.color.gray_700,
+ })
+ }
break
- case 'secondary':
- base.push({
- color: tokens.color.gray_700,
+ }
+ case 'negative': {
+ baseStyles.push({
+ color: tokens.color.white,
})
break
+ }
default:
}
switch (size) {
- case 'small':
- base.push(atoms.text_sm, {paddingBottom: 1})
+ case 'small': {
+ baseStyles.push(
+ atoms.text_sm,
+ web({paddingBottom: 1}),
+ native({marginTop: 2}),
+ )
break
- case 'large':
- base.push(atoms.text_md, {paddingBottom: 1})
+ }
+ case 'large': {
+ baseStyles.push(
+ atoms.text_md,
+ web({paddingBottom: 1}),
+ native({marginTop: 2}),
+ )
break
+ }
default:
}
- return base
- }, [type, size])
+ return baseStyles
+ }, [type, size, disabled])
return (
& {
+ style?: StyleProp // only accept text styles on element itself
+ warnOnMismatchingLabel?: boolean
+ action?: 'push' | 'replace' | 'navigate'
+} & Pick>[0], 'to'>
+
+/**
+ * A interactive element that renders as 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,
+ style,
+ action = 'push',
+ warnOnMismatchingLabel,
+ ...rest
+}: LinkProps) {
+ const t = useTheme()
+ const navigation = useNavigation()
+ const {href, accessibilityRole} = useLinkProps({
+ to:
+ typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
+ })
+ const isExternal = isExternalUrl(href)
+ const {openModal, closeModal} = useModalControls()
+ const onPress = React.useCallback(
+ (e: GestureResponderEvent) => {
+ const label = typeof children === 'string' ? children : ''
+ const requiresWarning = Boolean(
+ warnOnMismatchingLabel &&
+ label &&
+ isExternal &&
+ linkRequiresWarning(href, label),
+ )
+
+ if (requiresWarning) {
+ e.preventDefault()
+
+ openModal({
+ name: 'link-warning',
+ text: label,
+ 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,
+ warnOnMismatchingLabel,
+ navigation,
+ action,
+ children,
+ closeModal,
+ openModal,
+ ],
+ )
+
+ return (
+
+ )
+}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index 0b7c5f03bc..b687a0999a 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -7,6 +7,7 @@ import {useSetColorMode} from '#/state/shell'
import * as tokens from '#/alf/tokens'
import {atoms as a, useTheme, useBreakpoints, ThemeProvider as Alf} from '#/alf'
import {Button, ButtonText} from '#/view/com/Button'
+import {Link} from '#/view/com/Link'
import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
function ThemeSelector() {
@@ -142,6 +143,75 @@ function ThemedSection() {
)
}
+export function Buttons() {
+ const t = useTheme()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ External
+
+
+ External with custom children
+
+
+ https://blueskyweb.xyz
+
+
+ Internal
+
+
+
+ {({props}) => Link as a button}
+
+
+ )
+}
+
export function DebugScreen() {
const t = useTheme()
@@ -151,6 +221,8 @@ export function DebugScreen() {
+
+
@@ -173,49 +245,6 @@ export function DebugScreen() {
atoms.text_xs
atoms.text_xxs
-
-
-
-
-
-
-
-
-
-
-
-
-
Date: Tue, 9 Jan 2024 14:27:17 -0600
Subject: [PATCH 03/68] Comments
---
src/view/com/Button.tsx | 8 +++++++-
src/view/com/Link.tsx | 20 +++++++++++++++-----
2 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
index 259ae59771..8aa17ec5a9 100644
--- a/src/view/com/Button.tsx
+++ b/src/view/com/Button.tsx
@@ -11,11 +11,17 @@ import {atoms, tokens, web, native} from '#/alf'
export type ButtonType = 'primary' | 'secondary' | 'negative'
export type ButtonSize = 'small' | 'large'
-
export type VariantProps = {
+ /**
+ * The presentation styles of the button
+ */
type?: ButtonType
+ /**
+ * The size of the button
+ */
size?: ButtonSize
}
+
export type ButtonProps = Omit &
VariantProps & {
children:
diff --git a/src/view/com/Link.tsx b/src/view/com/Link.tsx
index f139e2d45c..8ff28f5fb3 100644
--- a/src/view/com/Link.tsx
+++ b/src/view/com/Link.tsx
@@ -26,9 +26,19 @@ import {useModalControls} from '#/state/modals'
import {router} from '#/routes'
export type LinkProps = Omit & {
- style?: StyleProp // only accept text styles on element itself
- warnOnMismatchingLabel?: boolean
+ /**
+ * `TextStyle` to apply to the anchor element itself. Does not apply to any children.
+ */
+ style?: StyleProp
+ /**
+ * 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
} & Pick>[0], 'to'>
/**
@@ -44,7 +54,7 @@ export function Link({
to,
style,
action = 'push',
- warnOnMismatchingLabel,
+ warnOnMismatchingTextChild,
...rest
}: LinkProps) {
const t = useTheme()
@@ -59,7 +69,7 @@ export function Link({
(e: GestureResponderEvent) => {
const label = typeof children === 'string' ? children : ''
const requiresWarning = Boolean(
- warnOnMismatchingLabel &&
+ warnOnMismatchingTextChild &&
label &&
isExternal &&
linkRequiresWarning(href, label),
@@ -118,7 +128,7 @@ export function Link({
[
href,
isExternal,
- warnOnMismatchingLabel,
+ warnOnMismatchingTextChild,
navigation,
action,
children,
From b5474eb61e4338235c68aa097616c63eabbdc45b Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 9 Jan 2024 14:29:39 -0600
Subject: [PATCH 04/68] Use new prop
---
src/view/screens/DebugNew.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index b687a0999a..e95991c8fa 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -185,7 +185,7 @@ export function Buttons() {
External
@@ -194,13 +194,13 @@ export function Buttons() {
https://blueskyweb.xyz
Internal
From 83876b62e190ffed10eb23c6f999aa919fea00df Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 9 Jan 2024 17:37:43 -0600
Subject: [PATCH 05/68] Add some form elements
---
src/alf/themes.ts | 12 ++-
src/view/com/Button.tsx | 7 +-
src/view/com/forms/InputGroup.tsx | 37 +++++++++
src/view/com/forms/InputText.tsx | 125 ++++++++++++++++++++++++++++++
src/view/icons/Logo.tsx | 6 +-
src/view/screens/DebugNew.tsx | 13 ++++
6 files changed, 189 insertions(+), 11 deletions(-)
create mode 100644 src/view/com/forms/InputGroup.tsx
create mode 100644 src/view/com/forms/InputText.tsx
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index aae5c58931..14e1de7ff6 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -4,14 +4,10 @@ import type {Mutable} from '#/alf/types'
export type ThemeName = 'light' | 'dark'
export type ReadonlyTheme = typeof light
export type Theme = Mutable
+export type ReadonlyPalette = typeof lightPalette
+export type Palette = Mutable
-export type Palette = {
- primary: string
- positive: string
- negative: string
-}
-
-export const lightPalette: Palette = {
+export const lightPalette = {
primary: tokens.color.blue_500,
positive: tokens.color.green_500,
negative: tokens.color.red_500,
@@ -24,6 +20,7 @@ export const darkPalette: Palette = {
} as const
export const light = {
+ name: 'light',
palette: lightPalette,
atoms: {
text: {
@@ -66,6 +63,7 @@ export const light = {
}
export const dark: Theme = {
+ name: 'dark',
palette: darkPalette,
atoms: {
text: {
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
index 8aa17ec5a9..1d858a8eea 100644
--- a/src/view/com/Button.tsx
+++ b/src/view/com/Button.tsx
@@ -7,7 +7,7 @@ import {
ViewStyle,
} from 'react-native'
-import {atoms, tokens, web, native} from '#/alf'
+import {useTheme, atoms, tokens, web, native} from '#/alf'
export type ButtonType = 'primary' | 'secondary' | 'negative'
export type ButtonSize = 'small' | 'large'
@@ -217,6 +217,8 @@ export function ButtonText({
disabled,
...rest
}: ButtonTextProps) {
+ const t = useTheme()
+
const textStyles = React.useMemo(() => {
const baseStyles = []
@@ -244,6 +246,7 @@ export function ButtonText({
break
}
default:
+ baseStyles.push(t.atoms.text)
}
switch (size) {
@@ -267,7 +270,7 @@ export function ButtonText({
}
return baseStyles
- }, [type, size, disabled])
+ }, [t, type, size, disabled])
return (
) {
+ const children = React.Children.toArray(props.children)
+ const total = children.length
+ return (
+
+ {children.map((child, i) => {
+ return React.isValidElement(child) ? (
+
+ {React.cloneElement(child, {
+ // @ts-ignore
+ style: [
+ ...(Array.isArray(child.props?.style)
+ ? child.props.style
+ : [child.props.style || {}]),
+ {
+ borderTopLeftRadius: i > 0 ? 0 : undefined,
+ borderTopRightRadius: i > 0 ? 0 : undefined,
+ borderBottomLeftRadius: i < total - 1 ? 0 : undefined,
+ borderBottomRightRadius: i < total - 1 ? 0 : undefined,
+ borderBottomWidth: i < total - 1 ? 0 : undefined,
+ },
+ ],
+ })}
+
+ ) : null
+ })}
+
+ )
+}
diff --git a/src/view/com/forms/InputText.tsx b/src/view/com/forms/InputText.tsx
new file mode 100644
index 0000000000..4f136a3241
--- /dev/null
+++ b/src/view/com/forms/InputText.tsx
@@ -0,0 +1,125 @@
+import React from 'react'
+import {View, TextInput, TextInputProps, TextStyle} from 'react-native'
+
+import {useTheme, atoms, web, tokens} from '#/alf'
+
+type Props = Omit & {
+ placeholder: string
+ hasError?: boolean
+ icon?: React.FunctionComponent
+}
+
+export function InputText({hasError, icon: Icon, ...props}: Props) {
+ const t = useTheme()
+ const [state, setState] = React.useState({
+ hovered: false,
+ focused: false,
+ })
+
+ const onHoverIn = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ hovered: true,
+ }))
+ }, [setState])
+ const onHoverOut = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ hovered: false,
+ }))
+ }, [setState])
+ const onFocus = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ focused: true,
+ }))
+ }, [setState])
+ const onBlur = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ focused: false,
+ }))
+ }, [setState])
+
+ const {inputStyles, iconStyles} = React.useMemo(() => {
+ const input: TextStyle[] = []
+ const icon: TextStyle[] = []
+
+ if (Icon) {
+ input.push({
+ paddingLeft: 40,
+ })
+ }
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_200,
+ })
+ icon.push({
+ color: tokens.color.red_400,
+ })
+ }
+
+ if (state.hovered || state.focused) {
+ input.push({
+ borderColor: t.atoms.border_contrast_500.borderColor,
+ })
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_500,
+ })
+ }
+ }
+
+ return {inputStyles: input, iconStyles: icon}
+ }, [t, state, hasError, Icon])
+
+ return (
+
+
+
+ {Icon && (
+
+ )}
+
+ )
+}
diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx
index 15ab5a11c0..9f9f57fc85 100644
--- a/src/view/icons/Logo.tsx
+++ b/src/view/icons/Logo.tsx
@@ -1,4 +1,5 @@
import React from 'react'
+import {StyleSheet} from 'react-native'
import Svg, {
Path,
Defs,
@@ -19,7 +20,8 @@ type Props = {
export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
const {fill, ...rest} = props
const gradient = fill === 'sky'
- const _fill = gradient ? 'url(#sky)' : fill || colors.blue3
+ const styles = StyleSheet.flatten(props.style)
+ const _fill = gradient ? 'url(#sky)' : fill || styles?.color || colors.blue3
// @ts-ignore it's fiiiiine
const size = parseInt(rest.width || 32)
return (
@@ -29,7 +31,7 @@ export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
ref={ref}
viewBox="0 0 64 57"
{...rest}
- style={{width: size, height: size * ratio}}>
+ style={[{width: size, height: size * ratio}, styles]}>
{gradient && (
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index e95991c8fa..de08ab8228 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -9,6 +9,8 @@ import {atoms as a, useTheme, useBreakpoints, ThemeProvider as Alf} from '#/alf'
import {Button, ButtonText} from '#/view/com/Button'
import {Link} from '#/view/com/Link'
import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
+import {InputText} from '#/view/com/forms/InputText'
+import {Logo} from '#/view/icons/Logo'
function ThemeSelector() {
const setColorMode = useSetColorMode()
@@ -212,6 +214,16 @@ export function Buttons() {
)
}
+function Forms() {
+ return (
+
+
+
+
+
+ )
+}
+
export function DebugScreen() {
const t = useTheme()
@@ -221,6 +233,7 @@ export function DebugScreen() {
+
From 40446a13044b5d45a920c3c9cf3cea8837255385 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 9 Jan 2024 17:58:47 -0600
Subject: [PATCH 06/68] Add labels to input
---
src/view/com/forms/InputText.tsx | 23 +++++++++++++++++++++--
src/view/screens/DebugNew.tsx | 2 +-
2 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/src/view/com/forms/InputText.tsx b/src/view/com/forms/InputText.tsx
index 4f136a3241..55506b84d7 100644
--- a/src/view/com/forms/InputText.tsx
+++ b/src/view/com/forms/InputText.tsx
@@ -2,14 +2,17 @@ import React from 'react'
import {View, TextInput, TextInputProps, TextStyle} from 'react-native'
import {useTheme, atoms, web, tokens} from '#/alf'
+import {Text} from '#/view/com/Typography'
type Props = Omit & {
+ label?: string
placeholder: string
hasError?: boolean
icon?: React.FunctionComponent
}
-export function InputText({hasError, icon: Icon, ...props}: Props) {
+export function InputText({label, hasError, icon: Icon, ...props}: Props) {
+ const labelId = React.useId()
const t = useTheme()
const [state, setState] = React.useState({
hovered: false,
@@ -77,7 +80,23 @@ export function InputText({hasError, icon: Icon, ...props}: Props) {
return (
+ {label && (
+
+ {label}
+
+ )}
+
-
+
From 214254b410687eb12b48a7ec527f43275cd35d32 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 10:45:55 -0600
Subject: [PATCH 07/68] Fix line height, add suffix
---
src/view/com/forms/InputText.tsx | 71 +++++++++++++++++++++++++-------
src/view/screens/DebugNew.tsx | 6 +++
2 files changed, 62 insertions(+), 15 deletions(-)
diff --git a/src/view/com/forms/InputText.tsx b/src/view/com/forms/InputText.tsx
index 55506b84d7..eb77c6242f 100644
--- a/src/view/com/forms/InputText.tsx
+++ b/src/view/com/forms/InputText.tsx
@@ -1,5 +1,11 @@
import React from 'react'
-import {View, TextInput, TextInputProps, TextStyle} from 'react-native'
+import {
+ View,
+ TextInput,
+ TextInputProps,
+ TextStyle,
+ LayoutChangeEvent,
+} from 'react-native'
import {useTheme, atoms, web, tokens} from '#/alf'
import {Text} from '#/view/com/Typography'
@@ -9,15 +15,23 @@ type Props = Omit & {
placeholder: string
hasError?: boolean
icon?: React.FunctionComponent
+ suffix?: React.FunctionComponent
}
-export function InputText({label, hasError, icon: Icon, ...props}: Props) {
+export function InputText({
+ label,
+ hasError,
+ icon: Icon,
+ suffix: Suffix,
+ ...props
+}: Props) {
const labelId = React.useId()
const t = useTheme()
const [state, setState] = React.useState({
hovered: false,
focused: false,
})
+ const [suffixPadding, setSuffixPadding] = React.useState(0)
const onHoverIn = React.useCallback(() => {
setState(s => ({
@@ -43,6 +57,12 @@ export function InputText({label, hasError, icon: Icon, ...props}: Props) {
focused: false,
}))
}, [setState])
+ const handleSuffixLayout = React.useCallback(
+ (e: LayoutChangeEvent) => {
+ setSuffixPadding(e.nativeEvent.layout.width + 16)
+ },
+ [setSuffixPadding],
+ )
const {inputStyles, iconStyles} = React.useMemo(() => {
const input: TextStyle[] = []
@@ -113,31 +133,52 @@ export function InputText({label, hasError, icon: Icon, ...props}: Props) {
atoms.text_md,
t.atoms.border,
t.atoms.text,
- {borderWidth: 2},
web({
paddingTop: atoms.pt_md.paddingTop - 1,
}),
+ {paddingRight: suffixPadding},
+ {borderWidth: 2, lineHeight: atoms.text_md.lineHeight * 1.1875},
...inputStyles,
...(Array.isArray(props.style) ? props.style : [props.style]),
]}
/>
{Icon && (
-
+
+
+ )}
+
+ {Suffix && (
+
+ atoms.align_center,
+ atoms.justify_center,
+ atoms.pr_lg,
+ {left: 'auto'},
+ ]}>
+
+
)}
)
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index 369b57caff..d52e7a6e00 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -220,6 +220,12 @@ function Forms() {
+
+ .bksy.social}
+ />
)
}
From 1ea20b3a053a22ea007d7305888e3ffd3168aec0 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 14:33:11 -0600
Subject: [PATCH 08/68] Date inputs
---
bskyweb/templates/base.html | 4 +
.../com/forms/InputDate/index.android.tsx | 162 ++++++++++++++++++
src/view/com/forms/InputDate/index.tsx | 77 +++++++++
src/view/com/forms/InputDate/index.web.tsx | 154 +++++++++++++++++
src/view/com/forms/InputDate/utils.ts | 16 ++
src/view/com/forms/types.ts | 22 +++
.../com/util/hooks/useInteractionState.ts | 21 +++
src/view/screens/DebugNew.tsx | 16 ++
web/index.html | 4 +
9 files changed, 476 insertions(+)
create mode 100644 src/view/com/forms/InputDate/index.android.tsx
create mode 100644 src/view/com/forms/InputDate/index.tsx
create mode 100644 src/view/com/forms/InputDate/index.web.tsx
create mode 100644 src/view/com/forms/InputDate/utils.ts
create mode 100644 src/view/com/forms/types.ts
create mode 100644 src/view/com/util/hooks/useInteractionState.ts
diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html
index 55c7c9fd86..ffc2adf0f8 100644
--- a/bskyweb/templates/base.html
+++ b/bskyweb/templates/base.html
@@ -39,6 +39,10 @@
height: calc(100% + env(safe-area-inset-top));
}
+ input::-webkit-date-and-time-value {
+ text-align: left;
+ }
+
/* Color theming */
:root {
--text: black;
diff --git a/src/view/com/forms/InputDate/index.android.tsx b/src/view/com/forms/InputDate/index.android.tsx
new file mode 100644
index 0000000000..76ffa2e56e
--- /dev/null
+++ b/src/view/com/forms/InputDate/index.android.tsx
@@ -0,0 +1,162 @@
+import React from 'react'
+import {View, TextInputProps, TextStyle, Pressable} from 'react-native'
+import DateTimePicker, {
+ BaseProps as DateTimePickerProps,
+} from '@react-native-community/datetimepicker'
+
+import {Logo} from '#/view/icons/Logo'
+import {useTheme, atoms, tokens} from '#/alf'
+import {Text} from '#/view/com/Typography'
+import {useInteractionState} from '#/view/com/util/hooks/useInteractionState'
+import {BaseProps} from '#/view/com/forms/types'
+import {
+ localizeDate,
+ toSimpleDateString,
+} from '#/view/com/forms/InputDate/utils'
+
+type Props = Omit & BaseProps
+
+export * as utils from '#/view/com/forms/InputDate/utils'
+
+export function InputDate({
+ value: initialValue,
+ onChange,
+ testID,
+ label,
+ hasError,
+ accessibilityLabel,
+ accessibilityHint,
+ ...props
+}: Props) {
+ const labelId = React.useId()
+ const t = useTheme()
+ const [open, setOpen] = React.useState(false)
+ const [value, setValue] = React.useState(initialValue)
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+
+ const {inputStyles, iconStyles} = React.useMemo(() => {
+ const input: TextStyle[] = [
+ {
+ paddingLeft: 40,
+ },
+ ]
+ const icon: TextStyle[] = []
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_200,
+ })
+ icon.push({
+ color: tokens.color.red_400,
+ })
+ }
+
+ if (focused) {
+ input.push({
+ borderColor: t.atoms.border_contrast_500.borderColor,
+ })
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_500,
+ })
+ }
+ }
+
+ return {inputStyles: input, iconStyles: icon}
+ }, [t, focused, hasError])
+
+ const onChangeInternal = React.useCallback<
+ Required['onChange']
+ >(
+ (_event, date) => {
+ setOpen(false)
+
+ if (date) {
+ const formatted = toSimpleDateString(date)
+ onChange(formatted)
+ setValue(formatted)
+ }
+ },
+ [onChange, setOpen, setValue],
+ )
+
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ setOpen(true)}
+ onFocus={onFocus}
+ onBlur={onBlur}
+ style={[
+ {
+ paddingTop: atoms.pt_md.paddingTop + 2,
+ },
+ atoms.w_full,
+ atoms.px_lg,
+ atoms.pb_md,
+ atoms.rounded_sm,
+ t.atoms.bg_contrast_100,
+ ...inputStyles,
+ ]}>
+ {localizeDate(value)}
+
+
+
+
+
+
+ {open && (
+
+ )}
+
+ )
+}
diff --git a/src/view/com/forms/InputDate/index.tsx b/src/view/com/forms/InputDate/index.tsx
new file mode 100644
index 0000000000..6b3c8b40b9
--- /dev/null
+++ b/src/view/com/forms/InputDate/index.tsx
@@ -0,0 +1,77 @@
+import React from 'react'
+import {View, TextInputProps} from 'react-native'
+import DateTimePicker, {
+ DateTimePickerEvent,
+} from '@react-native-community/datetimepicker'
+
+import {useTheme, atoms} from '#/alf'
+import {Text} from '#/view/com/Typography'
+import {BaseProps} from '#/view/com/forms/types'
+import {toSimpleDateString} from '#/view/com/forms/InputDate/utils'
+
+type Props = Omit & BaseProps
+
+export * as utils from '#/view/com/forms/InputDate/utils'
+
+/**
+ * Date-only input. Accepts a date in the format YYYY-MM-DD, and reports date
+ * changes in the same format.
+ *
+ * For dates of unknown format, convert with the
+ * `utils.toSimpleDateString(Date)` export of this file.
+ */
+export function InputDate({
+ value: initialValue,
+ onChange,
+ testID,
+ label,
+ accessibilityLabel,
+ accessibilityHint,
+}: Props) {
+ const labelId = React.useId()
+ const t = useTheme()
+ const [value, setValue] = React.useState(initialValue)
+
+ const onChangeInternal = React.useCallback(
+ (event: DateTimePickerEvent, date: Date | undefined) => {
+ if (date) {
+ const formatted = toSimpleDateString(date)
+ onChange(formatted)
+ setValue(formatted)
+ }
+ },
+ [onChange],
+ )
+
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+
+
+ )
+}
diff --git a/src/view/com/forms/InputDate/index.web.tsx b/src/view/com/forms/InputDate/index.web.tsx
new file mode 100644
index 0000000000..994a7f509f
--- /dev/null
+++ b/src/view/com/forms/InputDate/index.web.tsx
@@ -0,0 +1,154 @@
+import React from 'react'
+import {View, TextStyle} from 'react-native'
+// @ts-ignore
+import {unstable_createElement} from 'react-native-web'
+
+import {Logo} from '#/view/icons/Logo'
+import {useTheme, atoms, tokens} from '#/alf'
+import {Text} from '#/view/com/Typography'
+import {useInteractionState} from '#/view/com/util/hooks/useInteractionState'
+
+import {BaseProps} from '#/view/com/forms/types'
+import {toSimpleDateString} from '#/view/com/forms/InputDate/utils'
+
+type Props = BaseProps
+
+export * as utils from '#/view/com/forms/InputDate/utils'
+
+export function InputDate({
+ label,
+ hasError,
+ testID,
+ value: initialValue,
+ onChange,
+ accessibilityLabel,
+ accessibilityHint,
+ ...props
+}: Props) {
+ const labelId = React.useId()
+ const t = useTheme()
+ const [value, setValue] = React.useState(initialValue)
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+
+ const {inputStyles, iconStyles} = React.useMemo(() => {
+ const input: TextStyle[] = [
+ {
+ paddingLeft: 40,
+ },
+ ]
+ const icon: TextStyle[] = []
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_200,
+ })
+ icon.push({
+ color: tokens.color.red_400,
+ })
+ }
+
+ if (hovered || focused) {
+ input.push({
+ borderColor: t.atoms.border_contrast_500.borderColor,
+ })
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_500,
+ })
+ }
+ }
+
+ return {inputStyles: input, iconStyles: icon}
+ }, [t, hovered, focused, hasError])
+
+ const handleOnChange = React.useCallback(
+ (e: any) => {
+ const date = e.currentTarget.valueAsDate
+
+ if (date) {
+ const formatted = toSimpleDateString(date)
+ onChange(formatted)
+ setValue(formatted)
+ }
+ },
+ [onChange, setValue],
+ )
+
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ {unstable_createElement('input', {
+ ...props,
+ testID: `${testID}-datepicker`,
+ 'aria-labelledby': labelId,
+ 'aria-label': label,
+ accessibilityLabel: accessibilityLabel,
+ accessibilityHint: accessibilityHint,
+ type: 'date',
+ value: value,
+ onFocus: onFocus,
+ onBlur: onBlur,
+ onChange: handleOnChange,
+ onMouseEnter: onHoverIn,
+ onMouseLeave: onHoverOut,
+ style: [
+ {
+ outline: 0,
+ border: 0,
+ appearance: 'none',
+ boxSizing: 'border-box',
+ lineHeight: atoms.text_md.lineHeight * 1.1875,
+ paddingTop: atoms.pt_md.paddingTop - 1,
+ },
+ atoms.w_full,
+ atoms.px_lg,
+ atoms.pb_md,
+ atoms.rounded_sm,
+ atoms.text_md,
+ t.atoms.bg_contrast_100,
+ t.atoms.text,
+ ...inputStyles,
+ ],
+ })}
+
+
+
+
+
+ )
+}
diff --git a/src/view/com/forms/InputDate/utils.ts b/src/view/com/forms/InputDate/utils.ts
new file mode 100644
index 0000000000..c787272fe8
--- /dev/null
+++ b/src/view/com/forms/InputDate/utils.ts
@@ -0,0 +1,16 @@
+import {getLocales} from 'expo-localization'
+
+const LOCALE = getLocales()[0]
+
+// we need the date in the form yyyy-MM-dd to pass to the input
+export function toSimpleDateString(date: Date | string): string {
+ const _date = typeof date === 'string' ? new Date(date) : date
+ return _date.toISOString().split('T')[0]
+}
+
+export function localizeDate(date: Date | string): string {
+ const _date = typeof date === 'string' ? new Date(date) : date
+ return new Intl.DateTimeFormat(LOCALE.languageTag, {
+ timeZone: 'UTC',
+ }).format(_date)
+}
diff --git a/src/view/com/forms/types.ts b/src/view/com/forms/types.ts
new file mode 100644
index 0000000000..9a4217579b
--- /dev/null
+++ b/src/view/com/forms/types.ts
@@ -0,0 +1,22 @@
+import {AccessibilityProps, TextInputProps} from 'react-native'
+
+export type RequiredAccessibilityProps = Required
+
+export type BaseProps = Omit<
+ AccessibilityProps,
+ 'accessibilityLabel' | 'accessibilityHint'
+> &
+ Pick<
+ RequiredAccessibilityProps,
+ 'accessibilityLabel' | 'accessibilityHint'
+ > & {
+ value: T
+ onChange: (value: T) => void
+ testID: string
+ label?: string
+ hasError?: boolean
+ /**
+ * **NOTE:** Available only on web
+ */
+ autoFocus?: TextInputProps['autoFocus']
+ }
diff --git a/src/view/com/util/hooks/useInteractionState.ts b/src/view/com/util/hooks/useInteractionState.ts
new file mode 100644
index 0000000000..653b1c10e6
--- /dev/null
+++ b/src/view/com/util/hooks/useInteractionState.ts
@@ -0,0 +1,21 @@
+import React from 'react'
+
+export function useInteractionState() {
+ const [state, setState] = React.useState(false)
+
+ const onIn = React.useCallback(() => {
+ setState(true)
+ }, [setState])
+ const onOut = React.useCallback(() => {
+ setState(false)
+ }, [setState])
+
+ return React.useMemo(
+ () => ({
+ state,
+ onIn,
+ onOut,
+ }),
+ [state, onIn, onOut],
+ )
+}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index d52e7a6e00..35f0e415d2 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -10,6 +10,7 @@ import {Button, ButtonText} from '#/view/com/Button'
import {Link} from '#/view/com/Link'
import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
import {InputText} from '#/view/com/forms/InputText'
+import {InputDate, utils} from '#/view/com/forms/InputDate'
import {Logo} from '#/view/icons/Logo'
function ThemeSelector() {
@@ -226,6 +227,21 @@ function Forms() {
icon={Logo}
suffix={() => .bksy.social}
/>
+
+ console.log(date)}
+ accessibilityLabel="Date"
+ accessibilityHint="Enter a date"
+ />
+ console.log(date)}
+ accessibilityLabel="Date"
+ accessibilityHint="Enter a date"
+ />
)
}
diff --git a/web/index.html b/web/index.html
index 18b985bff5..70072f2ae2 100644
--- a/web/index.html
+++ b/web/index.html
@@ -43,6 +43,10 @@
height: calc(100% + env(safe-area-inset-top));
}
+ input::-webkit-date-and-time-value {
+ text-align: left;
+ }
+
/* Color theming */
:root {
--text: black;
From 0c8c37b2212f4694b85a7162fdb5ff91ece1d9c6 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 14:36:47 -0600
Subject: [PATCH 09/68] Autofill styles
---
bskyweb/templates/base.html | 15 +++++++++++++++
web/index.html | 15 +++++++++++++++
2 files changed, 30 insertions(+)
diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html
index ffc2adf0f8..089bdcfa0f 100644
--- a/bskyweb/templates/base.html
+++ b/bskyweb/templates/base.html
@@ -39,6 +39,21 @@
height: calc(100% + env(safe-area-inset-top));
}
+ /* Remove autofill styles on Webkit */
+ input:-webkit-autofill,
+ input:-webkit-autofill:hover,
+ input:-webkit-autofill:focus,
+ textarea:-webkit-autofill,
+ textarea:-webkit-autofill:hover,
+ textarea:-webkit-autofill:focus,
+ select:-webkit-autofill,
+ select:-webkit-autofill:hover,
+ select:-webkit-autofill:focus {
+ border: 0;
+ -webkit-text-fill-color: transparent;
+ -webkit-box-shadow: none;
+ }
+ /* Force left-align date/time inputs on iOS mobile */
input::-webkit-date-and-time-value {
text-align: left;
}
diff --git a/web/index.html b/web/index.html
index 70072f2ae2..585d573536 100644
--- a/web/index.html
+++ b/web/index.html
@@ -43,6 +43,21 @@
height: calc(100% + env(safe-area-inset-top));
}
+ /* Remove autofill styles on Webkit */
+ input:-webkit-autofill,
+ input:-webkit-autofill:hover,
+ input:-webkit-autofill:focus,
+ textarea:-webkit-autofill,
+ textarea:-webkit-autofill:hover,
+ textarea:-webkit-autofill:focus,
+ select:-webkit-autofill,
+ select:-webkit-autofill:hover,
+ select:-webkit-autofill:focus {
+ border: 0;
+ -webkit-text-fill-color: transparent;
+ -webkit-box-shadow: none;
+ }
+ /* Force left-align date/time inputs on iOS mobile */
input::-webkit-date-and-time-value {
text-align: left;
}
From 24074dea50fad2dd0e0aa7c1cd09b1b008919381 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 14:44:14 -0600
Subject: [PATCH 10/68] Clean up InputDate types
---
src/view/com/forms/InputDate/index.android.tsx | 9 ++++-----
src/view/com/forms/InputDate/index.tsx | 8 +++-----
src/view/com/forms/InputDate/index.web.tsx | 6 ++----
src/view/com/forms/types.ts | 6 +-----
4 files changed, 10 insertions(+), 19 deletions(-)
diff --git a/src/view/com/forms/InputDate/index.android.tsx b/src/view/com/forms/InputDate/index.android.tsx
index 76ffa2e56e..a492ad7da9 100644
--- a/src/view/com/forms/InputDate/index.android.tsx
+++ b/src/view/com/forms/InputDate/index.android.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {View, TextInputProps, TextStyle, Pressable} from 'react-native'
+import {View, TextStyle, Pressable} from 'react-native'
import DateTimePicker, {
BaseProps as DateTimePickerProps,
} from '@react-native-community/datetimepicker'
@@ -8,14 +8,13 @@ import {Logo} from '#/view/icons/Logo'
import {useTheme, atoms, tokens} from '#/alf'
import {Text} from '#/view/com/Typography'
import {useInteractionState} from '#/view/com/util/hooks/useInteractionState'
-import {BaseProps} from '#/view/com/forms/types'
+
+import {InputDateProps} from '#/view/com/forms/InputDate/types'
import {
localizeDate,
toSimpleDateString,
} from '#/view/com/forms/InputDate/utils'
-type Props = Omit & BaseProps
-
export * as utils from '#/view/com/forms/InputDate/utils'
export function InputDate({
@@ -27,7 +26,7 @@ export function InputDate({
accessibilityLabel,
accessibilityHint,
...props
-}: Props) {
+}: InputDateProps) {
const labelId = React.useId()
const t = useTheme()
const [open, setOpen] = React.useState(false)
diff --git a/src/view/com/forms/InputDate/index.tsx b/src/view/com/forms/InputDate/index.tsx
index 6b3c8b40b9..eb642c3a08 100644
--- a/src/view/com/forms/InputDate/index.tsx
+++ b/src/view/com/forms/InputDate/index.tsx
@@ -1,16 +1,14 @@
import React from 'react'
-import {View, TextInputProps} from 'react-native'
+import {View} from 'react-native'
import DateTimePicker, {
DateTimePickerEvent,
} from '@react-native-community/datetimepicker'
import {useTheme, atoms} from '#/alf'
import {Text} from '#/view/com/Typography'
-import {BaseProps} from '#/view/com/forms/types'
import {toSimpleDateString} from '#/view/com/forms/InputDate/utils'
-type Props = Omit & BaseProps
-
+import {InputDateProps} from '#/view/com/forms/InputDate/types'
export * as utils from '#/view/com/forms/InputDate/utils'
/**
@@ -27,7 +25,7 @@ export function InputDate({
label,
accessibilityLabel,
accessibilityHint,
-}: Props) {
+}: InputDateProps) {
const labelId = React.useId()
const t = useTheme()
const [value, setValue] = React.useState(initialValue)
diff --git a/src/view/com/forms/InputDate/index.web.tsx b/src/view/com/forms/InputDate/index.web.tsx
index 994a7f509f..33a7473850 100644
--- a/src/view/com/forms/InputDate/index.web.tsx
+++ b/src/view/com/forms/InputDate/index.web.tsx
@@ -8,11 +8,9 @@ import {useTheme, atoms, tokens} from '#/alf'
import {Text} from '#/view/com/Typography'
import {useInteractionState} from '#/view/com/util/hooks/useInteractionState'
-import {BaseProps} from '#/view/com/forms/types'
+import {InputDateProps} from '#/view/com/forms/InputDate/types'
import {toSimpleDateString} from '#/view/com/forms/InputDate/utils'
-type Props = BaseProps
-
export * as utils from '#/view/com/forms/InputDate/utils'
export function InputDate({
@@ -24,7 +22,7 @@ export function InputDate({
accessibilityLabel,
accessibilityHint,
...props
-}: Props) {
+}: InputDateProps) {
const labelId = React.useId()
const t = useTheme()
const [value, setValue] = React.useState(initialValue)
diff --git a/src/view/com/forms/types.ts b/src/view/com/forms/types.ts
index 9a4217579b..c43b760b0b 100644
--- a/src/view/com/forms/types.ts
+++ b/src/view/com/forms/types.ts
@@ -1,4 +1,4 @@
-import {AccessibilityProps, TextInputProps} from 'react-native'
+import {AccessibilityProps} from 'react-native'
export type RequiredAccessibilityProps = Required
@@ -15,8 +15,4 @@ export type BaseProps = Omit<
testID: string
label?: string
hasError?: boolean
- /**
- * **NOTE:** Available only on web
- */
- autoFocus?: TextInputProps['autoFocus']
}
From 9bd19a676274c8bcd3ff1f7330b168fc27dbb6ac Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 14:54:02 -0600
Subject: [PATCH 11/68] Improve types for InputText, value handling
---
src/view/com/forms/InputDate/types.ts | 10 ++++
src/view/com/forms/InputText.tsx | 76 +++++++++++++--------------
src/view/screens/DebugNew.tsx | 35 ++++++++++--
3 files changed, 79 insertions(+), 42 deletions(-)
create mode 100644 src/view/com/forms/InputDate/types.ts
diff --git a/src/view/com/forms/InputDate/types.ts b/src/view/com/forms/InputDate/types.ts
new file mode 100644
index 0000000000..8a8d8cd1db
--- /dev/null
+++ b/src/view/com/forms/InputDate/types.ts
@@ -0,0 +1,10 @@
+import {TextInputProps} from 'react-native'
+
+import {BaseProps} from '#/view/com/forms/types'
+
+export type InputDateProps = BaseProps & {
+ /**
+ * **NOTE:** Available only on web
+ */
+ autoFocus?: TextInputProps['autoFocus']
+}
diff --git a/src/view/com/forms/InputText.tsx b/src/view/com/forms/InputText.tsx
index eb77c6242f..c573a3c650 100644
--- a/src/view/com/forms/InputText.tsx
+++ b/src/view/com/forms/InputText.tsx
@@ -9,16 +9,23 @@ import {
import {useTheme, atoms, web, tokens} from '#/alf'
import {Text} from '#/view/com/Typography'
+import {useInteractionState} from '#/view/com/util/hooks/useInteractionState'
-type Props = Omit & {
- label?: string
- placeholder: string
- hasError?: boolean
- icon?: React.FunctionComponent
- suffix?: React.FunctionComponent
-}
+import {BaseProps} from '#/view/com/forms/types'
+
+type Props = BaseProps &
+ Omit & {
+ placeholder: Required['placeholder']
+ icon?: React.FunctionComponent
+ suffix?: React.FunctionComponent
+ }
export function InputText({
+ value: initialValue,
+ onChange,
+ testID,
+ accessibilityLabel,
+ accessibilityHint,
label,
hasError,
icon: Icon,
@@ -27,36 +34,15 @@ export function InputText({
}: Props) {
const labelId = React.useId()
const t = useTheme()
- const [state, setState] = React.useState({
- hovered: false,
- focused: false,
- })
+ const [value, setValue] = React.useState(initialValue)
const [suffixPadding, setSuffixPadding] = React.useState(0)
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
- const onHoverIn = React.useCallback(() => {
- setState(s => ({
- ...s,
- hovered: true,
- }))
- }, [setState])
- const onHoverOut = React.useCallback(() => {
- setState(s => ({
- ...s,
- hovered: false,
- }))
- }, [setState])
- const onFocus = React.useCallback(() => {
- setState(s => ({
- ...s,
- focused: true,
- }))
- }, [setState])
- const onBlur = React.useCallback(() => {
- setState(s => ({
- ...s,
- focused: false,
- }))
- }, [setState])
const handleSuffixLayout = React.useCallback(
(e: LayoutChangeEvent) => {
setSuffixPadding(e.nativeEvent.layout.width + 16)
@@ -83,7 +69,7 @@ export function InputText({
})
}
- if (state.hovered || state.focused) {
+ if (hovered || focused) {
input.push({
borderColor: t.atoms.border_contrast_500.borderColor,
})
@@ -96,7 +82,16 @@ export function InputText({
}
return {inputStyles: input, iconStyles: icon}
- }, [t, state, hasError, Icon])
+ }, [t, hovered, focused, hasError, Icon])
+
+ const handleOnChange = React.useCallback(
+ (e: any) => {
+ const value = e.currentTarget.value
+ onChange(value)
+ setValue(value)
+ },
+ [onChange, setValue],
+ )
return (
@@ -114,12 +109,17 @@ export function InputText({
)}
-
-
-
-
console.log(text)}
+ />
+ console.log(text)}
+ />
+ console.log(text)}
+ icon={Logo}
+ />
+ console.log(text)}
icon={Logo}
suffix={() => .bksy.social}
/>
From 22f0c16cf1fe956ffbde331dad37eaa5270161de Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 15:02:07 -0600
Subject: [PATCH 12/68] Enforce a11y props on buttons
---
src/view/com/Button.tsx | 15 +++++++--
src/view/com/Link.tsx | 6 ++--
src/view/screens/DebugNew.tsx | 61 +++++++++++++++++++++++++++++------
3 files changed, 67 insertions(+), 15 deletions(-)
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
index 1d858a8eea..7b3beb7dca 100644
--- a/src/view/com/Button.tsx
+++ b/src/view/com/Button.tsx
@@ -5,6 +5,7 @@ import {
PressableProps,
TextProps,
ViewStyle,
+ AccessibilityProps,
} from 'react-native'
import {useTheme, atoms, tokens, web, native} from '#/alf'
@@ -22,7 +23,10 @@ export type VariantProps = {
size?: ButtonSize
}
-export type ButtonProps = Omit &
+export type ButtonProps = Omit<
+ PressableProps,
+ 'children' | 'style' | 'accessibilityLabel' | 'accessibilityHint'
+> &
VariantProps & {
children:
| ((props: {
@@ -37,14 +41,17 @@ export type ButtonProps = Omit &
}) => React.ReactNode)
| React.ReactNode
| string
+ accessibilityLabel: Required['accessibilityLabel']
+ accessibilityHint: Required['accessibilityHint']
}
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
export function Button({
children,
- style,
type,
size,
+ accessibilityLabel,
+ accessibilityHint,
disabled = false,
...rest
}: ButtonProps) {
@@ -179,6 +186,9 @@ export function Button({
()
- const {href, accessibilityRole} = useLinkProps({
+ const {href} = useLinkProps({
to:
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
})
@@ -139,9 +139,9 @@ export function Link({
return (
From 2bac965da4b7d6f03e025b3175e6acdec823423a Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 16:13:02 -0600
Subject: [PATCH 13/68] Add Dialog, Portal
---
package.json | 1 +
src/App.native.tsx | 6 +-
src/App.web.tsx | 6 +-
src/view/com/Dialog/index.tsx | 7 +++
src/view/com/Dialog/index.web.tsx | 96 +++++++++++++++++++++++++++++++
src/view/com/Dialog/types.ts | 4 ++
src/view/com/Portal.tsx | 58 +++++++++++++++++++
src/view/screens/DebugNew.tsx | 25 ++++++++
yarn.lock | 25 ++++++++
9 files changed, 226 insertions(+), 2 deletions(-)
create mode 100644 src/view/com/Dialog/index.tsx
create mode 100644 src/view/com/Dialog/index.web.tsx
create mode 100644 src/view/com/Dialog/types.ts
create mode 100644 src/view/com/Portal.tsx
diff --git a/package.json b/package.json
index 7e63ad9a62..01d90da90f 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"@segment/analytics-react-native": "^2.10.1",
"@segment/sovran-react-native": "^0.4.5",
"@sentry/react-native": "5.5.0",
+ "@tamagui/focus-scope": "^1.84.1",
"@tanstack/react-query": "^5.8.1",
"@tiptap/core": "^2.0.0-beta.220",
"@tiptap/extension-document": "^2.0.0-beta.220",
diff --git a/src/App.native.tsx b/src/App.native.tsx
index a9dfafe36f..39485ee24f 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -41,6 +41,7 @@ import {
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
import {Splash} from '#/Splash'
+import {Provider as PortalProvider, Outlet as PortalOutlet} from '#/view/com/Portal'
SplashScreen.preventAutoHideAsync()
@@ -76,6 +77,7 @@ function InnerApp() {
+
@@ -113,7 +115,9 @@ function App() {
-
+
+
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 9ee9a4fd71..5760b0d4a2 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -30,6 +30,7 @@ import {
} from 'state/session'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
+import {Provider as PortalProvider, Outlet as PortalOutlet} from '#/view/com/Portal'
function InnerApp() {
const {isInitialLoad, currentAccount} = useSession()
@@ -58,6 +59,7 @@ function InnerApp() {
+
@@ -94,7 +96,9 @@ function App() {
-
+
+
+
diff --git a/src/view/com/Dialog/index.tsx b/src/view/com/Dialog/index.tsx
new file mode 100644
index 0000000000..fc3f784704
--- /dev/null
+++ b/src/view/com/Dialog/index.tsx
@@ -0,0 +1,7 @@
+import React from 'react'
+
+import {DialogProps} from '#/view/com/Dialog/types'
+
+export function Dialog(props: React.PropsWithChildren) {
+ return null
+}
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
new file mode 100644
index 0000000000..9c6f09f4f3
--- /dev/null
+++ b/src/view/com/Dialog/index.web.tsx
@@ -0,0 +1,96 @@
+import React from 'react'
+import {View, TouchableWithoutFeedback, DimensionValue} from 'react-native'
+import {FocusScope} from '@tamagui/focus-scope'
+import Animated, {FadeInDown, FadeIn, FadeOut} from 'react-native-reanimated'
+
+import {useTheme, atoms as a, useBreakpoints} from '#/alf'
+import {Portal} from '#/view/com/Portal'
+import {DialogProps} from '#/view/com/Dialog/types'
+
+const Context = React.createContext<{
+ dismiss: () => void
+}>({
+ dismiss: () => {},
+})
+
+export function useDialog() {
+ return React.useContext(Context)
+}
+
+export function Dialog({
+ isOpen,
+ onDismiss,
+ children,
+}: React.PropsWithChildren) {
+ const t = useTheme()
+ const {gtMobile} = useBreakpoints()
+
+ const dismiss = React.useCallback(() => {
+ onDismiss()
+ }, [onDismiss])
+
+ React.useEffect(() => {
+ function handler(e: KeyboardEvent) {
+ if (e.key === 'Escape') dismiss()
+ }
+
+ document.addEventListener('keydown', handler)
+
+ return () => document.removeEventListener('keydown', handler)
+ }, [dismiss])
+
+ const context = React.useMemo(() => ({dismiss}), [dismiss])
+
+ return (
+ <>
+ {isOpen && (
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/view/com/Dialog/types.ts b/src/view/com/Dialog/types.ts
new file mode 100644
index 0000000000..eb9dc56865
--- /dev/null
+++ b/src/view/com/Dialog/types.ts
@@ -0,0 +1,4 @@
+export type DialogProps = {
+ isOpen: boolean
+ onDismiss: () => void
+}
diff --git a/src/view/com/Portal.tsx b/src/view/com/Portal.tsx
new file mode 100644
index 0000000000..47a216bd80
--- /dev/null
+++ b/src/view/com/Portal.tsx
@@ -0,0 +1,58 @@
+import React from 'react'
+
+type Component = React.ReactElement
+
+type ContextType = {
+ outlet: Component | null
+ append(id: string, component: Component): void
+ remove(id: string): void
+}
+
+type ComponentMap = {
+ [id: string]: Component
+}
+
+export const Context = React.createContext({
+ outlet: null,
+ append: () => {},
+ remove: () => {},
+})
+
+export function Provider(props: React.PropsWithChildren<{}>) {
+ const map = React.useRef({})
+ const [outlet, setOutlet] = React.useState(null)
+
+ const append = React.useCallback((id, component) => {
+ if (map.current[id]) return
+ map.current[id] = {component}
+ setOutlet(<>{Object.values(map.current)}>)
+ }, [])
+
+ const remove = React.useCallback(id => {
+ delete map.current[id]
+ setOutlet(<>{Object.values(map.current)}>)
+ }, [])
+
+ console.log(outlet)
+
+ return (
+
+ {props.children}
+
+ )
+}
+
+export function Outlet() {
+ const ctx = React.useContext(Context)
+ return ctx.outlet
+}
+
+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
+}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index 444fe4f29a..ce7f5c20a8 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -12,6 +12,7 @@ import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
import {InputText} from '#/view/com/forms/InputText'
import {InputDate, utils} from '#/view/com/forms/InputDate'
import {Logo} from '#/view/icons/Logo'
+import {Dialog} from '#/view/com/Dialog'
function ThemeSelector() {
const setColorMode = useSetColorMode()
@@ -316,6 +317,28 @@ function Forms() {
)
}
+function Dialogs() {
+ const [isOpen, setIsOpen] = React.useState(false)
+
+ console.log({isOpen})
+
+ return (
+ <>
+ setIsOpen(true)} accessibilityLabel='Open basic dialog' accessibilityHint='Open basic dialog'>
+ Open basic dialog
+
+
+ >
+ )
+}
+
export function DebugScreen() {
const t = useTheme()
@@ -325,6 +348,8 @@ export function DebugScreen() {
+
+
diff --git a/yarn.lock b/yarn.lock
index c1db264bc6..7d0ceecd38 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6921,6 +6921,31 @@
"@svgr/plugin-svgo" "^5.5.0"
loader-utils "^2.0.0"
+"@tamagui/compose-refs@1.84.1":
+ version "1.84.1"
+ resolved "https://registry.yarnpkg.com/@tamagui/compose-refs/-/compose-refs-1.84.1.tgz#244735edc3ac2e617389297f005d5bc25872465f"
+ integrity sha512-oZ0rUmQABlGm/QKQITxAW9WLV3qjyq1ehgoWcZVmtc1Kc/hkFQe2J+wRQV726CmTAnuUgUXi3eoNMwBVoZksfQ==
+
+"@tamagui/constants@1.84.1":
+ version "1.84.1"
+ resolved "https://registry.yarnpkg.com/@tamagui/constants/-/constants-1.84.1.tgz#62e41837dbe844d14e255f3eea9c2583044d2509"
+ integrity sha512-QmvyCqtEIugqXutQI35GJQ1hlpSapYCdOHx9QlgsOWjAY34pu55MaY/tDrQeQ0AUmI/qx30vy7TsCJxB4QFEoQ==
+
+"@tamagui/focus-scope@^1.84.1":
+ version "1.84.1"
+ resolved "https://registry.yarnpkg.com/@tamagui/focus-scope/-/focus-scope-1.84.1.tgz#e9f061184048c75f87da023f54b9c5abccdd460d"
+ integrity sha512-0E1Wc3jmKhafETfH1dUuJYmGK1bDNA/9TySbOeTjTToxUoL3V0G2W5JSwSMCDqR1Bl+xrGlGwzXTUhouw8qSog==
+ dependencies:
+ "@tamagui/compose-refs" "1.84.1"
+ "@tamagui/use-event" "1.84.1"
+
+"@tamagui/use-event@1.84.1":
+ version "1.84.1"
+ resolved "https://registry.yarnpkg.com/@tamagui/use-event/-/use-event-1.84.1.tgz#a095a1bde9c40c4a397226c57c3fa32f6018f504"
+ integrity sha512-U88WCxvMz7ZSfMFMJEFbG3tJjK/Lf+PHlmtYvlx1V+YiqRBoj5+milzoM8PclENn5vZMiJW0ozYRgzI/cdE7Eg==
+ dependencies:
+ "@tamagui/constants" "1.84.1"
+
"@tanstack/query-core@5.8.1":
version "5.8.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.8.1.tgz#5215a028370d9b2f32e83787a0ea119e2f977996"
From 78a0f652347d262cfd910ad5dcf063a8f88df290 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 16:45:42 -0600
Subject: [PATCH 14/68] Dialog contents
---
src/view/com/Dialog/index.tsx | 14 +++++-
src/view/com/Dialog/index.web.tsx | 71 +++++++++++++++++++++++++++++--
src/view/screens/DebugNew.tsx | 25 ++++++-----
3 files changed, 94 insertions(+), 16 deletions(-)
diff --git a/src/view/com/Dialog/index.tsx b/src/view/com/Dialog/index.tsx
index fc3f784704..2cf78022d2 100644
--- a/src/view/com/Dialog/index.tsx
+++ b/src/view/com/Dialog/index.tsx
@@ -2,6 +2,18 @@ import React from 'react'
import {DialogProps} from '#/view/com/Dialog/types'
-export function Dialog(props: React.PropsWithChildren) {
+export function Outer(props: React.PropsWithChildren) {
+ return null
+}
+
+export function Inner(props: React.PropsWithChildren<{}>) {
+ return null
+}
+
+export function Header(props: React.PropsWithChildren<{ title: string }>) {
+ return null
+}
+
+export function Close() {
return null
}
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
index 9c6f09f4f3..a972a8f0d7 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/view/com/Dialog/index.web.tsx
@@ -4,8 +4,11 @@ import {FocusScope} from '@tamagui/focus-scope'
import Animated, {FadeInDown, FadeIn, FadeOut} from 'react-native-reanimated'
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
+import {EventStopper} from '#/view/com/util/EventStopper'
+import {H3, Text} from '#/view/com/Typography'
import {Portal} from '#/view/com/Portal'
import {DialogProps} from '#/view/com/Dialog/types'
+import {Button} from '#/view/com/Button'
const Context = React.createContext<{
dismiss: () => void
@@ -17,7 +20,7 @@ export function useDialog() {
return React.useContext(Context)
}
-export function Dialog({
+export function Outer({
isOpen,
onDismiss,
children,
@@ -63,7 +66,7 @@ export function Dialog({
style={[
a.absolute,
a.inset_0,
- t.atoms.bg_contrast_100,
+ t.atoms.bg_contrast_200,
{opacity: 0.8},
]}
/>
@@ -81,8 +84,11 @@ export function Dialog({
- {children}
+ aria-role="dialog"
+ >
+
+ {children}
+
@@ -94,3 +100,60 @@ export function Dialog({
>
)
}
+
+export function Inner(props: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ return (
+
+ {props.children}
+
+ )
+}
+
+export function Header({ children, title }: React.PropsWithChildren<{ title: string }>) {
+ const t = useTheme()
+ return (
+
+ {title}
+ {children}
+
+ )
+}
+
+export function Close() {
+ const t = useTheme()
+ const {dismiss} = useDialog()
+ return (
+
+
+ {({ state}) => (
+
+ X
+
+ )}
+
+
+ )
+}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index ce7f5c20a8..b72407d0d9 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -12,7 +12,7 @@ import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
import {InputText} from '#/view/com/forms/InputText'
import {InputDate, utils} from '#/view/com/forms/InputDate'
import {Logo} from '#/view/icons/Logo'
-import {Dialog} from '#/view/com/Dialog'
+import * as Dialog from '#/view/com/Dialog'
function ThemeSelector() {
const setColorMode = useSetColorMode()
@@ -320,21 +320,24 @@ function Forms() {
function Dialogs() {
const [isOpen, setIsOpen] = React.useState(false)
- console.log({isOpen})
-
return (
<>
setIsOpen(true)} accessibilityLabel='Open basic dialog' accessibilityHint='Open basic dialog'>
Open basic dialog
-
+ setIsOpen(false)}>
+
+
+
+
+ setIsOpen(false)} accessibilityLabel='Open basic dialog' accessibilityHint='Open basic dialog'>
+ Close basic dialog
+
+ setIsOpen(false)} accessibilityLabel='Open basic dialog' accessibilityHint='Open basic dialog'>
+ Close basic dialog
+
+
+
>
)
}
From ad6d9c4d9fcf9aa6847cfc93a31f4fdc7a6b75b8 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 19:19:35 -0600
Subject: [PATCH 15/68] Native dialog
---
src/view/com/Dialog/index.tsx | 120 ++++++++++++++++++++++++++--
src/view/com/Dialog/index.web.tsx | 126 +++++++++++++++++-------------
src/view/com/Dialog/types.ts | 11 ++-
src/view/screens/DebugNew.tsx | 25 +++---
4 files changed, 209 insertions(+), 73 deletions(-)
diff --git a/src/view/com/Dialog/index.tsx b/src/view/com/Dialog/index.tsx
index 2cf78022d2..229cd7b867 100644
--- a/src/view/com/Dialog/index.tsx
+++ b/src/view/com/Dialog/index.tsx
@@ -1,17 +1,123 @@
-import React from 'react'
+import React, {useImperativeHandle} from 'react'
+import {View, Dimensions} from 'react-native'
+import BottomSheet, {BottomSheetBackdrop} from '@gorhom/bottom-sheet'
-import {DialogProps} from '#/view/com/Dialog/types'
+import {useTheme, atoms as a} from '#/alf'
+import {Portal} from '#/view/com/Portal'
-export function Outer(props: React.PropsWithChildren) {
- return null
+import {DialogProps, DialogControl} from '#/view/com/Dialog/types'
+
+const Context = React.createContext<{
+ close: () => void
+}>({
+ close: () => {},
+})
+
+export function useDialogControl() {
+ const control = React.useRef({
+ open: () => {},
+ close: () => {},
+ })
+
+ return control
+}
+
+export function useDialog() {
+ return React.useContext(Context)
+}
+
+export function Outer({
+ control,
+ onClose,
+ children,
+}: React.PropsWithChildren) {
+ const t = useTheme()
+ const sheet = React.useRef(null)
+
+ const open = React.useCallback((i = 0) => {
+ sheet.current?.snapToIndex(i)
+ }, [])
+
+ const close = React.useCallback(() => {
+ sheet.current?.close()
+ onClose?.()
+ }, [onClose])
+
+ useImperativeHandle(
+ control,
+ () => ({
+ open,
+ close,
+ }),
+ [open, close],
+ )
+
+ return (
+
+ (
+
+ )}
+ handleIndicatorStyle={{backgroundColor: t.palette.primary}}
+ handleStyle={{display: 'none'}}
+ onChange={() => {}}
+ onClose={onClose}>
+ {children}
+
+
+ )
}
export function Inner(props: React.PropsWithChildren<{}>) {
- return null
+ const t = useTheme()
+ return (
+
+ {props.children}
+
+ )
}
-export function Header(props: React.PropsWithChildren<{ title: string }>) {
- return null
+export function Handle() {
+ const t = useTheme()
+ return (
+
+ )
}
export function Close() {
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
index a972a8f0d7..b27c757029 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/view/com/Dialog/index.web.tsx
@@ -1,48 +1,73 @@
-import React from 'react'
+import React, {useImperativeHandle} from 'react'
import {View, TouchableWithoutFeedback, DimensionValue} from 'react-native'
import {FocusScope} from '@tamagui/focus-scope'
import Animated, {FadeInDown, FadeIn, FadeOut} from 'react-native-reanimated'
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
import {EventStopper} from '#/view/com/util/EventStopper'
-import {H3, Text} from '#/view/com/Typography'
+import {Text} from '#/view/com/Typography'
import {Portal} from '#/view/com/Portal'
-import {DialogProps} from '#/view/com/Dialog/types'
import {Button} from '#/view/com/Button'
+import {DialogProps, DialogControl} from '#/view/com/Dialog/types'
+
const Context = React.createContext<{
- dismiss: () => void
+ close: () => void
}>({
- dismiss: () => {},
+ close: () => {},
})
+export function useDialogControl() {
+ const control = React.useRef({
+ open: () => {},
+ close: () => {},
+ })
+
+ return control
+}
+
export function useDialog() {
return React.useContext(Context)
}
export function Outer({
- isOpen,
- onDismiss,
+ control,
+ onClose,
children,
}: React.PropsWithChildren) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
+ const [isOpen, setIsOpen] = React.useState(false)
+
+ const open = React.useCallback(() => {
+ setIsOpen(true)
+ }, [setIsOpen])
- const dismiss = React.useCallback(() => {
- onDismiss()
- }, [onDismiss])
+ const close = React.useCallback(() => {
+ setIsOpen(false)
+ onClose?.()
+ }, [onClose, setIsOpen])
+
+ useImperativeHandle(
+ control,
+ () => ({
+ open,
+ close,
+ }),
+ [open, close],
+ )
React.useEffect(() => {
function handler(e: KeyboardEvent) {
- if (e.key === 'Escape') dismiss()
+ if (e.key === 'Escape') close()
}
document.addEventListener('keydown', handler)
return () => document.removeEventListener('keydown', handler)
- }, [dismiss])
+ }, [close])
- const context = React.useMemo(() => ({dismiss}), [dismiss])
+ const context = React.useMemo(() => ({close}), [close])
return (
<>
@@ -51,7 +76,7 @@ export function Outer({
+ onPress={close}>
-
- {children}
-
+ aria-role="dialog">
+ {children}
@@ -104,52 +126,46 @@ export function Outer({
export function Inner(props: React.PropsWithChildren<{}>) {
const t = useTheme()
return (
-
+
{props.children}
)
}
-export function Header({ children, title }: React.PropsWithChildren<{ title: string }>) {
- const t = useTheme()
- return (
-
- {title}
- {children}
-
- )
+export function Handle() {
+ return null
}
export function Close() {
const t = useTheme()
- const {dismiss} = useDialog()
+ const {close} = useDialog()
return (
-
-
- {({ state}) => (
-
+
+
+ {() => (
+
X
)}
diff --git a/src/view/com/Dialog/types.ts b/src/view/com/Dialog/types.ts
index eb9dc56865..ba7c55ca94 100644
--- a/src/view/com/Dialog/types.ts
+++ b/src/view/com/Dialog/types.ts
@@ -1,4 +1,11 @@
+import React from 'react'
+
+export type DialogControl = {
+ open: (index?: number) => void
+ close: () => void
+}
+
export type DialogProps = {
- isOpen: boolean
- onDismiss: () => void
+ control: React.RefObject
+ onClose?: () => void
}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index b72407d0d9..b6598cf669 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -318,22 +318,29 @@ function Forms() {
}
function Dialogs() {
- const [isOpen, setIsOpen] = React.useState(false)
+ const control = Dialog.useDialogControl()
return (
<>
- setIsOpen(true)} accessibilityLabel='Open basic dialog' accessibilityHint='Open basic dialog'>
+ control.current.open()}
+ accessibilityLabel="Open basic dialog"
+ accessibilityHint="Open basic dialog">
Open basic dialog
- setIsOpen(false)}>
+
+
+
-
-
- setIsOpen(false)} accessibilityLabel='Open basic dialog' accessibilityHint='Open basic dialog'>
- Close basic dialog
-
- setIsOpen(false)} accessibilityLabel='Open basic dialog' accessibilityHint='Open basic dialog'>
+ control.current.close()}
+ accessibilityLabel="Open basic dialog"
+ accessibilityHint="Open basic dialog">
Close basic dialog
From 1da54e53641d4a14a7ac29654ed2be4c55c65579 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 10 Jan 2024 19:33:27 -0600
Subject: [PATCH 16/68] Clean up
---
src/view/com/Dialog/index.web.tsx | 29 +++++++++++++++++++----------
src/view/com/Portal.tsx | 2 --
src/view/screens/DebugNew.tsx | 27 ++++++++++++++++++---------
3 files changed, 37 insertions(+), 21 deletions(-)
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
index b27c757029..f1576d6f33 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/view/com/Dialog/index.web.tsx
@@ -4,13 +4,14 @@ import {FocusScope} from '@tamagui/focus-scope'
import Animated, {FadeInDown, FadeIn, FadeOut} from 'react-native-reanimated'
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
-import {EventStopper} from '#/view/com/util/EventStopper'
import {Text} from '#/view/com/Typography'
import {Portal} from '#/view/com/Portal'
import {Button} from '#/view/com/Button'
import {DialogProps, DialogControl} from '#/view/com/Dialog/types'
+const stopPropagation = (e: any) => e.stopPropagation()
+
const Context = React.createContext<{
close: () => void
}>({
@@ -58,6 +59,8 @@ export function Outer({
)
React.useEffect(() => {
+ if (!isOpen) return
+
function handler(e: KeyboardEvent) {
if (e.key === 'Escape') close()
}
@@ -65,7 +68,7 @@ export function Outer({
document.addEventListener('keydown', handler)
return () => document.removeEventListener('keydown', handler)
- }, [close])
+ }, [isOpen, close])
const context = React.useMemo(() => ({close}), [close])
@@ -105,14 +108,20 @@ export function Outer({
paddingTop: gtMobile ? ('10vh' as DimensionValue) : 0,
},
]}>
-
-
- {children}
-
-
+ true}
+ onTouchEnd={stopPropagation}>
+
+
+ {children}
+
+
+
diff --git a/src/view/com/Portal.tsx b/src/view/com/Portal.tsx
index 47a216bd80..1813d9e05e 100644
--- a/src/view/com/Portal.tsx
+++ b/src/view/com/Portal.tsx
@@ -33,8 +33,6 @@ export function Provider(props: React.PropsWithChildren<{}>) {
setOutlet(<>{Object.values(map.current)}>)
}, [])
- console.log(outlet)
-
return (
{props.children}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index b6598cf669..24538bcb33 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -334,15 +334,24 @@ function Dialogs() {
-
- control.current.close()}
- accessibilityLabel="Open basic dialog"
- accessibilityHint="Open basic dialog">
- Close basic dialog
-
+
+ control.current.close()}
+ accessibilityLabel="Open basic dialog"
+ accessibilityHint="Open basic dialog">
+ Close basic dialog
+
+ control.current.close()}
+ accessibilityLabel="Open basic dialog"
+ accessibilityHint="Open basic dialog">
+ Some other control
+
+
>
From c100f04a68b458647548f29e74d4bc1e118d62a3 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 11 Jan 2024 13:05:32 -0600
Subject: [PATCH 17/68] Fix animations
---
src/view/com/Dialog/index.web.tsx | 45 +++++++++++++++++++------------
1 file changed, 28 insertions(+), 17 deletions(-)
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
index f1576d6f33..3d1a267ceb 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/view/com/Dialog/index.web.tsx
@@ -39,13 +39,17 @@ export function Outer({
const t = useTheme()
const {gtMobile} = useBreakpoints()
const [isOpen, setIsOpen] = React.useState(false)
+ const [isVisible, setIsVisible] = React.useState(true)
const open = React.useCallback(() => {
setIsOpen(true)
}, [setIsOpen])
- const close = React.useCallback(() => {
+ const close = React.useCallback(async () => {
+ setIsVisible(false)
+ await new Promise(resolve => setTimeout(resolve, 150))
setIsOpen(false)
+ setIsVisible(true)
onClose?.()
}, [onClose, setIsOpen])
@@ -88,16 +92,18 @@ export function Outer({
a.flex_row,
a.justify_center,
]}>
-
+ {isVisible && (
+
+ )}
true}
onTouchEnd={stopPropagation}>
-
- {children}
-
+ {isVisible ? (
+
+ {children}
+
+ ) : (
+
+ )}
From bf1641d03aa91037f0b197e9685fbcc195857196 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 11 Jan 2024 14:46:03 -0600
Subject: [PATCH 18/68] Improvements to web modal, exiting still broken
---
src/alf/atoms.ts | 3 ++
src/view/com/Dialog/index.web.tsx | 74 +++++++++++++++++--------------
src/view/screens/DebugNew.tsx | 1 +
3 files changed, 44 insertions(+), 34 deletions(-)
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index c142f5f716..15972ce67d 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -4,6 +4,9 @@ export const atoms = {
/*
* Positioning
*/
+ fixed: {
+ position: 'fixed',
+ },
absolute: {
position: 'absolute',
},
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
index 3d1a267ceb..ea5e309da0 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/view/com/Dialog/index.web.tsx
@@ -1,9 +1,9 @@
import React, {useImperativeHandle} from 'react'
-import {View, TouchableWithoutFeedback, DimensionValue} from 'react-native'
+import {View, TouchableWithoutFeedback, ViewStyle} from 'react-native'
import {FocusScope} from '@tamagui/focus-scope'
-import Animated, {FadeInDown, FadeIn, FadeOut} from 'react-native-reanimated'
+import Animated, {FadeInDown, FadeIn} from 'react-native-reanimated'
-import {useTheme, atoms as a, useBreakpoints} from '#/alf'
+import {useTheme, atoms as a, useBreakpoints, web} from '#/alf'
import {Text} from '#/view/com/Typography'
import {Portal} from '#/view/com/Portal'
import {Button} from '#/view/com/Button'
@@ -86,20 +86,21 @@ export function Outer({
onPress={close}>
{isVisible && (
@@ -109,30 +110,13 @@ export function Outer({
style={[
a.w_full,
a.z_20,
+ a.justify_center,
+ a.align_center,
{
- maxWidth: 600,
- paddingTop: gtMobile ? ('10vh' as DimensionValue) : 0,
+ minHeight: web('calc(90vh - 36px)') || undefined,
},
]}>
- true}
- onTouchEnd={stopPropagation}>
-
- {isVisible ? (
-
- {children}
-
- ) : (
-
- )}
-
-
+ {isVisible ? children : null}
@@ -143,12 +127,34 @@ export function Outer({
)
}
-export function Inner(props: React.PropsWithChildren<{}>) {
+export function Inner({
+ children,
+ style,
+}: React.PropsWithChildren<{style?: ViewStyle}>) {
const t = useTheme()
+ const {gtMobile} = useBreakpoints()
return (
-
- {props.children}
-
+
+ true}
+ onTouchEnd={stopPropagation}
+ entering={FadeInDown.duration(100)}
+ // exiting={FadeOut.duration(100)}
+ style={[
+ a.relative,
+ a.rounded_md,
+ a.w_full,
+ gtMobile ? a.p_xl : a.p_lg,
+ t.atoms.bg,
+ {maxWidth: 600},
+ ...(Array.isArray(style) ? style : [style || {}]),
+ ]}>
+ {children}
+
+
)
}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index 24538bcb33..b6bd1f2c89 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -343,6 +343,7 @@ function Dialogs() {
accessibilityHint="Open basic dialog">
Close basic dialog
+
Date: Thu, 11 Jan 2024 14:56:10 -0600
Subject: [PATCH 19/68] Clean up dialog types
---
src/view/com/Dialog/context.ts | 19 ++++++++++++++++
src/view/com/Dialog/index.tsx | 36 +++++++++++--------------------
src/view/com/Dialog/index.web.tsx | 35 +++++++-----------------------
src/view/com/Dialog/types.ts | 13 ++++++++---
src/view/screens/DebugNew.tsx | 2 +-
5 files changed, 51 insertions(+), 54 deletions(-)
create mode 100644 src/view/com/Dialog/context.ts
diff --git a/src/view/com/Dialog/context.ts b/src/view/com/Dialog/context.ts
new file mode 100644
index 0000000000..bd150f8cbc
--- /dev/null
+++ b/src/view/com/Dialog/context.ts
@@ -0,0 +1,19 @@
+import React from 'react'
+import {DialogContextProps, DialogControlProps} from '#/view/com/Dialog/types'
+
+export const Context = React.createContext({
+ close: () => {},
+})
+
+export function useDialogContext() {
+ return React.useContext(Context)
+}
+
+export function useDialogControl() {
+ const control = React.useRef({
+ open: () => {},
+ close: () => {},
+ })
+
+ return control
+}
diff --git a/src/view/com/Dialog/index.tsx b/src/view/com/Dialog/index.tsx
index 229cd7b867..6d6946ec44 100644
--- a/src/view/com/Dialog/index.tsx
+++ b/src/view/com/Dialog/index.tsx
@@ -5,36 +5,24 @@ import BottomSheet, {BottomSheetBackdrop} from '@gorhom/bottom-sheet'
import {useTheme, atoms as a} from '#/alf'
import {Portal} from '#/view/com/Portal'
-import {DialogProps, DialogControl} from '#/view/com/Dialog/types'
+import {
+ DialogOuterProps,
+ DialogControlProps,
+ DialogInnerProps,
+} from '#/view/com/Dialog/types'
+import {Context} from '#/view/com/Dialog/context'
-const Context = React.createContext<{
- close: () => void
-}>({
- close: () => {},
-})
-
-export function useDialogControl() {
- const control = React.useRef({
- open: () => {},
- close: () => {},
- })
-
- return control
-}
-
-export function useDialog() {
- return React.useContext(Context)
-}
+export {useDialogControl} from '#/view/com/Dialog/context'
export function Outer({
control,
onClose,
children,
-}: React.PropsWithChildren) {
+}: React.PropsWithChildren) {
const t = useTheme()
const sheet = React.useRef(null)
- const open = React.useCallback((i = 0) => {
+ const open = React.useCallback((i = 0) => {
sheet.current?.snapToIndex(i)
}, [])
@@ -52,6 +40,8 @@ export function Outer({
[open, close],
)
+ const context = React.useMemo(() => ({close}), [close])
+
return (
{}}
onClose={onClose}>
- {children}
+ {children}
)
}
-export function Inner(props: React.PropsWithChildren<{}>) {
+export function Inner(props: DialogInnerProps) {
const t = useTheme()
return (
e.stopPropagation()
-
-const Context = React.createContext<{
- close: () => void
-}>({
- close: () => {},
-})
-
-export function useDialogControl() {
- const control = React.useRef({
- open: () => {},
- close: () => {},
- })
-
- return control
-}
+export {useDialogControl} from '#/view/com/Dialog/context'
-export function useDialog() {
- return React.useContext(Context)
-}
+const stopPropagation = (e: any) => e.stopPropagation()
export function Outer({
control,
onClose,
children,
-}: React.PropsWithChildren) {
+}: React.PropsWithChildren) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
const [isOpen, setIsOpen] = React.useState(false)
@@ -127,10 +111,7 @@ export function Outer({
)
}
-export function Inner({
- children,
- style,
-}: React.PropsWithChildren<{style?: ViewStyle}>) {
+export function Inner({children, style}: DialogInnerProps) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
return (
@@ -164,7 +145,7 @@ export function Handle() {
export function Close() {
const t = useTheme()
- const {close} = useDialog()
+ const {close} = useDialogContext()
return (
void
+}
+
+export type DialogControlProps = {
open: (index?: number) => void
close: () => void
}
-export type DialogProps = {
- control: React.RefObject
+export type DialogOuterProps = {
+ control: React.RefObject
onClose?: () => void
}
+
+export type DialogInnerProps = React.PropsWithChildren<{style?: ViewStyle}>
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index b6bd1f2c89..6abcc438b7 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -332,7 +332,7 @@ function Dialogs() {
-
+
Date: Thu, 11 Jan 2024 22:06:59 -0600
Subject: [PATCH 20/68] Add Prompt, Dialog refinement, mobile refinement
---
src/alf/index.tsx | 1 +
src/alf/themes.ts | 24 ++++++
src/alf/util/flatten.ts | 3 +
src/view/com/Button.tsx | 8 +-
src/view/com/Dialog/context.ts | 6 +-
src/view/com/Dialog/index.tsx | 40 +++++----
src/view/com/Dialog/index.web.tsx | 24 ++++--
src/view/com/Dialog/types.ts | 21 ++++-
src/view/com/Prompt.tsx | 134 ++++++++++++++++++++++++++++++
src/view/com/Typography.tsx | 34 ++++++--
src/view/screens/DebugNew.tsx | 68 ++++++++++-----
11 files changed, 305 insertions(+), 58 deletions(-)
create mode 100644 src/alf/util/flatten.ts
create mode 100644 src/view/com/Prompt.tsx
diff --git a/src/alf/index.tsx b/src/alf/index.tsx
index 1daa0bfedc..69a8798539 100644
--- a/src/alf/index.tsx
+++ b/src/alf/index.tsx
@@ -5,6 +5,7 @@ import * as themes from '#/alf/themes'
export * as tokens from '#/alf/tokens'
export {atoms} from '#/alf/atoms'
export * from '#/alf/util/platform'
+export * from '#/alf/util/flatten'
type BreakpointName = keyof typeof breakpoints
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index 14e1de7ff6..f26f10a6f2 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -11,12 +11,36 @@ export const lightPalette = {
primary: tokens.color.blue_500,
positive: tokens.color.green_500,
negative: tokens.color.red_500,
+
+ contrast_0: tokens.color.gray_0,
+ contrast_100: tokens.color.gray_100,
+ contrast_200: tokens.color.gray_200,
+ contrast_300: tokens.color.gray_300,
+ contrast_400: tokens.color.gray_400,
+ contrast_500: tokens.color.gray_500,
+ contrast_600: tokens.color.gray_600,
+ contrast_700: tokens.color.gray_700,
+ contrast_800: tokens.color.gray_800,
+ contrast_900: tokens.color.gray_900,
+ contrast_1000: tokens.color.gray_1000,
} as const
export const darkPalette: Palette = {
primary: tokens.color.blue_500,
positive: tokens.color.green_400,
negative: tokens.color.red_400,
+
+ contrast_0: tokens.color.gray_1000,
+ contrast_100: tokens.color.gray_900,
+ contrast_200: tokens.color.gray_800,
+ contrast_300: tokens.color.gray_700,
+ contrast_400: tokens.color.gray_600,
+ contrast_500: tokens.color.gray_500,
+ contrast_600: tokens.color.gray_400,
+ contrast_700: tokens.color.gray_300,
+ contrast_800: tokens.color.gray_200,
+ contrast_900: tokens.color.gray_100,
+ contrast_1000: tokens.color.gray_0,
} as const
export const light = {
diff --git a/src/alf/util/flatten.ts b/src/alf/util/flatten.ts
new file mode 100644
index 0000000000..448716a082
--- /dev/null
+++ b/src/alf/util/flatten.ts
@@ -0,0 +1,3 @@
+import {StyleSheet} from 'react-native'
+
+export const flatten = StyleSheet.flatten
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
index 7b3beb7dca..51fe1e3541 100644
--- a/src/view/com/Button.tsx
+++ b/src/view/com/Button.tsx
@@ -284,13 +284,7 @@ export function ButtonText({
return (
+ style={[atoms.font_semibold, atoms.text_center, ...textStyles, style]}>
{children}
)
diff --git a/src/view/com/Dialog/context.ts b/src/view/com/Dialog/context.ts
index bd150f8cbc..2330969933 100644
--- a/src/view/com/Dialog/context.ts
+++ b/src/view/com/Dialog/context.ts
@@ -15,5 +15,9 @@ export function useDialogControl() {
close: () => {},
})
- return control
+ return {
+ ref: control,
+ open: () => control.current.open(),
+ close: () => control.current.close(),
+ }
}
diff --git a/src/view/com/Dialog/index.tsx b/src/view/com/Dialog/index.tsx
index 6d6946ec44..7f7a630a14 100644
--- a/src/view/com/Dialog/index.tsx
+++ b/src/view/com/Dialog/index.tsx
@@ -12,12 +12,14 @@ import {
} from '#/view/com/Dialog/types'
import {Context} from '#/view/com/Dialog/context'
-export {useDialogControl} from '#/view/com/Dialog/context'
+export {useDialogControl, useDialogContext} from '#/view/com/Dialog/context'
+export * from '#/view/com/Dialog/types'
export function Outer({
+ children,
control,
onClose,
- children,
+ nativeOptions,
}: React.PropsWithChildren) {
const t = useTheme()
const sheet = React.useRef(null)
@@ -32,7 +34,7 @@ export function Outer({
}, [onClose])
useImperativeHandle(
- control,
+ control.ref,
() => ({
open,
close,
@@ -45,13 +47,14 @@ export function Outer({
return (
(
{}}
onClose={onClose}>
- {children}
+
+
+ {children}
+
)
}
+// TODO a11y props here, or is that handled by the sheet?
export function Inner(props: DialogInnerProps) {
- const t = useTheme()
return (
{props.children}
@@ -101,7 +113,7 @@ export function Handle() {
t.atoms.bg_contrast_200,
{
top: 12,
- width: 80,
+ width: 50,
height: 6,
alignSelf: 'center',
},
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
index ee390daba1..37d4cebc85 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/view/com/Dialog/index.web.tsx
@@ -11,7 +11,8 @@ import {Button} from '#/view/com/Button'
import {DialogOuterProps, DialogInnerProps} from '#/view/com/Dialog/types'
import {Context, useDialogContext} from '#/view/com/Dialog/context'
-export {useDialogControl} from '#/view/com/Dialog/context'
+export {useDialogControl, useDialogContext} from '#/view/com/Dialog/context'
+export * from '#/view/com/Dialog/types'
const stopPropagation = (e: any) => e.stopPropagation()
@@ -38,7 +39,7 @@ export function Outer({
}, [onClose, setIsOpen])
useImperativeHandle(
- control,
+ control.ref,
() => ({
open,
close,
@@ -58,7 +59,12 @@ export function Outer({
return () => document.removeEventListener('keydown', handler)
}, [isOpen, close])
- const context = React.useMemo(() => ({close}), [close])
+ const context = React.useMemo(
+ () => ({
+ close,
+ }),
+ [close],
+ )
return (
<>
@@ -111,12 +117,19 @@ export function Outer({
)
}
-export function Inner({children, style}: DialogInnerProps) {
+export function Inner({
+ children,
+ style,
+ accessibilityLabelledBy,
+ accessibilityDescribedBy,
+}: DialogInnerProps) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
return (
{children}
diff --git a/src/view/com/Dialog/types.ts b/src/view/com/Dialog/types.ts
index 4c41849aa3..cdcad90da6 100644
--- a/src/view/com/Dialog/types.ts
+++ b/src/view/com/Dialog/types.ts
@@ -1,5 +1,8 @@
import React from 'react'
-import type {ViewStyle} from 'react-native'
+import type {ViewStyle, AccessibilityProps} from 'react-native'
+import {BottomSheetProps} from '@gorhom/bottom-sheet'
+
+type A11yProps = Required
export type DialogContextProps = {
close: () => void
@@ -11,8 +14,20 @@ export type DialogControlProps = {
}
export type DialogOuterProps = {
- control: React.RefObject
+ control: {
+ ref: React.RefObject
+ open: (index?: number) => void
+ close: () => void
+ }
onClose?: () => void
+ nativeOptions?: {
+ sheet?: Omit
+ }
+ webOptions?: {}
}
-export type DialogInnerProps = React.PropsWithChildren<{style?: ViewStyle}>
+export type DialogInnerProps = React.PropsWithChildren<{
+ style?: ViewStyle
+ accessibilityLabelledBy: A11yProps['aria-labelledby']
+ accessibilityDescribedBy: string
+}>
diff --git a/src/view/com/Prompt.tsx b/src/view/com/Prompt.tsx
new file mode 100644
index 0000000000..fc55bc4a51
--- /dev/null
+++ b/src/view/com/Prompt.tsx
@@ -0,0 +1,134 @@
+import React from 'react'
+import {View, PressableProps, LayoutChangeEvent} from 'react-native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+
+import {useTheme, atoms as a} from '#/alf'
+import {H2, P} from '#/view/com/Typography'
+import {Button} from '#/view/com/Button'
+
+import * as Dialog from '#/view/com/Dialog'
+
+export {useDialogControl as usePromptControl} from '#/view/com/Dialog'
+
+const Context = React.createContext<{
+ titleId: string
+ descriptionId: string
+}>({
+ titleId: '',
+ descriptionId: '',
+})
+
+export function Outer({
+ children,
+ control,
+}: React.PropsWithChildren<{
+ control: Dialog.DialogOuterProps['control']
+}>) {
+ const insets = useSafeAreaInsets()
+ const titleId = React.useId()
+ const descriptionId = React.useId()
+ const [defaultSnapPoints, setDefaultSnapPoints] = React.useState<
+ string | number
+ >('25%')
+
+ const context = React.useMemo(
+ () => ({titleId, descriptionId}),
+ [titleId, descriptionId],
+ )
+
+ const measureDefaultSnapPoint = React.useCallback(
+ (e: LayoutChangeEvent) => {
+ setDefaultSnapPoints(e.nativeEvent.layout.height + insets.bottom + 50)
+ },
+ [insets, setDefaultSnapPoints],
+ )
+
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+export function Title({children}: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ const {titleId} = React.useContext(Context)
+ return (
+
+ {children}
+
+ )
+}
+
+export function Description({children}: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ const {descriptionId} = React.useContext(Context)
+ return (
+
+ {children}
+
+ )
+}
+
+export function Actions({children}: React.PropsWithChildren<{}>) {
+ return (
+
+ {children}
+
+ )
+}
+
+export function Cancel({
+ children,
+}: React.PropsWithChildren<{onPress?: PressableProps['onPress']}>) {
+ const {close} = Dialog.useDialogContext()
+ return (
+
+ {children}
+
+ )
+}
+
+export function Action({
+ children,
+ onPress,
+}: React.PropsWithChildren<{onPress?: () => void}>) {
+ const {close} = Dialog.useDialogContext()
+ const handleOnPress = React.useCallback(() => {
+ close()
+ onPress?.()
+ }, [close, onPress])
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/view/com/Typography.tsx b/src/view/com/Typography.tsx
index 6579c2e511..029fc2e24f 100644
--- a/src/view/com/Typography.tsx
+++ b/src/view/com/Typography.tsx
@@ -1,6 +1,7 @@
import React from 'react'
import {Text as RNText, TextProps} from 'react-native'
-import {useTheme, atoms, web} from '#/alf'
+
+import {useTheme, atoms, web, flatten} from '#/alf'
export function Text({style, ...rest}: TextProps) {
const t = useTheme()
@@ -18,7 +19,7 @@ export function H1({style, ...rest}: TextProps) {
)
}
@@ -34,7 +35,7 @@ export function H2({style, ...rest}: TextProps) {
)
}
@@ -50,7 +51,7 @@ export function H3({style, ...rest}: TextProps) {
)
}
@@ -66,7 +67,7 @@ export function H4({style, ...rest}: TextProps) {
)
}
@@ -82,7 +83,7 @@ export function H5({style, ...rest}: TextProps) {
)
}
@@ -98,7 +99,26 @@ export function H6({style, ...rest}: TextProps) {
+ )
+}
+
+export function P({style, ...rest}: TextProps) {
+ const t = useTheme()
+ const attr =
+ web({
+ role: 'paragraph',
+ }) || {}
+ const _style = flatten(style)
+ const lineHeight =
+ (_style?.lineHeight || atoms.text_md.lineHeight) *
+ atoms.leading_normal.lineHeight
+ return (
+
)
}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index 6abcc438b7..27b993c95e 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -8,32 +8,39 @@ import * as tokens from '#/alf/tokens'
import {atoms as a, useTheme, useBreakpoints, ThemeProvider as Alf} from '#/alf'
import {Button, ButtonText} from '#/view/com/Button'
import {Link} from '#/view/com/Link'
-import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
+import {Text, H1, H2, H3, H4, H5, H6, P} from '#/view/com/Typography'
import {InputText} from '#/view/com/forms/InputText'
import {InputDate, utils} from '#/view/com/forms/InputDate'
import {Logo} from '#/view/icons/Logo'
import * as Dialog from '#/view/com/Dialog'
+import * as Prompt from '#/view/com/Prompt'
function ThemeSelector() {
const setColorMode = useSetColorMode()
return (
-
+
setColorMode('system')}>
System
setColorMode('light')}>
Light
setColorMode('dark')}>
Dark
@@ -319,39 +326,58 @@ function Forms() {
function Dialogs() {
const control = Dialog.useDialogControl()
+ const prompt = Prompt.usePromptControl()
return (
<>
control.current.open()}
+ onPress={() => control.open()}
accessibilityLabel="Open basic dialog"
accessibilityHint="Open basic dialog">
Open basic dialog
+ prompt.open()}
+ accessibilityLabel="Open prompt"
+ accessibilityHint="Open prompt">
+ Open prompt
+
+
+
+ Are you sure?
+
+ This action cannot be undone. This action cannot be undone. This
+ action cannot be undone.
+
+
+ Cancel
+ Confirm
+
+
+
-
+
- control.current.close()}
- accessibilityLabel="Open basic dialog"
- accessibilityHint="Open basic dialog">
- Close basic dialog
-
-
- control.current.close()}
- accessibilityLabel="Open basic dialog"
- accessibilityHint="Open basic dialog">
- Some other control
-
+ Dialog
+ Description
+
+ control.close()}
+ accessibilityLabel="Open basic dialog"
+ accessibilityHint="Open basic dialog">
+ Close basic dialog
+
+
From e85df5415605886cd49ef7b56a9ddfab83f433ec Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Fri, 12 Jan 2024 11:22:29 -0600
Subject: [PATCH 21/68] Integrate new design tokens, reorg storybook
---
src/Navigation.tsx | 4 +-
src/alf/atoms.ts | 312 ++++++--
src/alf/themes.ts | 86 +-
src/alf/tokens.ts | 112 +--
src/view/com/Button.tsx | 2 +-
src/view/com/Dialog/index.tsx | 2 +-
src/view/com/Prompt.tsx | 4 +-
src/view/com/Typography.tsx | 12 +-
.../com/forms/InputDate/index.android.tsx | 4 +-
src/view/com/forms/InputDate/index.tsx | 4 +-
src/view/com/forms/InputDate/index.web.tsx | 4 +-
src/view/com/forms/InputText.tsx | 4 +-
src/view/icons/Logo.tsx | 5 +-
src/view/screens/Debug.tsx | 520 ------------
src/view/screens/DebugNew.tsx | 746 ------------------
src/view/screens/Storybook/Breakpoints.tsx | 25 +
src/view/screens/Storybook/Buttons.tsx | 120 +++
src/view/screens/Storybook/Dialogs.tsx | 69 ++
src/view/screens/Storybook/Forms.tsx | 66 ++
src/view/screens/Storybook/Palette.tsx | 279 +++++++
src/view/screens/Storybook/Spacing.tsx | 64 ++
src/view/screens/Storybook/Theming.tsx | 56 ++
src/view/screens/Storybook/Typography.tsx | 29 +
src/view/screens/Storybook/index.tsx | 69 ++
24 files changed, 1137 insertions(+), 1461 deletions(-)
delete mode 100644 src/view/screens/Debug.tsx
delete mode 100644 src/view/screens/DebugNew.tsx
create mode 100644 src/view/screens/Storybook/Breakpoints.tsx
create mode 100644 src/view/screens/Storybook/Buttons.tsx
create mode 100644 src/view/screens/Storybook/Dialogs.tsx
create mode 100644 src/view/screens/Storybook/Forms.tsx
create mode 100644 src/view/screens/Storybook/Palette.tsx
create mode 100644 src/view/screens/Storybook/Spacing.tsx
create mode 100644 src/view/screens/Storybook/Theming.tsx
create mode 100644 src/view/screens/Storybook/Typography.tsx
create mode 100644 src/view/screens/Storybook/index.tsx
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 76a893c68c..534ba28d5b 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -61,7 +61,7 @@ import {ProfileListScreen} from './view/screens/ProfileList'
import {PostThreadScreen} from './view/screens/PostThread'
import {PostLikedByScreen} from './view/screens/PostLikedBy'
import {PostRepostedByScreen} from './view/screens/PostRepostedBy'
-import {DebugScreen} from './view/screens/DebugNew'
+import {Storybook} from './view/screens/Storybook'
import {LogScreen} from './view/screens/Log'
import {SupportScreen} from './view/screens/Support'
import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy'
@@ -191,7 +191,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
/>
DebugScreen}
+ getComponent={() => Storybook}
options={{title: title('Debug'), requireAuth: true}}
/>
+ style={[atoms.font_bold, atoms.text_center, ...textStyles, style]}>
{children}
)
diff --git a/src/view/com/Dialog/index.tsx b/src/view/com/Dialog/index.tsx
index 7f7a630a14..5a832e6fe4 100644
--- a/src/view/com/Dialog/index.tsx
+++ b/src/view/com/Dialog/index.tsx
@@ -91,7 +91,7 @@ export function Inner(props: DialogInnerProps) {
) {
return (
+ style={[a.font_bold, t.atoms.text_contrast_600, a.pb_sm]}>
{children}
)
@@ -82,7 +82,7 @@ export function Description({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
const {descriptionId} = React.useContext(Context)
return (
-
+
{children}
)
diff --git a/src/view/com/Typography.tsx b/src/view/com/Typography.tsx
index 029fc2e24f..66cf0720de 100644
--- a/src/view/com/Typography.tsx
+++ b/src/view/com/Typography.tsx
@@ -19,7 +19,7 @@ export function H1({style, ...rest}: TextProps) {
)
}
@@ -35,7 +35,7 @@ export function H2({style, ...rest}: TextProps) {
)
}
@@ -51,7 +51,7 @@ export function H3({style, ...rest}: TextProps) {
)
}
@@ -67,7 +67,7 @@ export function H4({style, ...rest}: TextProps) {
)
}
@@ -83,7 +83,7 @@ export function H5({style, ...rest}: TextProps) {
)
}
@@ -99,7 +99,7 @@ export function H6({style, ...rest}: TextProps) {
)
}
diff --git a/src/view/com/forms/InputDate/index.android.tsx b/src/view/com/forms/InputDate/index.android.tsx
index a492ad7da9..13b1ac25d2 100644
--- a/src/view/com/forms/InputDate/index.android.tsx
+++ b/src/view/com/forms/InputDate/index.android.tsx
@@ -87,8 +87,8 @@ export function InputDate({
nativeID={labelId}
style={[
atoms.text_sm,
- atoms.font_semibold,
- t.atoms.text_contrast_700,
+ atoms.font_bold,
+ t.atoms.text_contrast_600,
atoms.mb_sm,
]}>
{label}
diff --git a/src/view/com/forms/InputDate/index.tsx b/src/view/com/forms/InputDate/index.tsx
index eb642c3a08..89a19560b7 100644
--- a/src/view/com/forms/InputDate/index.tsx
+++ b/src/view/com/forms/InputDate/index.tsx
@@ -48,8 +48,8 @@ export function InputDate({
nativeID={labelId}
style={[
atoms.text_sm,
- atoms.font_semibold,
- t.atoms.text_contrast_700,
+ atoms.font_bold,
+ t.atoms.text_contrast_600,
atoms.mb_sm,
]}>
{label}
diff --git a/src/view/com/forms/InputDate/index.web.tsx b/src/view/com/forms/InputDate/index.web.tsx
index 33a7473850..87e41e8e65 100644
--- a/src/view/com/forms/InputDate/index.web.tsx
+++ b/src/view/com/forms/InputDate/index.web.tsx
@@ -85,8 +85,8 @@ export function InputDate({
nativeID={labelId}
style={[
atoms.text_sm,
- atoms.font_semibold,
- t.atoms.text_contrast_700,
+ atoms.font_bold,
+ t.atoms.text_contrast_600,
atoms.mb_sm,
]}>
{label}
diff --git a/src/view/com/forms/InputText.tsx b/src/view/com/forms/InputText.tsx
index c573a3c650..f66f817422 100644
--- a/src/view/com/forms/InputText.tsx
+++ b/src/view/com/forms/InputText.tsx
@@ -100,8 +100,8 @@ export function InputText({
nativeID={labelId}
style={[
atoms.text_sm,
- atoms.font_semibold,
- t.atoms.text_contrast_700,
+ atoms.font_bold,
+ t.atoms.text_contrast_600,
atoms.mb_sm,
]}>
{label}
diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx
index 9f9f57fc85..9212381a9e 100644
--- a/src/view/icons/Logo.tsx
+++ b/src/view/icons/Logo.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {StyleSheet} from 'react-native'
+import {StyleSheet, TextProps} from 'react-native'
import Svg, {
Path,
Defs,
@@ -15,7 +15,8 @@ const ratio = 57 / 64
type Props = {
fill?: PathProps['fill']
-} & SvgProps
+ style?: TextProps['style']
+} & Omit
export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
const {fill, ...rest} = props
diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx
deleted file mode 100644
index 0e0464200e..0000000000
--- a/src/view/screens/Debug.tsx
+++ /dev/null
@@ -1,520 +0,0 @@
-import React from 'react'
-import {ScrollView, View} from 'react-native'
-import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
-import {ViewHeader} from '../com/util/ViewHeader'
-import {ThemeProvider, PaletteColorName} from 'lib/ThemeContext'
-import {usePalette} from 'lib/hooks/usePalette'
-import {s} from 'lib/styles'
-import * as Toast from 'view/com/util/Toast'
-import {Text} from '../com/util/text/Text'
-import {ViewSelector} from '../com/util/ViewSelector'
-import {EmptyState} from '../com/util/EmptyState'
-import * as LoadingPlaceholder from '../com/util/LoadingPlaceholder'
-import {Button, ButtonType} from '../com/util/forms/Button'
-import {DropdownButton, DropdownItem} from '../com/util/forms/DropdownButton'
-import {ToggleButton} from '../com/util/forms/ToggleButton'
-import {RadioGroup} from '../com/util/forms/RadioGroup'
-import {ErrorScreen} from '../com/util/error/ErrorScreen'
-import {ErrorMessage} from '../com/util/error/ErrorMessage'
-
-const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs']
-
-export const DebugScreen = ({}: NativeStackScreenProps<
- CommonNavigatorParams,
- 'Debug'
->) => {
- const [colorScheme, setColorScheme] = React.useState<'light' | 'dark'>(
- 'light',
- )
- const onToggleColorScheme = () => {
- setColorScheme(colorScheme === 'light' ? 'dark' : 'light')
- }
- return (
-
-
-
- )
-}
-
-function DebugInner({
- colorScheme,
- onToggleColorScheme,
-}: {
- colorScheme: 'light' | 'dark'
- onToggleColorScheme: () => void
-}) {
- const [currentView, setCurrentView] = React.useState(0)
- const pal = usePalette('default')
-
- const renderItem = (item: any) => {
- return (
-
-
-
-
- {item.currentView === 3 ? (
-
- ) : item.currentView === 2 ? (
-
- ) : item.currentView === 1 ? (
-
- ) : (
-
- )}
-
- )
- }
-
- const items = [{currentView}]
-
- return (
-
-
-
-
- )
-}
-
-function Heading({label}: {label: string}) {
- const pal = usePalette('default')
- return (
-
-
- {label}
-
-
- )
-}
-
-function BaseView() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-function ControlsView() {
- return (
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-function ErrorView() {
- return (
-
-
- {}}
- />
-
-
-
-
-
-
-
-
- {}}
- />
-
-
- {}}
- numberOfLines={1}
- />
-
-
- )
-}
-
-function NotifsView() {
- const triggerPush = () => {
- // TODO: implement local notification for testing
- }
- const triggerToast = () => {
- Toast.show('The task has been completed')
- }
- const triggerToast2 = () => {
- Toast.show('The task has been completed successfully and with no problems')
- }
- return (
-
-
-
-
-
-
-
- )
-}
-
-function PaletteView({palette}: {palette: PaletteColorName}) {
- const defaultPal = usePalette('default')
- const pal = usePalette(palette)
- return (
-
- {palette} colors
- Light text
- Link text
- {palette !== 'default' && (
-
- Inverted text
-
- )}
-
- )
-}
-
-function TypographyView() {
- const pal = usePalette('default')
- return (
-
-
- '2xl-thin' lorem ipsum dolor
-
-
- '2xl' lorem ipsum dolor
-
-
- '2xl-medium' lorem ipsum dolor
-
-
- '2xl-bold' lorem ipsum dolor
-
-
- '2xl-heavy' lorem ipsum dolor
-
-
- 'xl-thin' lorem ipsum dolor
-
-
- 'xl' lorem ipsum dolor
-
-
- 'xl-medium' lorem ipsum dolor
-
-
- 'xl-bold' lorem ipsum dolor
-
-
- 'xl-heavy' lorem ipsum dolor
-
-
- 'lg-thin' lorem ipsum dolor
-
-
- 'lg' lorem ipsum dolor
-
-
- 'lg-medium' lorem ipsum dolor
-
-
- 'lg-bold' lorem ipsum dolor
-
-
- 'lg-heavy' lorem ipsum dolor
-
-
- 'md-thin' lorem ipsum dolor
-
-
- 'md' lorem ipsum dolor
-
-
- 'md-medium' lorem ipsum dolor
-
-
- 'md-bold' lorem ipsum dolor
-
-
- 'md-heavy' lorem ipsum dolor
-
-
- 'sm-thin' lorem ipsum dolor
-
-
- 'sm' lorem ipsum dolor
-
-
- 'sm-medium' lorem ipsum dolor
-
-
- 'sm-bold' lorem ipsum dolor
-
-
- 'sm-heavy' lorem ipsum dolor
-
-
- 'xs-thin' lorem ipsum dolor
-
-
- 'xs' lorem ipsum dolor
-
-
- 'xs-medium' lorem ipsum dolor
-
-
- 'xs-bold' lorem ipsum dolor
-
-
- 'xs-heavy' lorem ipsum dolor
-
-
-
- 'title-2xl' lorem ipsum dolor
-
-
- 'title-xl' lorem ipsum dolor
-
-
- 'title-lg' lorem ipsum dolor
-
-
- 'title' lorem ipsum dolor
-
-
- Button
-
-
- Button-lg
-
-
- )
-}
-
-function EmptyStateView() {
- return
-}
-
-function LoadingPlaceholderView() {
- return (
- <>
-
-
- >
- )
-}
-
-function ButtonsView() {
- const defaultPal = usePalette('default')
- const buttonStyles = {marginRight: 5}
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-const DROPDOWN_ITEMS: DropdownItem[] = [
- {
- icon: ['far', 'paste'],
- label: 'Copy post text',
- onPress() {},
- },
- {
- icon: 'share',
- label: 'Share...',
- onPress() {},
- },
- {
- icon: 'circle-exclamation',
- label: 'Report post',
- onPress() {},
- },
-]
-function DropdownButtonsView() {
- const defaultPal = usePalette('default')
- return (
-
-
-
-
-
-
- Bare
-
-
-
- )
-}
-
-function ToggleButtonsView() {
- const defaultPal = usePalette('default')
- const buttonStyles = s.mb5
- const [isSelected, setIsSelected] = React.useState(false)
- const onToggle = () => setIsSelected(!isSelected)
- return (
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-const RADIO_BUTTON_ITEMS = [
- {key: 'default-light', label: 'Default Light'},
- {key: 'primary', label: 'Primary'},
- {key: 'secondary', label: 'Secondary'},
- {key: 'inverted', label: 'Inverted'},
- {key: 'primary-outline', label: 'Primary Outline'},
- {key: 'secondary-outline', label: 'Secondary Outline'},
- {key: 'primary-light', label: 'Primary Light'},
- {key: 'secondary-light', label: 'Secondary Light'},
-]
-function RadioButtonsView() {
- const defaultPal = usePalette('default')
- const [rgType, setRgType] = React.useState('default-light')
- return (
-
- setRgType(v as ButtonType)}
- />
-
- )
-}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
deleted file mode 100644
index 27b993c95e..0000000000
--- a/src/view/screens/DebugNew.tsx
+++ /dev/null
@@ -1,746 +0,0 @@
-import React from 'react'
-import {View} from 'react-native'
-import {CenteredView, ScrollView} from '#/view/com/util/Views'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-
-import {useSetColorMode} from '#/state/shell'
-import * as tokens from '#/alf/tokens'
-import {atoms as a, useTheme, useBreakpoints, ThemeProvider as Alf} from '#/alf'
-import {Button, ButtonText} from '#/view/com/Button'
-import {Link} from '#/view/com/Link'
-import {Text, H1, H2, H3, H4, H5, H6, P} from '#/view/com/Typography'
-import {InputText} from '#/view/com/forms/InputText'
-import {InputDate, utils} from '#/view/com/forms/InputDate'
-import {Logo} from '#/view/icons/Logo'
-import * as Dialog from '#/view/com/Dialog'
-import * as Prompt from '#/view/com/Prompt'
-
-function ThemeSelector() {
- const setColorMode = useSetColorMode()
-
- return (
-
- setColorMode('system')}>
- System
-
- setColorMode('light')}>
- Light
-
- setColorMode('dark')}>
- Dark
-
-
- )
-}
-
-function BreakpointDebugger() {
- const t = useTheme()
- const breakpoints = useBreakpoints()
-
- return (
-
- Breakpoint Debugger
-
- Current breakpoint: {!breakpoints.gtMobile && mobile}
- {breakpoints.gtMobile && !breakpoints.gtTablet && tablet}
- {breakpoints.gtTablet && desktop}
-
-
- {JSON.stringify(breakpoints, null, 2)}
-
-
- )
-}
-
-function ThemedSection() {
- const t = useTheme()
-
- return (
-
- theme.atoms.text
-
-
- theme.atoms.text_contrast_700
-
-
-
- theme.atoms.text_contrast_500
-
-
-
-
-
- theme.bg
-
-
- theme.bg_contrast_100
-
-
-
-
- theme.bg_contrast_200
-
-
- theme.bg_contrast_300
-
-
-
-
- theme.bg_positive
-
-
- theme.bg_negative
-
-
-
- )
-}
-
-export function Buttons() {
- const t = useTheme()
-
- return (
-
-
- Unstyled button
-
-
-
- {({state}) => (
-
- Entirely custom button, state: {JSON.stringify(state)}
-
- )}
-
-
-
- Default button
-
-
-
- Default button (disabled)
-
-
-
- {({props}) => (
- <>
-
- Default with an icon
- >
- )}
-
-
-
- Small button
-
-
-
- Small button (disabled)
-
-
-
- External
-
-
- External with custom children
-
-
- https://blueskyweb.xyz
-
-
- Internal
-
-
-
- {({props}) => Link as a button}
-
-
- )
-}
-
-function Forms() {
- return (
-
- console.log(text)}
- />
- console.log(text)}
- />
- console.log(text)}
- icon={Logo}
- />
- console.log(text)}
- icon={Logo}
- suffix={() => .bksy.social}
- />
-
- console.log(date)}
- accessibilityLabel="Date"
- accessibilityHint="Enter a date"
- />
- console.log(date)}
- accessibilityLabel="Date"
- accessibilityHint="Enter a date"
- />
-
- )
-}
-
-function Dialogs() {
- const control = Dialog.useDialogControl()
- const prompt = Prompt.usePromptControl()
-
- return (
- <>
- control.open()}
- accessibilityLabel="Open basic dialog"
- accessibilityHint="Open basic dialog">
- Open basic dialog
-
-
- prompt.open()}
- accessibilityLabel="Open prompt"
- accessibilityHint="Open prompt">
- Open prompt
-
-
-
- Are you sure?
-
- This action cannot be undone. This action cannot be undone. This
- action cannot be undone.
-
-
- Cancel
- Confirm
-
-
-
-
-
-
-
- Dialog
- Description
-
- control.close()}
- accessibilityLabel="Open basic dialog"
- accessibilityHint="Open basic dialog">
- Close basic dialog
-
-
-
-
-
- >
- )
-}
-
-export function DebugScreen() {
- const t = useTheme()
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Heading 1
- Heading 2
- Heading 3
- Heading 4
- Heading 5
- Heading 6
-
- atoms.text_xxl
- atoms.text_xl
- atoms.text_lg
- atoms.text_md
- atoms.text_sm
- atoms.text_xs
- atoms.text_xxs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Spacing
-
-
-
- xxs (2px)
-
-
-
-
- xs (4px)
-
-
-
-
- sm (8px)
-
-
-
-
- md (12px)
-
-
-
-
- lg (18px)
-
-
-
-
- xl (24px)
-
-
-
-
- xxl (32px)
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/view/screens/Storybook/Breakpoints.tsx b/src/view/screens/Storybook/Breakpoints.tsx
new file mode 100644
index 0000000000..34a9dbfd6e
--- /dev/null
+++ b/src/view/screens/Storybook/Breakpoints.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a, useTheme, useBreakpoints} from '#/alf'
+import {Text, H3} from '#/view/com/Typography'
+
+export function Breakpoints() {
+ const t = useTheme()
+ const breakpoints = useBreakpoints()
+
+ return (
+
+ Breakpoint Debugger
+
+ Current breakpoint: {!breakpoints.gtMobile && mobile}
+ {breakpoints.gtMobile && !breakpoints.gtTablet && tablet}
+ {breakpoints.gtTablet && desktop}
+
+
+ {JSON.stringify(breakpoints, null, 2)}
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx
new file mode 100644
index 0000000000..41993cc29b
--- /dev/null
+++ b/src/view/screens/Storybook/Buttons.tsx
@@ -0,0 +1,120 @@
+import React from 'react'
+import {View} from 'react-native'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonText} from '#/view/com/Button'
+import {Link} from '#/view/com/Link'
+import {Text, H3} from '#/view/com/Typography'
+
+export function Buttons() {
+ const t = useTheme()
+
+ return (
+
+
+ Unstyled button
+
+
+
+ {({state}) => (
+
+ Entirely custom button, state: {JSON.stringify(state)}
+
+ )}
+
+
+
+ Default button
+
+
+
+ Default button (disabled)
+
+
+
+ {({props}) => (
+ <>
+
+ Default with an icon
+ >
+ )}
+
+
+
+ Small button
+
+
+
+ Small button (disabled)
+
+
+
+ External
+
+
+ External with custom children
+
+
+ https://blueskyweb.xyz
+
+
+ Internal
+
+
+
+ {({props}) => Link as a button}
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
new file mode 100644
index 0000000000..e0fcf0f811
--- /dev/null
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -0,0 +1,69 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a} from '#/alf'
+import {Button} from '#/view/com/Button'
+import {H3, P} from '#/view/com/Typography'
+import * as Dialog from '#/view/com/Dialog'
+import * as Prompt from '#/view/com/Prompt'
+
+export function Dialogs() {
+ const control = Dialog.useDialogControl()
+ const prompt = Prompt.usePromptControl()
+
+ return (
+ <>
+ control.open()}
+ accessibilityLabel="Open basic dialog"
+ accessibilityHint="Open basic dialog">
+ Open basic dialog
+
+
+ prompt.open()}
+ accessibilityLabel="Open prompt"
+ accessibilityHint="Open prompt">
+ Open prompt
+
+
+
+ Are you sure?
+
+ This action cannot be undone. This action cannot be undone. This
+ action cannot be undone.
+
+
+ Cancel
+ Confirm
+
+
+
+
+
+
+
+ Dialog
+ Description
+
+ control.close()}
+ accessibilityLabel="Open basic dialog"
+ accessibilityHint="Open basic dialog">
+ Close basic dialog
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
new file mode 100644
index 0000000000..198b0582ec
--- /dev/null
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -0,0 +1,66 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a} from '#/alf'
+import {Text} from '#/view/com/Typography'
+import {InputText} from '#/view/com/forms/InputText'
+import {InputDate, utils} from '#/view/com/forms/InputDate'
+import {Logo} from '#/view/icons/Logo'
+
+export function Forms() {
+ return (
+
+ console.log(text)}
+ />
+ console.log(text)}
+ />
+ console.log(text)}
+ icon={Logo}
+ />
+ console.log(text)}
+ icon={Logo}
+ suffix={() => .bksy.social}
+ />
+
+ console.log(date)}
+ accessibilityLabel="Date"
+ accessibilityHint="Enter a date"
+ />
+ console.log(date)}
+ accessibilityLabel="Date"
+ accessibilityHint="Enter a date"
+ />
+
+ )
+}
diff --git a/src/view/screens/Storybook/Palette.tsx b/src/view/screens/Storybook/Palette.tsx
new file mode 100644
index 0000000000..1844f8d900
--- /dev/null
+++ b/src/view/screens/Storybook/Palette.tsx
@@ -0,0 +1,279 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import * as tokens from '#/alf/tokens'
+import {atoms as a} from '#/alf'
+
+export function Palette() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/Spacing.tsx b/src/view/screens/Storybook/Spacing.tsx
new file mode 100644
index 0000000000..c162779340
--- /dev/null
+++ b/src/view/screens/Storybook/Spacing.tsx
@@ -0,0 +1,64 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/view/com/Typography'
+
+export function Spacing() {
+ const t = useTheme()
+ return (
+
+
+
+ 2xs (2px)
+
+
+
+
+ xs (4px)
+
+
+
+
+ sm (8px)
+
+
+
+
+ md (12px)
+
+
+
+
+ lg (16px)
+
+
+
+
+ xl (20px)
+
+
+
+
+ 2xl (24px)
+
+
+
+
+ 3xl (28px)
+
+
+
+
+ 4xl (32px)
+
+
+
+
+ 5xl (40px)
+
+
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/Theming.tsx b/src/view/screens/Storybook/Theming.tsx
new file mode 100644
index 0000000000..68a402683d
--- /dev/null
+++ b/src/view/screens/Storybook/Theming.tsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/view/com/Typography'
+import {Palette} from './Palette'
+
+export function Theming() {
+ const t = useTheme()
+
+ return (
+
+
+
+ theme.atoms.text
+
+
+
+ theme.atoms.text_contrast_600
+
+
+
+
+ theme.atoms.text_contrast_500
+
+
+
+
+ theme.atoms.text_contrast_400
+
+
+
+
+
+
+ theme.atoms.bg
+
+
+ theme.atoms.bg_contrast_25
+
+
+ theme.atoms.bg_contrast_50
+
+
+ theme.atoms.bg_contrast_100
+
+
+ theme.atoms.bg_contrast_200
+
+
+ theme.atoms.bg_contrast_300
+
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/Typography.tsx b/src/view/screens/Storybook/Typography.tsx
new file mode 100644
index 0000000000..470d17f913
--- /dev/null
+++ b/src/view/screens/Storybook/Typography.tsx
@@ -0,0 +1,29 @@
+import React from 'react'
+
+import {atoms as a} from '#/alf'
+import {Text, H1, H2, H3, H4, H5, H6, P} from '#/view/com/Typography'
+
+export function Typography() {
+ return (
+ <>
+ H1 Heading
+ H2 Heading
+ H3 Heading
+ H4 Heading
+ H5 Heading
+ H6 Heading
+ P Paragraph
+
+ atoms.text_5xl
+ atoms.text_4xl
+ atoms.text_3xl
+ atoms.text_2xl
+ atoms.text_xl
+ atoms.text_lg
+ atoms.text_md
+ atoms.text_sm
+ atoms.text_xs
+ atoms.text_2xs
+ >
+ )
+}
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
new file mode 100644
index 0000000000..642eae9f7f
--- /dev/null
+++ b/src/view/screens/Storybook/index.tsx
@@ -0,0 +1,69 @@
+import React from 'react'
+import {View} from 'react-native'
+import {CenteredView, ScrollView} from '#/view/com/util/Views'
+
+import {atoms as a, useTheme, ThemeProvider} from '#/alf'
+import {useSetColorMode} from '#/state/shell'
+import {Button} from '#/view/com/Button'
+
+import {Theming} from './Theming'
+import {Typography} from './Typography'
+import {Spacing} from './Spacing'
+import {Buttons} from './Buttons'
+import {Forms} from './Forms'
+import {Dialogs} from './Dialogs'
+import {Breakpoints} from './Breakpoints'
+
+export function Storybook() {
+ const t = useTheme()
+ const setColorMode = useSetColorMode()
+
+ return (
+
+
+
+
+ setColorMode('system')}>
+ System
+
+ setColorMode('light')}>
+ Light
+
+ setColorMode('dark')}>
+ Dark
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
From a33fa45b7483b5e11f8c74d8663c3b0453307cb4 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Fri, 12 Jan 2024 11:35:47 -0600
Subject: [PATCH 22/68] Button colors
---
src/alf/themes.ts | 8 ++++++--
src/alf/tokens.ts | 1 +
src/view/com/Button.tsx | 26 ++++++++++++++++----------
3 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index 4be901c647..614bab66f1 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -8,6 +8,8 @@ export type ReadonlyPalette = typeof lightPalette
export type Palette = Mutable
export const lightPalette = {
+ white: tokens.color.white,
+ black: tokens.color.black,
primary: tokens.color.blue_500,
positive: tokens.color.green_500,
negative: tokens.color.red_500,
@@ -26,9 +28,11 @@ export const lightPalette = {
} as const
export const darkPalette: Palette = {
+ white: tokens.color.white,
+ black: tokens.color.black,
primary: tokens.color.blue_500,
- positive: tokens.color.green_400,
- negative: tokens.color.red_400,
+ positive: tokens.color.green_500,
+ negative: tokens.color.red_500,
contrast_25: tokens.color.gray_900,
contrast_50: tokens.color.gray_800,
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 564c7fc8e1..34bef53bd7 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -1,5 +1,6 @@
export const color = {
white: '#FFFFFF',
+ black: '#000000',
gray_25: `#FCFCFD`,
gray_50: `#F9FAFB`,
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
index be3c0d67ab..62227b5830 100644
--- a/src/view/com/Button.tsx
+++ b/src/view/com/Button.tsx
@@ -106,23 +106,29 @@ export function Button({
case 'primary': {
if (disabled) {
baseStyles.push({
- backgroundColor: tokens.color.blue_300,
+ backgroundColor: tokens.color.blue_200,
})
} else {
baseStyles.push({
backgroundColor: tokens.color.blue_500,
})
+ hoverStyles.push({
+ backgroundColor: tokens.color.blue_600,
+ })
}
break
}
case 'secondary': {
if (disabled) {
baseStyles.push({
- backgroundColor: tokens.color.gray_100,
+ backgroundColor: tokens.color.gray_200,
})
} else {
baseStyles.push({
- backgroundColor: tokens.color.gray_200,
+ backgroundColor: tokens.color.gray_300,
+ })
+ hoverStyles.push({
+ backgroundColor: tokens.color.gray_400,
})
}
break
@@ -145,16 +151,16 @@ export function Button({
switch (size) {
case 'large': {
baseStyles.push(
- atoms.py_md,
- atoms.px_xl,
- atoms.rounded_md,
+ {paddingVertical: 15},
+ atoms.px_2xl,
+ atoms.rounded_sm,
atoms.gap_sm,
)
break
}
case 'small': {
baseStyles.push(
- atoms.py_sm,
+ {paddingVertical: 9},
atoms.px_md,
atoms.rounded_sm,
atoms.gap_xs,
@@ -239,11 +245,11 @@ export function ButtonText({
case 'secondary': {
if (disabled) {
baseStyles.push({
- color: tokens.color.gray_500,
+ color: tokens.color.gray_400,
})
} else {
baseStyles.push({
- color: tokens.color.gray_700,
+ color: tokens.color.gray_800,
})
}
break
@@ -261,7 +267,7 @@ export function ButtonText({
switch (size) {
case 'small': {
baseStyles.push(
- atoms.text_sm,
+ atoms.text_md,
web({paddingBottom: 1}),
native({marginTop: 2}),
)
From 45c807b83b4849b51f72ab10d93c1c4ef406f7f2 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Fri, 12 Jan 2024 13:38:44 -0600
Subject: [PATCH 23/68] Dim mode
---
src/alf/themes.ts | 76 +++++++++++++++----
src/alf/tokens.ts | 3 -
.../com/forms/InputDate/index.android.tsx | 4 +-
src/view/com/forms/InputDate/index.web.tsx | 4 +-
src/view/com/forms/InputText.tsx | 4 +-
src/view/screens/Storybook/Theming.tsx | 2 +-
src/view/screens/Storybook/index.tsx | 3 +
7 files changed, 71 insertions(+), 25 deletions(-)
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index 614bab66f1..a4fe02fc1c 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -1,15 +1,15 @@
import * as tokens from '#/alf/tokens'
import type {Mutable} from '#/alf/types'
-export type ThemeName = 'light' | 'dark'
+export type ThemeName = 'light' | 'dim' | 'dark'
export type ReadonlyTheme = typeof light
export type Theme = Mutable
export type ReadonlyPalette = typeof lightPalette
export type Palette = Mutable
export const lightPalette = {
- white: tokens.color.white,
- black: tokens.color.black,
+ white: '#FFFFFF',
+ black: '#080B12',
primary: tokens.color.blue_500,
positive: tokens.color.green_500,
negative: tokens.color.red_500,
@@ -28,8 +28,8 @@ export const lightPalette = {
} as const
export const darkPalette: Palette = {
- white: tokens.color.white,
- black: tokens.color.black,
+ white: '#FFFFFF',
+ black: '#080B12',
primary: tokens.color.blue_500,
positive: tokens.color.green_500,
negative: tokens.color.red_500,
@@ -64,10 +64,10 @@ export const light = {
color: lightPalette.contrast_400,
},
text_inverted: {
- color: tokens.color.white,
+ color: lightPalette.white,
},
bg: {
- backgroundColor: tokens.color.white,
+ backgroundColor: lightPalette.white,
},
bg_contrast_25: {
backgroundColor: lightPalette.contrast_25,
@@ -85,10 +85,56 @@ export const light = {
backgroundColor: lightPalette.contrast_300,
},
border: {
- borderColor: tokens.color.gray_200,
+ borderColor: lightPalette.contrast_100,
},
- border_contrast_500: {
- borderColor: tokens.color.gray_500,
+ border_contrast: {
+ borderColor: lightPalette.contrast_400,
+ },
+ },
+}
+
+export const dim: Theme = {
+ name: 'dim',
+ palette: darkPalette,
+ atoms: {
+ text: {
+ color: darkPalette.white,
+ },
+ text_contrast_600: {
+ color: darkPalette.contrast_600,
+ },
+ text_contrast_500: {
+ color: darkPalette.contrast_500,
+ },
+ text_contrast_400: {
+ color: darkPalette.contrast_400,
+ },
+ text_inverted: {
+ color: darkPalette.contrast_900,
+ },
+ bg: {
+ backgroundColor: darkPalette.contrast_25,
+ },
+ bg_contrast_25: {
+ backgroundColor: darkPalette.contrast_50,
+ },
+ bg_contrast_50: {
+ backgroundColor: darkPalette.contrast_100,
+ },
+ bg_contrast_100: {
+ backgroundColor: darkPalette.contrast_200,
+ },
+ bg_contrast_200: {
+ backgroundColor: darkPalette.contrast_300,
+ },
+ bg_contrast_300: {
+ backgroundColor: darkPalette.contrast_400,
+ },
+ border: {
+ borderColor: darkPalette.contrast_50,
+ },
+ border_contrast: {
+ borderColor: darkPalette.contrast_300,
},
},
}
@@ -98,7 +144,7 @@ export const dark: Theme = {
palette: darkPalette,
atoms: {
text: {
- color: tokens.color.white,
+ color: darkPalette.white,
},
text_contrast_600: {
color: darkPalette.contrast_600,
@@ -113,7 +159,7 @@ export const dark: Theme = {
color: tokens.color.gray_900,
},
bg: {
- backgroundColor: tokens.color.gray_900,
+ backgroundColor: darkPalette.black,
},
bg_contrast_25: {
backgroundColor: darkPalette.contrast_25,
@@ -131,10 +177,10 @@ export const dark: Theme = {
backgroundColor: darkPalette.contrast_300,
},
border: {
- borderColor: tokens.color.gray_800,
+ borderColor: darkPalette.contrast_50,
},
- border_contrast_500: {
- borderColor: tokens.color.gray_500,
+ border_contrast: {
+ borderColor: darkPalette.contrast_300,
},
},
}
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 34bef53bd7..35189e7b88 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -1,7 +1,4 @@
export const color = {
- white: '#FFFFFF',
- black: '#000000',
-
gray_25: `#FCFCFD`,
gray_50: `#F9FAFB`,
gray_100: `#F3F4F6`,
diff --git a/src/view/com/forms/InputDate/index.android.tsx b/src/view/com/forms/InputDate/index.android.tsx
index 13b1ac25d2..9d80ba38a5 100644
--- a/src/view/com/forms/InputDate/index.android.tsx
+++ b/src/view/com/forms/InputDate/index.android.tsx
@@ -52,7 +52,7 @@ export function InputDate({
if (focused) {
input.push({
- borderColor: t.atoms.border_contrast_500.borderColor,
+ borderColor: t.atoms.border_contrast.borderColor,
})
if (hasError) {
@@ -130,7 +130,7 @@ export function InputDate({
]}>
-
+
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index 642eae9f7f..3724154c00 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -52,6 +52,9 @@ export function Storybook() {
+
+
+
From ceef55041a0517b09cb05ddb0a5a5c57ea32135c Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Sat, 13 Jan 2024 12:11:25 -0600
Subject: [PATCH 24/68] Reorg
---
src/App.native.tsx | 5 +-
src/App.web.tsx | 5 +-
src/alf/themes.ts | 92 +++-
src/alf/tokens.ts | 3 +
src/components/Button.tsx | 434 ++++++++++++++++++
.../com => components}/Dialog/context.ts | 2 +-
src/{view/com => components}/Dialog/index.tsx | 12 +-
.../com => components}/Dialog/index.web.tsx | 14 +-
src/{view/com => components}/Dialog/types.ts | 0
src/{view/com => components}/Link.tsx | 6 +-
src/{view/com => components}/Portal.tsx | 0
src/{view/com => components}/Prompt.tsx | 14 +-
src/{view/com => components}/Typography.tsx | 0
.../forms/InputDate/index.android.tsx | 10 +-
.../forms/InputDate/index.tsx | 8 +-
.../forms/InputDate/index.web.tsx | 10 +-
.../forms/InputDate/types.ts | 2 +-
.../forms/InputDate/utils.ts | 0
.../com => components}/forms/InputGroup.tsx | 0
.../com => components}/forms/InputText.tsx | 6 +-
src/{view/com => components}/forms/types.ts | 0
.../hooks/useInteractionState.ts | 0
src/view/com/Button.tsx | 297 ------------
src/view/screens/Storybook/Breakpoints.tsx | 2 +-
src/view/screens/Storybook/Buttons.tsx | 142 ++----
src/view/screens/Storybook/Dialogs.tsx | 17 +-
src/view/screens/Storybook/Forms.tsx | 6 +-
src/view/screens/Storybook/Links.tsx | 59 +++
src/view/screens/Storybook/Spacing.tsx | 86 ++--
src/view/screens/Storybook/Theming.tsx | 2 +-
src/view/screens/Storybook/Typography.tsx | 7 +-
src/view/screens/Storybook/index.tsx | 15 +-
32 files changed, 730 insertions(+), 526 deletions(-)
create mode 100644 src/components/Button.tsx
rename src/{view/com => components}/Dialog/context.ts (84%)
rename src/{view/com => components}/Dialog/index.tsx (91%)
rename src/{view/com => components}/Dialog/index.web.tsx (92%)
rename src/{view/com => components}/Dialog/types.ts (100%)
rename src/{view/com => components}/Link.tsx (96%)
rename src/{view/com => components}/Portal.tsx (100%)
rename src/{view/com => components}/Prompt.tsx (91%)
rename src/{view/com => components}/Typography.tsx (100%)
rename src/{view/com => components}/forms/InputDate/index.android.tsx (92%)
rename src/{view/com => components}/forms/InputDate/index.tsx (88%)
rename src/{view/com => components}/forms/InputDate/index.web.tsx (91%)
rename src/{view/com => components}/forms/InputDate/types.ts (78%)
rename src/{view/com => components}/forms/InputDate/utils.ts (100%)
rename src/{view/com => components}/forms/InputGroup.tsx (100%)
rename src/{view/com => components}/forms/InputText.tsx (96%)
rename src/{view/com => components}/forms/types.ts (100%)
rename src/{view/com/util => components}/hooks/useInteractionState.ts (100%)
delete mode 100644 src/view/com/Button.tsx
create mode 100644 src/view/screens/Storybook/Links.tsx
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 39485ee24f..7042c6e098 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -41,7 +41,10 @@ import {
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
import {Splash} from '#/Splash'
-import {Provider as PortalProvider, Outlet as PortalOutlet} from '#/view/com/Portal'
+import {
+ Provider as PortalProvider,
+ Outlet as PortalOutlet,
+} from '#/components/Portal'
SplashScreen.preventAutoHideAsync()
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 5760b0d4a2..546f5614df 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -30,7 +30,10 @@ import {
} from 'state/session'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
-import {Provider as PortalProvider, Outlet as PortalOutlet} from '#/view/com/Portal'
+import {
+ Provider as PortalProvider,
+ Outlet as PortalOutlet,
+} from '#/components/Portal'
function InnerApp() {
const {isInitialLoad, currentAccount} = useSession()
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index a4fe02fc1c..103693b0b3 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -8,11 +8,8 @@ export type ReadonlyPalette = typeof lightPalette
export type Palette = Mutable
export const lightPalette = {
- white: '#FFFFFF',
- black: '#080B12',
- primary: tokens.color.blue_500,
- positive: tokens.color.green_500,
- negative: tokens.color.red_500,
+ white: tokens.color.white,
+ black: tokens.color.black,
contrast_25: tokens.color.gray_25,
contrast_50: tokens.color.gray_50,
@@ -25,14 +22,47 @@ export const lightPalette = {
contrast_700: tokens.color.gray_700,
contrast_800: tokens.color.gray_800,
contrast_900: tokens.color.gray_900,
+
+ primary_25: tokens.color.blue_25,
+ primary_50: tokens.color.blue_50,
+ primary_100: tokens.color.blue_100,
+ primary_200: tokens.color.blue_200,
+ primary_300: tokens.color.blue_300,
+ primary_400: tokens.color.blue_400,
+ primary_500: tokens.color.blue_500,
+ primary_600: tokens.color.blue_600,
+ primary_700: tokens.color.blue_700,
+ primary_800: tokens.color.blue_800,
+ primary_900: tokens.color.blue_900,
+
+ positive_25: tokens.color.green_25,
+ positive_50: tokens.color.green_50,
+ positive_100: tokens.color.green_100,
+ positive_200: tokens.color.green_200,
+ positive_300: tokens.color.green_300,
+ positive_400: tokens.color.green_400,
+ positive_500: tokens.color.green_500,
+ positive_600: tokens.color.green_600,
+ positive_700: tokens.color.green_700,
+ positive_800: tokens.color.green_800,
+ positive_900: tokens.color.green_900,
+
+ negative_25: tokens.color.red_25,
+ negative_50: tokens.color.red_50,
+ negative_100: tokens.color.red_100,
+ negative_200: tokens.color.red_200,
+ negative_300: tokens.color.red_300,
+ negative_400: tokens.color.red_400,
+ negative_500: tokens.color.red_500,
+ negative_600: tokens.color.red_600,
+ negative_700: tokens.color.red_700,
+ negative_800: tokens.color.red_800,
+ negative_900: tokens.color.red_900,
} as const
export const darkPalette: Palette = {
- white: '#FFFFFF',
- black: '#080B12',
- primary: tokens.color.blue_500,
- positive: tokens.color.green_500,
- negative: tokens.color.red_500,
+ white: tokens.color.white,
+ black: tokens.color.black,
contrast_25: tokens.color.gray_900,
contrast_50: tokens.color.gray_800,
@@ -45,6 +75,42 @@ export const darkPalette: Palette = {
contrast_700: tokens.color.gray_100,
contrast_800: tokens.color.gray_50,
contrast_900: tokens.color.gray_25,
+
+ primary_25: tokens.color.blue_25,
+ primary_50: tokens.color.blue_50,
+ primary_100: tokens.color.blue_100,
+ primary_200: tokens.color.blue_200,
+ primary_300: tokens.color.blue_300,
+ primary_400: tokens.color.blue_400,
+ primary_500: tokens.color.blue_500,
+ primary_600: tokens.color.blue_600,
+ primary_700: tokens.color.blue_700,
+ primary_800: tokens.color.blue_800,
+ primary_900: tokens.color.blue_900,
+
+ positive_25: tokens.color.green_25,
+ positive_50: tokens.color.green_50,
+ positive_100: tokens.color.green_100,
+ positive_200: tokens.color.green_200,
+ positive_300: tokens.color.green_300,
+ positive_400: tokens.color.green_400,
+ positive_500: tokens.color.green_500,
+ positive_600: tokens.color.green_600,
+ positive_700: tokens.color.green_700,
+ positive_800: tokens.color.green_800,
+ positive_900: tokens.color.green_900,
+
+ negative_25: tokens.color.red_25,
+ negative_50: tokens.color.red_50,
+ negative_100: tokens.color.red_100,
+ negative_200: tokens.color.red_200,
+ negative_300: tokens.color.red_300,
+ negative_400: tokens.color.red_400,
+ negative_500: tokens.color.red_500,
+ negative_600: tokens.color.red_600,
+ negative_700: tokens.color.red_700,
+ negative_800: tokens.color.red_800,
+ negative_900: tokens.color.red_900,
} as const
export const light = {
@@ -52,7 +118,7 @@ export const light = {
palette: lightPalette,
atoms: {
text: {
- color: tokens.color.gray_900,
+ color: lightPalette.black,
},
text_contrast_600: {
color: lightPalette.contrast_600,
@@ -110,7 +176,7 @@ export const dim: Theme = {
color: darkPalette.contrast_400,
},
text_inverted: {
- color: darkPalette.contrast_900,
+ color: darkPalette.black,
},
bg: {
backgroundColor: darkPalette.contrast_25,
@@ -156,7 +222,7 @@ export const dark: Theme = {
color: darkPalette.contrast_400,
},
text_inverted: {
- color: tokens.color.gray_900,
+ color: darkPalette.black,
},
bg: {
backgroundColor: darkPalette.black,
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 35189e7b88..db402840f8 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -1,4 +1,7 @@
export const color = {
+ white: '#FFFFFF',
+ black: '#080B12',
+
gray_25: `#FCFCFD`,
gray_50: `#F9FAFB`,
gray_100: `#F3F4F6`,
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
new file mode 100644
index 0000000000..9d36892a55
--- /dev/null
+++ b/src/components/Button.tsx
@@ -0,0 +1,434 @@
+import React from 'react'
+import {
+ Pressable,
+ Text,
+ PressableProps,
+ TextProps,
+ ViewStyle,
+ AccessibilityProps,
+} from 'react-native'
+
+import {useTheme, atoms, tokens, web, native} from '#/alf'
+
+export type ButtonVariant = 'solid' | 'outline' | 'ghost'
+export type ButtonColor = 'primary' | 'secondary' | 'negative'
+export type ButtonSize = 'small' | 'large'
+export type VariantProps = {
+ /**
+ * The style variation of the button
+ */
+ variant?: ButtonVariant
+ /**
+ * The color of the button
+ */
+ color?: ButtonColor
+ /**
+ * The size of the button
+ */
+ size?: ButtonSize
+}
+
+export type ButtonProps = Omit<
+ PressableProps,
+ 'children' | 'style' | 'accessibilityLabel' | 'accessibilityHint'
+> &
+ VariantProps & {
+ children:
+ | ((props: {
+ state: {
+ pressed: boolean
+ hovered: boolean
+ focused: boolean
+ }
+ props: VariantProps & {
+ disabled?: boolean
+ }
+ }) => React.ReactNode)
+ | React.ReactNode
+ | string
+ accessibilityLabel: Required['accessibilityLabel']
+ accessibilityHint: Required['accessibilityHint']
+ }
+export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
+
+export function Button({
+ children,
+ variant,
+ color,
+ size,
+ accessibilityLabel,
+ accessibilityHint,
+ disabled = false,
+ ...rest
+}: ButtonProps) {
+ const t = useTheme()
+ const [state, setState] = React.useState({
+ pressed: false,
+ hovered: false,
+ focused: false,
+ })
+
+ const onPressIn = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ pressed: true,
+ }))
+ }, [setState])
+ const onPressOut = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ pressed: false,
+ }))
+ }, [setState])
+ const onHoverIn = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ hovered: true,
+ }))
+ }, [setState])
+ const onHoverOut = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ hovered: false,
+ }))
+ }, [setState])
+ const onFocus = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ focused: true,
+ }))
+ }, [setState])
+ const onBlur = React.useCallback(() => {
+ setState(s => ({
+ ...s,
+ focused: false,
+ }))
+ }, [setState])
+
+ const {baseStyles, hoverStyles} = React.useMemo(() => {
+ const baseStyles: ViewStyle[] = []
+ const hoverStyles: ViewStyle[] = []
+ const light = t.name === 'light'
+
+ if (color === 'primary') {
+ if (variant === 'solid') {
+ if (!disabled) {
+ baseStyles.push({
+ backgroundColor: t.palette.primary_500,
+ })
+ hoverStyles.push({
+ backgroundColor: t.palette.primary_600,
+ })
+ } else {
+ baseStyles.push({
+ backgroundColor: t.palette.primary_700,
+ })
+ }
+ } else if (variant === 'outline') {
+ baseStyles.push(atoms.border, t.atoms.bg, {
+ borderWidth: 1,
+ })
+
+ if (!disabled) {
+ baseStyles.push(atoms.border, {
+ borderColor: tokens.color.blue_500,
+ })
+ hoverStyles.push(atoms.border, {
+ backgroundColor: light
+ ? t.palette.primary_100
+ : t.palette.primary_900,
+ })
+ } else {
+ baseStyles.push(atoms.border, {
+ borderColor: light ? tokens.color.blue_200 : tokens.color.blue_900,
+ })
+ }
+ } else if (variant === 'ghost') {
+ if (!disabled) {
+ baseStyles.push(t.atoms.bg)
+ hoverStyles.push({
+ backgroundColor: light
+ ? t.palette.primary_100
+ : t.palette.primary_900,
+ })
+ }
+ }
+ } else if (color === 'secondary') {
+ if (variant === 'solid') {
+ if (!disabled) {
+ baseStyles.push({
+ backgroundColor: light
+ ? tokens.color.gray_200
+ : tokens.color.gray_800,
+ })
+ hoverStyles.push({
+ backgroundColor: light
+ ? tokens.color.gray_300
+ : tokens.color.gray_900,
+ })
+ } else {
+ baseStyles.push({
+ backgroundColor: light
+ ? tokens.color.gray_300
+ : tokens.color.gray_900,
+ })
+ }
+ } else if (variant === 'outline') {
+ baseStyles.push(atoms.border, t.atoms.bg, {
+ borderWidth: 1,
+ })
+
+ if (!disabled) {
+ baseStyles.push(atoms.border, {
+ borderColor: light ? tokens.color.gray_500 : tokens.color.gray_500,
+ })
+ hoverStyles.push(atoms.border, t.atoms.bg_contrast_50)
+ } else {
+ baseStyles.push(atoms.border, {
+ borderColor: light ? tokens.color.gray_200 : tokens.color.gray_800,
+ })
+ }
+ } else if (variant === 'ghost') {
+ if (!disabled) {
+ baseStyles.push(t.atoms.bg)
+ hoverStyles.push({
+ backgroundColor: light
+ ? tokens.color.gray_100
+ : tokens.color.gray_800,
+ })
+ }
+ }
+ } else if (color === 'negative') {
+ if (variant === 'solid') {
+ if (!disabled) {
+ baseStyles.push({
+ backgroundColor: t.palette.negative_500,
+ })
+ hoverStyles.push({
+ backgroundColor: t.palette.negative_600,
+ })
+ } else {
+ baseStyles.push({
+ backgroundColor: t.palette.negative_700,
+ })
+ }
+ } else if (variant === 'outline') {
+ baseStyles.push(atoms.border, t.atoms.bg, {
+ borderWidth: 1,
+ })
+
+ if (!disabled) {
+ baseStyles.push(atoms.border, {
+ borderColor: t.palette.negative_600,
+ })
+ hoverStyles.push(atoms.border, {
+ backgroundColor: light ? t.palette.negative_50 : '#2D0614', // darker red
+ })
+ } else {
+ baseStyles.push(atoms.border, {
+ borderColor: light
+ ? t.palette.negative_200
+ : t.palette.negative_900,
+ })
+ }
+ } else if (variant === 'ghost') {
+ if (!disabled) {
+ baseStyles.push(t.atoms.bg)
+ hoverStyles.push({
+ backgroundColor: light ? t.palette.negative_50 : '#2D0614', // darker red
+ })
+ }
+ }
+ }
+
+ if (size === 'large') {
+ baseStyles.push(
+ {paddingVertical: 15},
+ atoms.px_2xl,
+ atoms.rounded_sm,
+ atoms.gap_sm,
+ )
+ } else if (size === 'small') {
+ baseStyles.push(
+ {paddingVertical: 9},
+ atoms.px_md,
+ atoms.rounded_sm,
+ atoms.gap_xs,
+ )
+ }
+
+ return {
+ baseStyles,
+ hoverStyles,
+ }
+ }, [t, variant, color, size, disabled])
+
+ const childProps = React.useMemo(
+ () => ({
+ state,
+ props: {
+ variant,
+ color,
+ size,
+ disabled: disabled || false,
+ },
+ }),
+ [state, variant, color, size, disabled],
+ )
+
+ return (
+
+ {typeof children === 'string' ? (
+
+ {children}
+
+ ) : typeof children === 'function' ? (
+ children(childProps)
+ ) : (
+ children
+ )}
+
+ )
+}
+
+export function ButtonText({
+ children,
+ style,
+ variant,
+ color,
+ size,
+ disabled,
+ ...rest
+}: ButtonTextProps) {
+ const t = useTheme()
+
+ const textStyles = React.useMemo(() => {
+ const baseStyles = []
+ const light = t.name === 'light'
+
+ if (color === 'primary') {
+ if (variant === 'solid') {
+ if (!disabled) {
+ baseStyles.push({color: t.palette.white})
+ } else {
+ baseStyles.push({color: t.palette.white, opacity: 0.5})
+ }
+ } else if (variant === 'outline') {
+ if (!disabled) {
+ baseStyles.push({
+ color: light ? t.palette.primary_600 : t.palette.primary_500,
+ })
+ } else {
+ baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
+ }
+ } else if (variant === 'ghost') {
+ if (!disabled) {
+ baseStyles.push({color: t.palette.primary_600})
+ } else {
+ baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
+ }
+ }
+ } else if (color === 'secondary') {
+ if (variant === 'solid') {
+ if (!disabled) {
+ baseStyles.push({
+ color: light ? tokens.color.gray_700 : tokens.color.gray_100,
+ })
+ } else {
+ baseStyles.push({
+ color: light ? tokens.color.gray_400 : tokens.color.gray_700,
+ })
+ }
+ } else if (variant === 'outline') {
+ if (!disabled) {
+ baseStyles.push({
+ color: light ? tokens.color.gray_600 : tokens.color.gray_300,
+ })
+ } else {
+ baseStyles.push({
+ color: light ? tokens.color.gray_400 : tokens.color.gray_700,
+ })
+ }
+ } else if (variant === 'ghost') {
+ if (!disabled) {
+ baseStyles.push({
+ color: light ? tokens.color.gray_600 : tokens.color.gray_300,
+ })
+ } else {
+ baseStyles.push({
+ color: light ? tokens.color.gray_400 : tokens.color.gray_600,
+ })
+ }
+ }
+ } else if (color === 'negative') {
+ if (variant === 'solid') {
+ if (!disabled) {
+ baseStyles.push({color: t.palette.white})
+ } else {
+ baseStyles.push({color: t.palette.white, opacity: 0.5})
+ }
+ } else if (variant === 'outline') {
+ if (!disabled) {
+ baseStyles.push({color: t.palette.negative_500})
+ } else {
+ baseStyles.push({color: t.palette.negative_500, opacity: 0.5})
+ }
+ } else if (variant === 'ghost') {
+ if (!disabled) {
+ baseStyles.push({color: t.palette.negative_500})
+ } else {
+ baseStyles.push({color: t.palette.negative_500, opacity: 0.5})
+ }
+ }
+ }
+
+ if (size === 'large') {
+ baseStyles.push(
+ atoms.text_md,
+ web({paddingBottom: 1}),
+ native({marginTop: 2}),
+ )
+ } else {
+ baseStyles.push(
+ atoms.text_md,
+ web({paddingBottom: 1}),
+ native({marginTop: 2}),
+ )
+ }
+
+ return baseStyles
+ }, [t, variant, color, size, disabled])
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/view/com/Dialog/context.ts b/src/components/Dialog/context.ts
similarity index 84%
rename from src/view/com/Dialog/context.ts
rename to src/components/Dialog/context.ts
index 2330969933..76473aa243 100644
--- a/src/view/com/Dialog/context.ts
+++ b/src/components/Dialog/context.ts
@@ -1,5 +1,5 @@
import React from 'react'
-import {DialogContextProps, DialogControlProps} from '#/view/com/Dialog/types'
+import {DialogContextProps, DialogControlProps} from '#/components/Dialog/types'
export const Context = React.createContext({
close: () => {},
diff --git a/src/view/com/Dialog/index.tsx b/src/components/Dialog/index.tsx
similarity index 91%
rename from src/view/com/Dialog/index.tsx
rename to src/components/Dialog/index.tsx
index 5a832e6fe4..4bc1338e0b 100644
--- a/src/view/com/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -3,17 +3,17 @@ import {View, Dimensions} from 'react-native'
import BottomSheet, {BottomSheetBackdrop} from '@gorhom/bottom-sheet'
import {useTheme, atoms as a} from '#/alf'
-import {Portal} from '#/view/com/Portal'
+import {Portal} from '#/components/Portal'
import {
DialogOuterProps,
DialogControlProps,
DialogInnerProps,
-} from '#/view/com/Dialog/types'
-import {Context} from '#/view/com/Dialog/context'
+} from '#/components/Dialog/types'
+import {Context} from '#/components/Dialog/context'
-export {useDialogControl, useDialogContext} from '#/view/com/Dialog/context'
-export * from '#/view/com/Dialog/types'
+export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
+export * from '#/components/Dialog/types'
export function Outer({
children,
@@ -62,7 +62,7 @@ export function Outer({
{...props}
/>
)}
- handleIndicatorStyle={{backgroundColor: t.palette.primary}}
+ handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}}
onClose={onClose}>
diff --git a/src/view/com/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
similarity index 92%
rename from src/view/com/Dialog/index.web.tsx
rename to src/components/Dialog/index.web.tsx
index 37d4cebc85..8efccd55d7 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -4,15 +4,15 @@ import {FocusScope} from '@tamagui/focus-scope'
import Animated, {FadeInDown, FadeIn} from 'react-native-reanimated'
import {useTheme, atoms as a, useBreakpoints, web} from '#/alf'
-import {Text} from '#/view/com/Typography'
-import {Portal} from '#/view/com/Portal'
-import {Button} from '#/view/com/Button'
+import {Text} from '#/components/Typography'
+import {Portal} from '#/components/Portal'
+import {Button} from '#/components/Button'
-import {DialogOuterProps, DialogInnerProps} from '#/view/com/Dialog/types'
-import {Context, useDialogContext} from '#/view/com/Dialog/context'
+import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
+import {Context, useDialogContext} from '#/components/Dialog/context'
-export {useDialogControl, useDialogContext} from '#/view/com/Dialog/context'
-export * from '#/view/com/Dialog/types'
+export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
+export * from '#/components/Dialog/types'
const stopPropagation = (e: any) => e.stopPropagation()
diff --git a/src/view/com/Dialog/types.ts b/src/components/Dialog/types.ts
similarity index 100%
rename from src/view/com/Dialog/types.ts
rename to src/components/Dialog/types.ts
diff --git a/src/view/com/Link.tsx b/src/components/Link.tsx
similarity index 96%
rename from src/view/com/Link.tsx
rename to src/components/Link.tsx
index fd6bf5ca3c..8a2e825479 100644
--- a/src/view/com/Link.tsx
+++ b/src/components/Link.tsx
@@ -15,7 +15,7 @@ import {sanitizeUrl} from '@braintree/sanitize-url'
import {isWeb} from '#/platform/detection'
import {useTheme, web} from '#/alf'
-import {Button, ButtonProps} from '#/view/com/Button'
+import {Button, ButtonProps} from '#/components/Button'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {
convertBskyAppUrlIfNeeded,
@@ -157,10 +157,10 @@ export function Link({
{children as string}
diff --git a/src/view/com/Portal.tsx b/src/components/Portal.tsx
similarity index 100%
rename from src/view/com/Portal.tsx
rename to src/components/Portal.tsx
diff --git a/src/view/com/Prompt.tsx b/src/components/Prompt.tsx
similarity index 91%
rename from src/view/com/Prompt.tsx
rename to src/components/Prompt.tsx
index baf7625828..b5841e7bb9 100644
--- a/src/view/com/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -3,12 +3,12 @@ import {View, PressableProps, LayoutChangeEvent} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
-import {H2, P} from '#/view/com/Typography'
-import {Button} from '#/view/com/Button'
+import {H2, P} from '#/components/Typography'
+import {Button} from '#/components/Button'
-import * as Dialog from '#/view/com/Dialog'
+import * as Dialog from '#/components/Dialog'
-export {useDialogControl as usePromptControl} from '#/view/com/Dialog'
+export {useDialogControl as usePromptControl} from '#/components/Dialog'
const Context = React.createContext<{
titleId: string
@@ -102,7 +102,8 @@ export function Cancel({
const {close} = Dialog.useDialogContext()
return (
& {
diff --git a/src/view/com/forms/types.ts b/src/components/forms/types.ts
similarity index 100%
rename from src/view/com/forms/types.ts
rename to src/components/forms/types.ts
diff --git a/src/view/com/util/hooks/useInteractionState.ts b/src/components/hooks/useInteractionState.ts
similarity index 100%
rename from src/view/com/util/hooks/useInteractionState.ts
rename to src/components/hooks/useInteractionState.ts
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
deleted file mode 100644
index 62227b5830..0000000000
--- a/src/view/com/Button.tsx
+++ /dev/null
@@ -1,297 +0,0 @@
-import React from 'react'
-import {
- Pressable,
- Text,
- PressableProps,
- TextProps,
- ViewStyle,
- AccessibilityProps,
-} from 'react-native'
-
-import {useTheme, atoms, tokens, web, native} from '#/alf'
-
-export type ButtonType = 'primary' | 'secondary' | 'negative'
-export type ButtonSize = 'small' | 'large'
-export type VariantProps = {
- /**
- * The presentation styles of the button
- */
- type?: ButtonType
- /**
- * The size of the button
- */
- size?: ButtonSize
-}
-
-export type ButtonProps = Omit<
- PressableProps,
- 'children' | 'style' | 'accessibilityLabel' | 'accessibilityHint'
-> &
- VariantProps & {
- children:
- | ((props: {
- state: {
- pressed: boolean
- hovered: boolean
- focused: boolean
- }
- props: VariantProps & {
- disabled?: boolean
- }
- }) => React.ReactNode)
- | React.ReactNode
- | string
- accessibilityLabel: Required['accessibilityLabel']
- accessibilityHint: Required['accessibilityHint']
- }
-export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
-
-export function Button({
- children,
- type,
- size,
- accessibilityLabel,
- accessibilityHint,
- disabled = false,
- ...rest
-}: ButtonProps) {
- const [state, setState] = React.useState({
- pressed: false,
- hovered: false,
- focused: false,
- })
-
- const onPressIn = React.useCallback(() => {
- setState(s => ({
- ...s,
- pressed: true,
- }))
- }, [setState])
- const onPressOut = React.useCallback(() => {
- setState(s => ({
- ...s,
- pressed: false,
- }))
- }, [setState])
- const onHoverIn = React.useCallback(() => {
- setState(s => ({
- ...s,
- hovered: true,
- }))
- }, [setState])
- const onHoverOut = React.useCallback(() => {
- setState(s => ({
- ...s,
- hovered: false,
- }))
- }, [setState])
- const onFocus = React.useCallback(() => {
- setState(s => ({
- ...s,
- focused: true,
- }))
- }, [setState])
- const onBlur = React.useCallback(() => {
- setState(s => ({
- ...s,
- focused: false,
- }))
- }, [setState])
-
- const {baseStyles, hoverStyles} = React.useMemo(() => {
- const baseStyles: ViewStyle[] = []
- const hoverStyles: ViewStyle[] = []
-
- switch (type) {
- case 'primary': {
- if (disabled) {
- baseStyles.push({
- backgroundColor: tokens.color.blue_200,
- })
- } else {
- baseStyles.push({
- backgroundColor: tokens.color.blue_500,
- })
- hoverStyles.push({
- backgroundColor: tokens.color.blue_600,
- })
- }
- break
- }
- case 'secondary': {
- if (disabled) {
- baseStyles.push({
- backgroundColor: tokens.color.gray_200,
- })
- } else {
- baseStyles.push({
- backgroundColor: tokens.color.gray_300,
- })
- hoverStyles.push({
- backgroundColor: tokens.color.gray_400,
- })
- }
- break
- }
- case 'negative': {
- if (disabled) {
- baseStyles.push({
- backgroundColor: tokens.color.red_400,
- })
- } else {
- baseStyles.push({
- backgroundColor: tokens.color.red_500,
- })
- }
- break
- }
- default:
- }
-
- switch (size) {
- case 'large': {
- baseStyles.push(
- {paddingVertical: 15},
- atoms.px_2xl,
- atoms.rounded_sm,
- atoms.gap_sm,
- )
- break
- }
- case 'small': {
- baseStyles.push(
- {paddingVertical: 9},
- atoms.px_md,
- atoms.rounded_sm,
- atoms.gap_xs,
- )
- break
- }
- default:
- }
-
- return {
- baseStyles,
- hoverStyles,
- }
- }, [type, size, disabled])
-
- const childProps = React.useMemo(
- () => ({
- state,
- props: {
- type,
- size,
- disabled: disabled || false,
- },
- }),
- [state, type, size, disabled],
- )
-
- return (
-
- {typeof children === 'string' ? (
-
- {children}
-
- ) : typeof children === 'function' ? (
- children(childProps)
- ) : (
- children
- )}
-
- )
-}
-
-export function ButtonText({
- children,
- style,
- type,
- size,
- disabled,
- ...rest
-}: ButtonTextProps) {
- const t = useTheme()
-
- const textStyles = React.useMemo(() => {
- const baseStyles = []
-
- switch (type) {
- case 'primary': {
- baseStyles.push({color: tokens.color.white})
- break
- }
- case 'secondary': {
- if (disabled) {
- baseStyles.push({
- color: tokens.color.gray_400,
- })
- } else {
- baseStyles.push({
- color: tokens.color.gray_800,
- })
- }
- break
- }
- case 'negative': {
- baseStyles.push({
- color: tokens.color.white,
- })
- break
- }
- default:
- baseStyles.push(t.atoms.text)
- }
-
- switch (size) {
- case 'small': {
- baseStyles.push(
- atoms.text_md,
- web({paddingBottom: 1}),
- native({marginTop: 2}),
- )
- break
- }
- case 'large': {
- baseStyles.push(
- atoms.text_md,
- web({paddingBottom: 1}),
- native({marginTop: 2}),
- )
- break
- }
- default:
- }
-
- return baseStyles
- }, [t, type, size, disabled])
-
- return (
-
- {children}
-
- )
-}
diff --git a/src/view/screens/Storybook/Breakpoints.tsx b/src/view/screens/Storybook/Breakpoints.tsx
index 34a9dbfd6e..1b846d517c 100644
--- a/src/view/screens/Storybook/Breakpoints.tsx
+++ b/src/view/screens/Storybook/Breakpoints.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme, useBreakpoints} from '#/alf'
-import {Text, H3} from '#/view/com/Typography'
+import {Text, H3} from '#/components/Typography'
export function Breakpoints() {
const t = useTheme()
diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx
index 41993cc29b..b1f3979981 100644
--- a/src/view/screens/Storybook/Buttons.tsx
+++ b/src/view/screens/Storybook/Buttons.tsx
@@ -1,120 +1,42 @@
import React from 'react'
import {View} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {atoms as a, useTheme} from '#/alf'
-import {Button, ButtonText} from '#/view/com/Button'
-import {Link} from '#/view/com/Link'
-import {Text, H3} from '#/view/com/Typography'
+import {atoms as a} from '#/alf'
+import {Button, ButtonVariant, ButtonColor} from '#/components/Button'
+import {H1} from '#/components/Typography'
export function Buttons() {
- const t = useTheme()
-
return (
-
-
- Unstyled button
-
-
-
- {({state}) => (
-
- Entirely custom button, state: {JSON.stringify(state)}
+
+ Buttons
+
+
+ {['primary', 'secondary', 'negative'].map(color => (
+
+ {['solid', 'outline', 'ghost'].map(variant => (
+
+
+ Button
+
+
+ Button
+
+
+ ))}
- )}
-
-
-
- Default button
-
-
-
- Default button (disabled)
-
-
-
- {({props}) => (
- <>
-
- Default with an icon
- >
- )}
-
-
-
- Small button
-
-
-
- Small button (disabled)
-
-
-
- External
-
-
- External with custom children
-
-
- https://blueskyweb.xyz
-
-
- Internal
-
-
-
- {({props}) => Link as a button}
-
+ ))}
+
)
}
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index e0fcf0f811..b4a779e9d0 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -2,10 +2,10 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
-import {Button} from '#/view/com/Button'
-import {H3, P} from '#/view/com/Typography'
-import * as Dialog from '#/view/com/Dialog'
-import * as Prompt from '#/view/com/Prompt'
+import {Button} from '#/components/Button'
+import {H3, P} from '#/components/Typography'
+import * as Dialog from '#/components/Dialog'
+import * as Prompt from '#/components/Prompt'
export function Dialogs() {
const control = Dialog.useDialogControl()
@@ -14,7 +14,8 @@ export function Dialogs() {
return (
<>
control.open()}
accessibilityLabel="Open basic dialog"
@@ -23,7 +24,8 @@ export function Dialogs() {
prompt.open()}
accessibilityLabel="Open prompt"
@@ -53,7 +55,8 @@ export function Dialogs() {
Description
control.close()}
accessibilityLabel="Open basic dialog"
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 198b0582ec..85c8701d79 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -2,9 +2,9 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
-import {Text} from '#/view/com/Typography'
-import {InputText} from '#/view/com/forms/InputText'
-import {InputDate, utils} from '#/view/com/forms/InputDate'
+import {Text} from '#/components/Typography'
+import {InputText} from '#/components/forms/InputText'
+import {InputDate, utils} from '#/components/forms/InputDate'
import {Logo} from '#/view/icons/Logo'
export function Forms() {
diff --git a/src/view/screens/Storybook/Links.tsx b/src/view/screens/Storybook/Links.tsx
new file mode 100644
index 0000000000..b5c183425a
--- /dev/null
+++ b/src/view/screens/Storybook/Links.tsx
@@ -0,0 +1,59 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a} from '#/alf'
+import {ButtonText} from '#/components/Button'
+import {Link} from '#/components/Link'
+import {H1, H3} from '#/components/Typography'
+
+export function Links() {
+ return (
+
+ Links
+
+
+
+ External
+
+
+ External with custom children
+
+
+ https://blueskyweb.xyz
+
+
+ Internal
+
+
+
+ {({props}) => Link as a button}
+
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/Spacing.tsx b/src/view/screens/Storybook/Spacing.tsx
index c162779340..d7faf93a80 100644
--- a/src/view/screens/Storybook/Spacing.tsx
+++ b/src/view/screens/Storybook/Spacing.tsx
@@ -2,62 +2,62 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
-import {Text} from '#/view/com/Typography'
+import {Text, H1} from '#/components/Typography'
export function Spacing() {
const t = useTheme()
return (
-
-
-
- 2xs (2px)
-
-
+
+ Spacing
-
- xs (4px)
-
-
+
+ 2xs (2px)
+
+
-
- sm (8px)
-
-
+
+ xs (4px)
+
+
-
- md (12px)
-
-
+
+ sm (8px)
+
+
+
+
+ md (12px)
+
+
-
- lg (16px)
-
-
+
+ lg (16px)
+
+
-
- xl (20px)
-
-
+
+ xl (20px)
+
+
-
- 2xl (24px)
-
-
+
+ 2xl (24px)
+
+
-
- 3xl (28px)
-
-
+
+ 3xl (28px)
+
+
-
- 4xl (32px)
-
-
+
+ 4xl (32px)
+
+
-
- 5xl (40px)
-
-
+
+ 5xl (40px)
+
)
diff --git a/src/view/screens/Storybook/Theming.tsx b/src/view/screens/Storybook/Theming.tsx
index 08b2284224..a054434734 100644
--- a/src/view/screens/Storybook/Theming.tsx
+++ b/src/view/screens/Storybook/Theming.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
-import {Text} from '#/view/com/Typography'
+import {Text} from '#/components/Typography'
import {Palette} from './Palette'
export function Theming() {
diff --git a/src/view/screens/Storybook/Typography.tsx b/src/view/screens/Storybook/Typography.tsx
index 470d17f913..2e1f04a66e 100644
--- a/src/view/screens/Storybook/Typography.tsx
+++ b/src/view/screens/Storybook/Typography.tsx
@@ -1,11 +1,12 @@
import React from 'react'
+import {View} from 'react-native'
import {atoms as a} from '#/alf'
-import {Text, H1, H2, H3, H4, H5, H6, P} from '#/view/com/Typography'
+import {Text, H1, H2, H3, H4, H5, H6, P} from '#/components/Typography'
export function Typography() {
return (
- <>
+
H1 Heading
H2 Heading
H3 Heading
@@ -24,6 +25,6 @@ export function Typography() {
atoms.text_sm
atoms.text_xs
atoms.text_2xs
- >
+
)
}
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index 3724154c00..4006c192b4 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -4,12 +4,13 @@ import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {atoms as a, useTheme, ThemeProvider} from '#/alf'
import {useSetColorMode} from '#/state/shell'
-import {Button} from '#/view/com/Button'
+import {Button} from '#/components/Button'
import {Theming} from './Theming'
import {Typography} from './Typography'
import {Spacing} from './Spacing'
import {Buttons} from './Buttons'
+import {Links} from './Links'
import {Forms} from './Forms'
import {Dialogs} from './Dialogs'
import {Breakpoints} from './Breakpoints'
@@ -21,10 +22,11 @@ export function Storybook() {
return (
-
+
+
From 1e9c44fc75e1b7d9fc0bc3cdeb91b048b82af9f4 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Sat, 13 Jan 2024 13:21:51 -0600
Subject: [PATCH 25/68] Some styles
---
src/components/Prompt.tsx | 6 +-
src/components/forms/InputGroup.tsx | 8 +-
src/components/forms/InputText.tsx | 123 +++++++++++++++++++--------
src/view/screens/Storybook/Forms.tsx | 48 +++++++++++
4 files changed, 147 insertions(+), 38 deletions(-)
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index b5841e7bb9..61bf614be0 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -3,7 +3,7 @@ import {View, PressableProps, LayoutChangeEvent} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
-import {H2, P} from '#/components/Typography'
+import {H4, P} from '#/components/Typography'
import {Button} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -70,11 +70,11 @@ export function Title({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
const {titleId} = React.useContext(Context)
return (
-
{children}
-
+
)
}
diff --git a/src/components/forms/InputGroup.tsx b/src/components/forms/InputGroup.tsx
index 7a906c719a..6908d4df8e 100644
--- a/src/components/forms/InputGroup.tsx
+++ b/src/components/forms/InputGroup.tsx
@@ -1,12 +1,13 @@
import React from 'react'
import {View} from 'react-native'
-import {atoms} from '#/alf'
+import {atoms, useTheme} from '#/alf'
/**
* NOT FINISHED, just here as a reference
*/
export function InputGroup(props: React.PropsWithChildren<{}>) {
+ const t = useTheme()
const children = React.Children.toArray(props.children)
const total = children.length
return (
@@ -14,6 +15,11 @@ export function InputGroup(props: React.PropsWithChildren<{}>) {
{children.map((child, i) => {
return React.isValidElement(child) ? (
+ {i > 0 ? (
+
+ ) : null}
{React.cloneElement(child, {
// @ts-ignore
style: [
diff --git a/src/components/forms/InputText.tsx b/src/components/forms/InputText.tsx
index eac2f0b01c..9c91ccf2aa 100644
--- a/src/components/forms/InputText.tsx
+++ b/src/components/forms/InputText.tsx
@@ -42,6 +42,7 @@ export function InputText({
onOut: onHoverOut,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+ const hasIcon = !!Icon
const handleSuffixLayout = React.useCallback(
(e: LayoutChangeEvent) => {
@@ -50,39 +51,85 @@ export function InputText({
[setSuffixPadding],
)
- const {inputStyles, iconStyles} = React.useMemo(() => {
- const input: TextStyle[] = []
- const icon: TextStyle[] = []
-
- if (Icon) {
- input.push({
- paddingLeft: 40,
- })
- }
-
- if (hasError) {
- input.push({
- borderColor: tokens.color.red_200,
- })
- icon.push({
- color: tokens.color.red_400,
- })
- }
-
- if (hovered || focused) {
- input.push({
- borderColor: t.atoms.border_contrast.borderColor,
- })
+ const {inputBaseStyles, inputHoverStyles, inputFocusStyles} =
+ React.useMemo(() => {
+ const base: TextStyle[] = []
+ const hover: TextStyle[] = [
+ {
+ borderColor: t.palette.contrast_300,
+ },
+ ]
+ const focus: TextStyle[] = [
+ {
+ backgroundColor: t.palette.contrast_50,
+ borderColor: t.palette.primary_500,
+ },
+ ]
+
+ if (hasIcon) {
+ base.push({
+ paddingLeft: 40,
+ })
+ }
if (hasError) {
- input.push({
+ base.push({
+ backgroundColor:
+ t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
+ borderColor:
+ t.name === 'light'
+ ? t.palette.negative_300
+ : t.palette.negative_800,
+ })
+ hover.push({
+ borderColor: tokens.color.red_500,
+ })
+ focus.push({
+ backgroundColor:
+ t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
borderColor: tokens.color.red_500,
})
}
- }
- return {inputStyles: input, iconStyles: icon}
- }, [t, hovered, focused, hasError, Icon])
+ return {
+ inputBaseStyles: base,
+ inputHoverStyles: hover,
+ inputFocusStyles: focus,
+ }
+ }, [t, hasError, hasIcon])
+
+ const {iconBaseStyles, iconHoverStyles, iconFocusStyles} =
+ React.useMemo(() => {
+ const base: TextStyle[] = []
+ const hover: TextStyle[] = [
+ {
+ color: t.palette.contrast_500,
+ },
+ ]
+ const focus: TextStyle[] = [
+ {
+ color: t.palette.primary_500,
+ },
+ ]
+
+ if (hasError) {
+ base.push({
+ color: t.palette.negative_400,
+ })
+ hover.push({
+ color: t.palette.negative_500,
+ })
+ focus.push({
+ color: t.palette.negative_500,
+ })
+ }
+
+ return {
+ iconBaseStyles: base,
+ iconHoverStyles: hover,
+ iconFocusStyles: focus,
+ }
+ }, [t, hasError])
const handleOnChange = React.useCallback(
(e: any) => {
@@ -116,7 +163,7 @@ export function InputText({
aria-label={label}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
- placeholderTextColor={t.atoms.text_contrast_500.color}
+ placeholderTextColor={t.atoms.text_contrast_400.color}
onFocus={onFocus}
onBlur={onBlur}
onChange={handleOnChange}
@@ -125,20 +172,21 @@ export function InputText({
onMouseLeave: onHoverOut,
})}
style={[
- t.name === 'dark' ? t.atoms.bg_contrast_100 : t.atoms.bg,
+ t.atoms.bg_contrast_50,
atoms.w_full,
atoms.px_lg,
atoms.py_md,
atoms.rounded_sm,
atoms.text_md,
- t.atoms.border,
t.atoms.text,
web({
paddingTop: atoms.pt_md.paddingTop - 1,
}),
- {paddingRight: suffixPadding},
+ {borderColor: 'transparent', paddingRight: suffixPadding},
{borderWidth: 2, lineHeight: atoms.text_md.lineHeight * 1.1875},
- ...inputStyles,
+ ...inputBaseStyles,
+ ...(hovered ? inputHoverStyles : []),
+ ...(focused ? inputFocusStyles : []),
...(Array.isArray(props.style) ? props.style : [props.style]),
]}
/>
@@ -155,12 +203,19 @@ export function InputText({
]}>
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 85c8701d79..fa687d786d 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -5,6 +5,7 @@ import {atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
import {InputText} from '#/components/forms/InputText'
import {InputDate, utils} from '#/components/forms/InputDate'
+import {InputGroup} from '#/components/forms/InputGroup'
import {Logo} from '#/view/icons/Logo'
export function Forms() {
@@ -27,6 +28,16 @@ export function Forms() {
value="Test initial value"
onChange={text => console.log(text)}
/>
+ console.log(text)}
+ icon={Logo}
+ />
.bksy.social}
/>
+ console.log(text)}
+ />
+
+
+ console.log(text)}
+ />
+ console.log(text)}
+ />
+ console.log(text)}
+ />
+
Date: Sat, 13 Jan 2024 17:43:20 -0600
Subject: [PATCH 26/68] Toggles
---
src/alf/atoms.ts | 6 +
src/alf/tokens.ts | 4 +-
src/components/forms/Toggle.tsx | 399 +++++++++++++++++++++++++++
src/view/screens/Storybook/Forms.tsx | 234 +++++++++++-----
4 files changed, 568 insertions(+), 75 deletions(-)
create mode 100644 src/components/forms/Toggle.tsx
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index b3b06efccf..d2dafac7eb 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -48,6 +48,12 @@ export const atoms = {
/*
* Border radius
*/
+ rounded_2xs: {
+ borderRadius: tokens.borderRadius._2xs,
+ },
+ rounded_xs: {
+ borderRadius: tokens.borderRadius.xs,
+ },
rounded_sm: {
borderRadius: tokens.borderRadius.sm,
},
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index db402840f8..ff8a8ebb8b 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -16,7 +16,7 @@ export const color = {
blue_25: `#F5FAFF`,
blue_50: `#EFF8FF`,
- blue_100: `#EFF8FF`,
+ blue_100: `#D1E9FF`,
blue_200: `#B2DDFF`,
blue_300: `#84CAFF`,
blue_400: `#53B1FD`,
@@ -84,6 +84,8 @@ export const lineHeight = {
} as const
export const borderRadius = {
+ _2xs: 2,
+ xs: 4,
sm: 8,
md: 12,
full: 999,
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
new file mode 100644
index 0000000000..e3cfa1a4cc
--- /dev/null
+++ b/src/components/forms/Toggle.tsx
@@ -0,0 +1,399 @@
+import React from 'react'
+import {Pressable, PressableProps, View, ViewStyle} from 'react-native'
+
+import {useTheme, atoms as a, web} from '#/alf'
+import {Text} from '#/components/Typography'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
+import {StyleProp} from 'react-native'
+
+type ItemState = {
+ name: string
+ value: boolean
+ disabled: boolean
+ hovered: boolean
+ pressed: boolean
+ focused: boolean
+}
+
+const ItemContext = React.createContext({
+ name: '',
+ value: false,
+ disabled: false,
+ hovered: false,
+ pressed: false,
+ focused: false,
+})
+
+const GroupContext = React.createContext<{
+ values: string[]
+ disabled: boolean
+ role?: 'radio' | 'checkbox'
+}>({
+ values: [],
+ disabled: false,
+ role: 'checkbox',
+})
+
+type ItemProps = Omit & {
+ name: string
+ value?: boolean
+ onChange?: ({name, value}: {name: string; value: boolean}) => void
+ style?: (state: ItemState) => ViewStyle
+ children: ((props: ItemState) => React.ReactNode) | React.ReactNode
+}
+
+function Item({
+ children,
+ name,
+ value = false,
+ disabled,
+ onChange,
+ style,
+}: ItemProps) {
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
+ const {
+ state: pressed,
+ onIn: onPressIn,
+ onOut: onPressOut,
+ } = useInteractionState()
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+
+ const onPress = React.useCallback(() => {
+ const next = !value
+ onChange?.({name, value: next})
+ }, [name, value, onChange])
+
+ const state = React.useMemo(
+ () => ({
+ name,
+ value,
+ disabled: disabled ?? false,
+ hovered,
+ pressed,
+ focused,
+ }),
+ [name, value, disabled, hovered, pressed, focused],
+ )
+
+ return (
+
+
+ {typeof children === 'function' ? children(state) : children}
+
+
+ )
+}
+
+function Group({
+ children,
+ values: initialValues,
+ onChange,
+ disabled,
+ role = 'checkbox',
+ maxSelections,
+ style,
+}: React.PropsWithChildren<{
+ values: string[]
+ onChange: (value: string[]) => void
+ disabled?: boolean
+ role?: 'radio' | 'checkbox'
+ maxSelections?: number
+ style?: StyleProp
+}>) {
+ const _disabled = disabled ?? false
+ const [values, setValues] = React.useState(
+ role === 'radio' ? initialValues.slice(0, 1) : initialValues,
+ )
+ const [maxReached, setMaxReached] = React.useState(false)
+
+ const itemOnChange = React.useCallback<
+ Exclude
+ >(
+ ({name, value}) => {
+ if (role === 'checkbox') {
+ setValues(s => {
+ const state = s.filter(v => v !== name)
+ return value ? state.concat(name) : state
+ })
+ } else {
+ setValues([name])
+ }
+ },
+ [role, setValues],
+ )
+
+ React.useEffect(() => {
+ onChange(values)
+ }, [values, onChange])
+
+ React.useEffect(() => {
+ if (role === 'checkbox') {
+ if (
+ maxSelections &&
+ values.length >= maxSelections &&
+ maxReached === false
+ ) {
+ setMaxReached(true)
+ } else if (
+ maxSelections &&
+ values.length < maxSelections &&
+ maxReached === true
+ ) {
+ setMaxReached(false)
+ }
+ }
+ }, [role, values.length, maxSelections, maxReached, setMaxReached])
+
+ return (
+
+
+ {React.Children.map(children, child => {
+ if (!React.isValidElement(child)) return null
+
+ const isSelected = values.includes(child.props.name)
+ let isDisabled = _disabled || child.props.disabled
+
+ if (maxReached && !isSelected) {
+ isDisabled = true
+ }
+
+ return React.isValidElement(child) ? (
+
+ {React.cloneElement(child, {
+ // @ts-ignore TODO figure out children types
+ disabled: isDisabled,
+ value: isSelected,
+ onChange: itemOnChange,
+ })}
+
+ ) : null
+ })}
+
+
+ )
+}
+
+function Label({children}: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ const {disabled} = React.useContext(ItemContext)
+ return (
+
+ {children}
+
+ )
+}
+
+function createSharedToggleStyles({
+ theme: t,
+ hovered,
+ focused,
+ value,
+ disabled,
+}: {
+ theme: ReturnType
+ value: boolean
+ hovered: boolean
+ focused: boolean
+ disabled: boolean
+}) {
+ return [
+ hovered || focused
+ ? {
+ backgroundColor: t.palette.contrast_50,
+ borderColor: t.palette.contrast_500,
+ }
+ : {},
+ value
+ ? {
+ backgroundColor:
+ t.name === 'light' ? t.palette.primary_25 : t.palette.primary_900,
+ borderColor: t.palette.primary_500,
+ }
+ : {},
+ value && (hovered || focused)
+ ? {
+ backgroundColor:
+ t.name === 'light' ? t.palette.primary_100 : t.palette.primary_800,
+ borderColor:
+ t.name === 'light' ? t.palette.primary_600 : t.palette.primary_400,
+ }
+ : {},
+ disabled
+ ? {
+ backgroundColor: t.palette.contrast_200,
+ borderColor: t.palette.contrast_300,
+ }
+ : {},
+ ]
+}
+
+function Checkbox() {
+ const t = useTheme()
+ const {value, hovered, focused, disabled} = React.useContext(ItemContext)
+ return (
+
+ {value ? (
+
+ ) : null}
+
+ )
+}
+
+function Switch() {
+ const t = useTheme()
+ const {value, hovered, focused, disabled} = React.useContext(ItemContext)
+ return (
+
+
+
+ )
+}
+
+function Radio() {
+ const t = useTheme()
+ const {value, hovered, focused, disabled} = React.useContext(ItemContext)
+ return (
+
+ {value ? (
+
+ ) : null}
+
+ )
+}
+
+export default {
+ Item,
+ Checkbox,
+ Label,
+ Switch,
+ Radio,
+ Group,
+}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index fa687d786d..a6513641be 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -2,73 +2,48 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
-import {Text} from '#/components/Typography'
+import {Text, H1, H3} from '#/components/Typography'
import {InputText} from '#/components/forms/InputText'
import {InputDate, utils} from '#/components/forms/InputDate'
import {InputGroup} from '#/components/forms/InputGroup'
import {Logo} from '#/view/icons/Logo'
+import Toggle from '#/components/forms/Toggle'
export function Forms() {
return (
-
- console.log(text)}
- />
- console.log(text)}
- />
- console.log(text)}
- icon={Logo}
- />
- console.log(text)}
- icon={Logo}
- />
- console.log(text)}
- icon={Logo}
- suffix={() => .bksy.social}
- />
- console.log(text)}
- />
+
+ Forms
-
+
+ InputText
+
+ console.log(text)}
+ />
+ console.log(text)}
+ />
+ console.log(text)}
+ icon={Logo}
+ />
console.log(text)}
+ icon={Logo}
/>
console.log(text)}
+ icon={Logo}
+ suffix={() => .bksy.social}
/>
console.log(text)}
/>
-
- console.log(date)}
- accessibilityLabel="Date"
- accessibilityHint="Enter a date"
- />
- console.log(date)}
- accessibilityLabel="Date"
- accessibilityHint="Enter a date"
- />
+ InputDate
+ console.log(date)}
+ accessibilityLabel="Date"
+ accessibilityHint="Enter a date"
+ />
+ console.log(date)}
+ accessibilityLabel="Date"
+ accessibilityHint="Enter a date"
+ />
+
+
+
+ InputGroup (WIP)
+
+ console.log(text)}
+ />
+ console.log(text)}
+ />
+ console.log(text)}
+ />
+
+
+
+
+ Toggles
+
+ console.log(e)}
+ style={[a.gap_sm]}>
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ console.log(e)}
+ style={[a.gap_sm]}>
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ console.log(e)}
+ style={[a.gap_sm]}>
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
)
}
From cb1bfe0e6f482b6e6b4db3915ddab81a464843bf Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Sun, 14 Jan 2024 10:43:26 -0600
Subject: [PATCH 27/68] Improve a11y
---
src/components/forms/Toggle.tsx | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index e3cfa1a4cc..2bcf40836f 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -5,8 +5,10 @@ import {useTheme, atoms as a, web} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {StyleProp} from 'react-native'
+import {AccessibilityRole} from 'react-native'
type ItemState = {
+ id: string
name: string
value: boolean
disabled: boolean
@@ -16,6 +18,7 @@ type ItemState = {
}
const ItemContext = React.createContext({
+ id: '',
name: '',
value: false,
disabled: false,
@@ -49,7 +52,9 @@ function Item({
disabled,
onChange,
style,
+ role,
}: ItemProps) {
+ const labelId = React.useId()
const {
state: hovered,
onIn: onHoverIn,
@@ -69,6 +74,7 @@ function Item({
const state = React.useMemo(
() => ({
+ id: labelId,
name,
value,
disabled: disabled ?? false,
@@ -76,7 +82,7 @@ function Item({
pressed,
focused,
}),
- [name, value, disabled, hovered, pressed, focused],
+ [labelId, name, value, disabled, hovered, pressed, focused],
)
return (
@@ -84,9 +90,14 @@ function Item({
) {
const t = useTheme()
- const {disabled} = React.useContext(ItemContext)
+ const {id, disabled} = React.useContext(ItemContext)
return (
Date: Sun, 14 Jan 2024 11:14:13 -0600
Subject: [PATCH 28/68] Autosize dialog, handle max height, Dialog.ScrolLView
not working
---
src/components/Dialog/index.tsx | 29 +++++++++++++++++---
src/components/Dialog/index.web.tsx | 1 +
src/components/Prompt.tsx | 38 ++++++--------------------
src/view/screens/Storybook/Dialogs.tsx | 1 +
4 files changed, 36 insertions(+), 33 deletions(-)
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 4bc1338e0b..7b3e34db20 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -1,6 +1,7 @@
import React, {useImperativeHandle} from 'react'
-import {View, Dimensions} from 'react-native'
+import {View, Dimensions, LayoutChangeEvent} from 'react-native'
import BottomSheet, {BottomSheetBackdrop} from '@gorhom/bottom-sheet'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
import {Portal} from '#/components/Portal'
@@ -12,6 +13,13 @@ import {
} from '#/components/Dialog/types'
import {Context} from '#/components/Dialog/context'
+/**
+ * Exports
+ */
+export {
+ BottomSheetScrollView as ScrollView,
+ BottomSheetTextInput as TextInput,
+} from '@gorhom/bottom-sheet'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
@@ -21,8 +29,12 @@ export function Outer({
onClose,
nativeOptions,
}: React.PropsWithChildren) {
+ const insets = useSafeAreaInsets()
const t = useTheme()
const sheet = React.useRef(null)
+ const [defaultSnapPoints, setDefaultSnapPoints] = React.useState<
+ string | number
+ >('25%')
const open = React.useCallback((i = 0) => {
sheet.current?.snapToIndex(i)
@@ -33,6 +45,15 @@ export function Outer({
onClose?.()
}, [onClose])
+ const measureDefaultSnapPoint = React.useCallback(
+ (e: LayoutChangeEvent) => {
+ const min = e.nativeEvent.layout.height + insets.bottom + 40
+ const max = Dimensions.get('window').height - insets.top - 40
+ setDefaultSnapPoints(min < max ? min : max)
+ },
+ [insets, setDefaultSnapPoints],
+ )
+
useImperativeHandle(
control.ref,
() => ({
@@ -47,9 +68,9 @@ export function Outer({
return (
- {children}
+ {children}
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 8efccd55d7..88b29e13a8 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -11,6 +11,7 @@ import {Button} from '#/components/Button'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
import {Context, useDialogContext} from '#/components/Dialog/context'
+export {ScrollView, TextInput} from 'react-native'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index 61bf614be0..4617943829 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -1,6 +1,5 @@
import React from 'react'
-import {View, PressableProps, LayoutChangeEvent} from 'react-native'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {View, PressableProps} from 'react-native'
import {useTheme, atoms as a} from '#/alf'
import {H4, P} from '#/components/Typography'
@@ -24,43 +23,24 @@ export function Outer({
}: React.PropsWithChildren<{
control: Dialog.DialogOuterProps['control']
}>) {
- const insets = useSafeAreaInsets()
const titleId = React.useId()
const descriptionId = React.useId()
- const [defaultSnapPoints, setDefaultSnapPoints] = React.useState<
- string | number
- >('25%')
const context = React.useMemo(
() => ({titleId, descriptionId}),
[titleId, descriptionId],
)
- const measureDefaultSnapPoint = React.useCallback(
- (e: LayoutChangeEvent) => {
- setDefaultSnapPoints(e.nativeEvent.layout.height + insets.bottom + 50)
- },
- [insets, setDefaultSnapPoints],
- )
-
return (
-
+
-
-
-
- {children}
-
-
+
+
+ {children}
+
)
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index b4a779e9d0..8ecc2d101c 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -50,6 +50,7 @@ export function Dialogs() {
accessibilityLabelledBy="dialog-title"
accessibilityDescribedBy="dialog-description">
+
Dialog
Description
From 94d31d4cc50d567b6b237e147d0e453ff9964954 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 10:48:59 -0600
Subject: [PATCH 29/68] Try to use BottomSheet's own APIs
---
src/components/Dialog/index.tsx | 57 ++++++++++++--------------
src/view/screens/Storybook/Dialogs.tsx | 2 +-
src/view/screens/Storybook/index.tsx | 2 +-
3 files changed, 28 insertions(+), 33 deletions(-)
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 7b3e34db20..de9d69edb2 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -1,6 +1,9 @@
import React, {useImperativeHandle} from 'react'
-import {View, Dimensions, LayoutChangeEvent} from 'react-native'
-import BottomSheet, {BottomSheetBackdrop} from '@gorhom/bottom-sheet'
+import {View, Dimensions} from 'react-native'
+import BottomSheet, {
+ BottomSheetBackdrop,
+ BottomSheetView,
+} from '@gorhom/bottom-sheet'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
@@ -29,12 +32,8 @@ export function Outer({
onClose,
nativeOptions,
}: React.PropsWithChildren) {
- const insets = useSafeAreaInsets()
const t = useTheme()
const sheet = React.useRef(null)
- const [defaultSnapPoints, setDefaultSnapPoints] = React.useState<
- string | number
- >('25%')
const open = React.useCallback((i = 0) => {
sheet.current?.snapToIndex(i)
@@ -45,15 +44,6 @@ export function Outer({
onClose?.()
}, [onClose])
- const measureDefaultSnapPoint = React.useCallback(
- (e: LayoutChangeEvent) => {
- const min = e.nativeEvent.layout.height + insets.bottom + 40
- const max = Dimensions.get('window').height - insets.top - 40
- setDefaultSnapPoints(min < max ? min : max)
- },
- [insets, setDefaultSnapPoints],
- )
-
useImperativeHandle(
control.ref,
() => ({
@@ -68,10 +58,11 @@ export function Outer({
return (
-
-
- {children}
-
+
+
+
+ {children}
+
+
)
@@ -108,6 +101,7 @@ export function Outer({
// TODO a11y props here, or is that handled by the sheet?
export function Inner(props: DialogInnerProps) {
+ const insets = useSafeAreaInsets()
return (
{props.children}
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index 8ecc2d101c..f34fca8b85 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -51,7 +51,7 @@ export function Dialogs() {
accessibilityDescribedBy="dialog-description">
-
+
Dialog
Description
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index 4006c192b4..bde3b33b2a 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -53,6 +53,7 @@ export function Storybook() {
+
@@ -68,7 +69,6 @@ export function Storybook() {
-
From 048ce0244f3dbf005f332e37e75054f26ba95a7b Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 15:58:57 -0600
Subject: [PATCH 30/68] Scrollable dialogs
---
src/App.native.tsx | 6 +-
src/App.web.tsx | 6 +-
src/components/Dialog/index.tsx | 80 +++++++++++++++-----------
src/components/Dialog/index.web.tsx | 7 ++-
src/components/Prompt.tsx | 3 +-
src/view/screens/Storybook/Dialogs.tsx | 13 +++--
src/view/shell/index.tsx | 2 +
src/view/shell/index.web.tsx | 2 +
8 files changed, 67 insertions(+), 52 deletions(-)
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 7042c6e098..b0aa4b182a 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -41,10 +41,7 @@ import {
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
import {Splash} from '#/Splash'
-import {
- Provider as PortalProvider,
- Outlet as PortalOutlet,
-} from '#/components/Portal'
+import {Provider as PortalProvider} from '#/components/Portal'
SplashScreen.preventAutoHideAsync()
@@ -80,7 +77,6 @@ function InnerApp() {
-
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 546f5614df..756d4954eb 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -30,10 +30,7 @@ import {
} from 'state/session'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
-import {
- Provider as PortalProvider,
- Outlet as PortalOutlet,
-} from '#/components/Portal'
+import {Provider as PortalProvider} from '#/components/Portal'
function InnerApp() {
const {isInitialLoad, currentAccount} = useSession()
@@ -62,7 +59,6 @@ function InnerApp() {
-
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index de9d69edb2..89af99d66a 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -2,6 +2,7 @@ import React, {useImperativeHandle} from 'react'
import {View, Dimensions} from 'react-native'
import BottomSheet, {
BottomSheetBackdrop,
+ BottomSheetScrollView,
BottomSheetView,
} from '@gorhom/bottom-sheet'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
@@ -16,13 +17,6 @@ import {
} from '#/components/Dialog/types'
import {Context} from '#/components/Dialog/context'
-/**
- * Exports
- */
-export {
- BottomSheetScrollView as ScrollView,
- BottomSheetTextInput as TextInput,
-} from '@gorhom/bottom-sheet'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
@@ -34,6 +28,8 @@ export function Outer({
}: React.PropsWithChildren) {
const t = useTheme()
const sheet = React.useRef(null)
+ const sheetOptions = nativeOptions?.sheet || {}
+ const hasSnapPoints = !!sheetOptions.snapPoints
const open = React.useCallback((i = 0) => {
sheet.current?.snapToIndex(i)
@@ -58,15 +54,15 @@ export function Outer({
return (
(
-
-
-
- {children}
-
-
+
+
+ {children}
+
)
@@ -103,8 +97,27 @@ export function Outer({
export function Inner(props: DialogInnerProps) {
const insets = useSafeAreaInsets()
return (
-
+ {props.children}
+
+ )
+}
+
+export function ScrollableInner(props: DialogInnerProps) {
+ const insets = useSafeAreaInsets()
+ return (
+
{props.children}
-
+
)
}
@@ -126,12 +139,13 @@ export function Handle() {
a.absolute,
a.rounded_sm,
a.z_10,
- t.atoms.bg_contrast_200,
{
- top: 12,
- width: 50,
- height: 6,
+ top: a.pt_lg.paddingTop,
+ width: 35,
+ height: 4,
alignSelf: 'center',
+ backgroundColor: t.palette.contrast_900,
+ opacity: 0.5,
},
]}
/>
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 88b29e13a8..b98ddae5e8 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -11,7 +11,6 @@ import {Button} from '#/components/Button'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
import {Context, useDialogContext} from '#/components/Dialog/context'
-export {ScrollView, TextInput} from 'react-native'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
@@ -91,7 +90,7 @@ export function Outer({
style={[
web(a.fixed),
a.inset_0,
- t.atoms.bg_contrast_300,
+ t.atoms.bg_contrast_100,
{opacity: 0.8},
]}
/>
@@ -145,7 +144,7 @@ export function Inner({
a.border,
gtMobile ? a.p_xl : a.p_lg,
t.atoms.bg,
- {maxWidth: 600, borderColor: t.palette.contrast_300},
+ {maxWidth: 600, borderColor: t.palette.contrast_200},
...(Array.isArray(style) ? style : [style || {}]),
]}>
{children}
@@ -154,6 +153,8 @@ export function Inner({
)
}
+export const ScrollableInner = Inner
+
export function Handle() {
return null
}
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index 4617943829..5e1f88453f 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -34,11 +34,12 @@ export function Outer({
return (
+
+
-
{children}
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index f34fca8b85..9dd76be40a 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -45,15 +45,18 @@ export function Dialogs() {
-
-
+
+
+
-
-
Dialog
Description
+
-
+
>
)
diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx
index 51c03ae3dd..5320aebfc0 100644
--- a/src/view/shell/index.tsx
+++ b/src/view/shell/index.tsx
@@ -28,6 +28,7 @@ import {isAndroid} from 'platform/detection'
import {useSession} from '#/state/session'
import {useCloseAnyActiveElement} from '#/state/util'
import * as notifications from 'lib/notifications/notifications'
+import {Outlet as PortalOutlet} from '#/components/Portal'
function ShellInner() {
const isDrawerOpen = useIsDrawerOpen()
@@ -94,6 +95,7 @@ function ShellInner() {
+
>
)
diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx
index 38da860bdc..0a2603acfc 100644
--- a/src/view/shell/index.web.tsx
+++ b/src/view/shell/index.web.tsx
@@ -15,6 +15,7 @@ import {useAuxClick} from 'lib/hooks/useAuxClick'
import {t} from '@lingui/macro'
import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell'
import {useCloseAllActiveElements} from '#/state/util'
+import {Outlet as PortalOutlet} from '#/components/Portal'
function ShellInner() {
const isDrawerOpen = useIsDrawerOpen()
@@ -41,6 +42,7 @@ function ShellInner() {
+
{!isDesktop && isDrawerOpen && (
Date: Mon, 15 Jan 2024 16:04:31 -0600
Subject: [PATCH 31/68] Add web shadow
---
src/components/Dialog/index.web.tsx | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index b98ddae5e8..d1d25a9019 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -90,7 +90,7 @@ export function Outer({
style={[
web(a.fixed),
a.inset_0,
- t.atoms.bg_contrast_100,
+ t.atoms.bg,
{opacity: 0.8},
]}
/>
@@ -144,7 +144,13 @@ export function Inner({
a.border,
gtMobile ? a.p_xl : a.p_lg,
t.atoms.bg,
- {maxWidth: 600, borderColor: t.palette.contrast_200},
+ {
+ maxWidth: 600,
+ borderColor: t.palette.contrast_200,
+ shadowColor: t.palette.black,
+ shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
+ shadowRadius: 30,
+ },
...(Array.isArray(style) ? style : [style || {}]),
]}>
{children}
From 4285808785a0c3f65091f80ae8391f04ec76f5b2 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 16:10:29 -0600
Subject: [PATCH 32/68] Handle overscroll
---
src/components/Dialog/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 89af99d66a..268d804331 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -123,10 +123,10 @@ export function ScrollableInner(props: DialogInnerProps) {
{
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
- paddingBottom: insets.bottom + a.pb_5xl.paddingBottom,
},
]}>
{props.children}
+
)
}
From 48abb5ac3728a8fb0e897aebe16ad2d9516054e0 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 16:28:11 -0600
Subject: [PATCH 33/68] Styles
---
src/components/Dialog/index.tsx | 3 ++-
src/components/Dialog/index.web.tsx | 3 +--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 268d804331..95f6437e77 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -62,9 +62,10 @@ export function Outer({
{...sheetOptions}
ref={sheet}
index={-1}
- backgroundStyle={{backgroundColor: t.atoms.bg.backgroundColor}}
+ backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={props => (
)}
From 4d3128aa126f273b8505e76e7dcdf11b0299b3a5 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 16:42:00 -0600
Subject: [PATCH 34/68] Dialog text input
---
src/components/Dialog/index.tsx | 4 +
src/components/Dialog/index.web.tsx | 1 +
src/components/forms/InputText.tsx | 438 +++++++++++++------------
src/view/screens/Storybook/Dialogs.tsx | 8 +
4 files changed, 236 insertions(+), 215 deletions(-)
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 95f6437e77..87d0a08372 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -3,12 +3,14 @@ import {View, Dimensions} from 'react-native'
import BottomSheet, {
BottomSheetBackdrop,
BottomSheetScrollView,
+ BottomSheetTextInput,
BottomSheetView,
} from '@gorhom/bottom-sheet'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
import {Portal} from '#/components/Portal'
+import {createTextInput} from '#/components/forms/InputText'
import {
DialogOuterProps,
@@ -19,6 +21,8 @@ import {Context} from '#/components/Dialog/context'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
+// @ts-ignore
+export const InputText = createTextInput(BottomSheetTextInput)
export function Outer({
children,
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 514013d673..6ddc1b0b4a 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -13,6 +13,7 @@ import {Context, useDialogContext} from '#/components/Dialog/context'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
+export {InputText} from '#/components/forms/InputText'
const stopPropagation = (e: any) => e.stopPropagation()
diff --git a/src/components/forms/InputText.tsx b/src/components/forms/InputText.tsx
index 9c91ccf2aa..5a6b60d468 100644
--- a/src/components/forms/InputText.tsx
+++ b/src/components/forms/InputText.tsx
@@ -20,221 +20,229 @@ type Props = BaseProps &
suffix?: React.FunctionComponent
}
-export function InputText({
- value: initialValue,
- onChange,
- testID,
- accessibilityLabel,
- accessibilityHint,
- label,
- hasError,
- icon: Icon,
- suffix: Suffix,
- ...props
-}: Props) {
- const labelId = React.useId()
- const t = useTheme()
- const [value, setValue] = React.useState(initialValue)
- const [suffixPadding, setSuffixPadding] = React.useState(0)
- const {
- state: hovered,
- onIn: onHoverIn,
- onOut: onHoverOut,
- } = useInteractionState()
- const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
- const hasIcon = !!Icon
-
- const handleSuffixLayout = React.useCallback(
- (e: LayoutChangeEvent) => {
- setSuffixPadding(e.nativeEvent.layout.width + 16)
- },
- [setSuffixPadding],
- )
-
- const {inputBaseStyles, inputHoverStyles, inputFocusStyles} =
- React.useMemo(() => {
- const base: TextStyle[] = []
- const hover: TextStyle[] = [
- {
- borderColor: t.palette.contrast_300,
- },
- ]
- const focus: TextStyle[] = [
- {
- backgroundColor: t.palette.contrast_50,
- borderColor: t.palette.primary_500,
- },
- ]
-
- if (hasIcon) {
- base.push({
- paddingLeft: 40,
- })
- }
-
- if (hasError) {
- base.push({
- backgroundColor:
- t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
- borderColor:
- t.name === 'light'
- ? t.palette.negative_300
- : t.palette.negative_800,
- })
- hover.push({
- borderColor: tokens.color.red_500,
- })
- focus.push({
- backgroundColor:
- t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
- borderColor: tokens.color.red_500,
- })
- }
-
- return {
- inputBaseStyles: base,
- inputHoverStyles: hover,
- inputFocusStyles: focus,
- }
- }, [t, hasError, hasIcon])
-
- const {iconBaseStyles, iconHoverStyles, iconFocusStyles} =
- React.useMemo(() => {
- const base: TextStyle[] = []
- const hover: TextStyle[] = [
- {
- color: t.palette.contrast_500,
- },
- ]
- const focus: TextStyle[] = [
- {
- color: t.palette.primary_500,
- },
- ]
-
- if (hasError) {
- base.push({
- color: t.palette.negative_400,
- })
- hover.push({
- color: t.palette.negative_500,
- })
- focus.push({
- color: t.palette.negative_500,
- })
- }
-
- return {
- iconBaseStyles: base,
- iconHoverStyles: hover,
- iconFocusStyles: focus,
- }
- }, [t, hasError])
-
- const handleOnChange = React.useCallback(
- (e: any) => {
- const value = e.currentTarget.value
- onChange(value)
- setValue(value)
- },
- [onChange, setValue],
- )
-
- return (
-
- {label && (
-
- {label}
-
- )}
-
-
-
- {Icon && (
-
- (initialValue)
+ const [suffixPadding, setSuffixPadding] = React.useState(0)
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+ const hasIcon = !!Icon
+
+ const handleSuffixLayout = React.useCallback(
+ (e: LayoutChangeEvent) => {
+ setSuffixPadding(e.nativeEvent.layout.width + 16)
+ },
+ [setSuffixPadding],
+ )
+
+ const {inputBaseStyles, inputHoverStyles, inputFocusStyles} =
+ React.useMemo(() => {
+ const base: TextStyle[] = []
+ const hover: TextStyle[] = [
+ {
+ borderColor: t.palette.contrast_300,
+ },
+ ]
+ const focus: TextStyle[] = [
+ {
+ backgroundColor: t.palette.contrast_50,
+ borderColor: t.palette.primary_500,
+ },
+ ]
+
+ if (hasIcon) {
+ base.push({
+ paddingLeft: 40,
+ })
+ }
+
+ if (hasError) {
+ base.push({
+ backgroundColor:
+ t.name === 'light'
+ ? t.palette.negative_25
+ : t.palette.negative_900,
+ borderColor:
+ t.name === 'light'
+ ? t.palette.negative_300
+ : t.palette.negative_800,
+ })
+ hover.push({
+ borderColor: tokens.color.red_500,
+ })
+ focus.push({
+ backgroundColor:
+ t.name === 'light'
+ ? t.palette.negative_25
+ : t.palette.negative_900,
+ borderColor: tokens.color.red_500,
+ })
+ }
+
+ return {
+ inputBaseStyles: base,
+ inputHoverStyles: hover,
+ inputFocusStyles: focus,
+ }
+ }, [t, hasError, hasIcon])
+
+ const {iconBaseStyles, iconHoverStyles, iconFocusStyles} =
+ React.useMemo(() => {
+ const base: TextStyle[] = []
+ const hover: TextStyle[] = [
+ {
+ color: t.palette.contrast_500,
+ },
+ ]
+ const focus: TextStyle[] = [
+ {
+ color: t.palette.primary_500,
+ },
+ ]
+
+ if (hasError) {
+ base.push({
+ color: t.palette.negative_400,
+ })
+ hover.push({
+ color: t.palette.negative_500,
+ })
+ focus.push({
+ color: t.palette.negative_500,
+ })
+ }
+
+ return {
+ iconBaseStyles: base,
+ iconHoverStyles: hover,
+ iconFocusStyles: focus,
+ }
+ }, [t, hasError])
+
+ const handleOnChange = React.useCallback(
+ (e: any) => {
+ const value = e.currentTarget.value
+ onChange(value)
+ setValue(value)
+ },
+ [onChange, setValue],
+ )
+
+ return (
+
+ {label && (
+
-
- )}
-
- {Suffix && (
-
+ {label}
+
+ )}
+
+
-
-
- )}
-
- )
+ t.atoms.bg_contrast_50,
+ atoms.w_full,
+ atoms.px_lg,
+ atoms.py_md,
+ atoms.rounded_sm,
+ atoms.text_md,
+ t.atoms.text,
+ web({
+ paddingTop: atoms.pt_md.paddingTop - 1,
+ }),
+ {borderColor: 'transparent', paddingRight: suffixPadding},
+ {borderWidth: 2, lineHeight: atoms.text_md.lineHeight * 1.1875},
+ ...inputBaseStyles,
+ ...(hovered ? inputHoverStyles : []),
+ ...(focused ? inputFocusStyles : []),
+ ...(Array.isArray(props.style) ? props.style : [props.style]),
+ ]}
+ />
+
+ {Icon && (
+
+
+
+ )}
+
+ {Suffix && (
+
+
+
+ )}
+
+ )
+ }
}
+
+export const InputText = createTextInput(TextInput)
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index 9dd76be40a..5ad6764b0d 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -56,6 +56,14 @@ export function Dialogs() {
Dialog
Description
+ {}}
+ placeholder="Type here"
+ accessibilityLabel="Type"
+ accessibilityHint="Type"
+ />
Date: Mon, 15 Jan 2024 18:52:11 -0600
Subject: [PATCH 35/68] Shadows
---
src/alf/atoms.ts | 19 +++++++++
src/alf/themes.ts | 43 +++++++++++++++++++++
src/alf/tokens.ts | 1 +
src/view/screens/Storybook/Shadows.tsx | 53 ++++++++++++++++++++++++++
src/view/screens/Storybook/index.tsx | 4 +-
5 files changed, 119 insertions(+), 1 deletion(-)
create mode 100644 src/view/screens/Storybook/Shadows.tsx
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index d2dafac7eb..c0d20e9c77 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -209,6 +209,25 @@ export const atoms = {
borderBottomWidth: 1,
},
+ /*
+ * Shadow
+ */
+ shadow_sm: {
+ shadowRadius: 8,
+ shadowOpacity: 0.1,
+ elevation: 8,
+ },
+ shadow_md: {
+ shadowRadius: 16,
+ shadowOpacity: 0.1,
+ elevation: 16,
+ },
+ shadow_lg: {
+ shadowRadius: 32,
+ shadowOpacity: 0.1,
+ elevation: 24,
+ },
+
/*
* Padding
*/
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index 103693b0b3..dc45944563 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -1,5 +1,6 @@
import * as tokens from '#/alf/tokens'
import type {Mutable} from '#/alf/types'
+import {atoms} from '#/alf/atoms'
export type ThemeName = 'light' | 'dim' | 'dark'
export type ReadonlyTheme = typeof light
@@ -156,6 +157,18 @@ export const light = {
border_contrast: {
borderColor: lightPalette.contrast_400,
},
+ shadow_sm: {
+ ...atoms.shadow_sm,
+ shadowColor: lightPalette.black,
+ },
+ shadow_md: {
+ ...atoms.shadow_md,
+ shadowColor: lightPalette.black,
+ },
+ shadow_lg: {
+ ...atoms.shadow_lg,
+ shadowColor: lightPalette.black,
+ },
},
}
@@ -202,6 +215,21 @@ export const dim: Theme = {
border_contrast: {
borderColor: darkPalette.contrast_300,
},
+ shadow_sm: {
+ ...atoms.shadow_sm,
+ shadowOpacity: 0.7,
+ shadowColor: tokens.color.trueBlack,
+ },
+ shadow_md: {
+ ...atoms.shadow_md,
+ shadowOpacity: 0.7,
+ shadowColor: tokens.color.trueBlack,
+ },
+ shadow_lg: {
+ ...atoms.shadow_lg,
+ shadowOpacity: 0.7,
+ shadowColor: tokens.color.trueBlack,
+ },
},
}
@@ -248,5 +276,20 @@ export const dark: Theme = {
border_contrast: {
borderColor: darkPalette.contrast_300,
},
+ shadow_sm: {
+ ...atoms.shadow_sm,
+ shadowOpacity: 0.7,
+ shadowColor: tokens.color.trueBlack,
+ },
+ shadow_md: {
+ ...atoms.shadow_md,
+ shadowOpacity: 0.7,
+ shadowColor: tokens.color.trueBlack,
+ },
+ shadow_lg: {
+ ...atoms.shadow_lg,
+ shadowOpacity: 0.7,
+ shadowColor: tokens.color.trueBlack,
+ },
},
}
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index ff8a8ebb8b..0bcffaebef 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -1,6 +1,7 @@
export const color = {
white: '#FFFFFF',
black: '#080B12',
+ trueBlack: '#000000',
gray_25: `#FCFCFD`,
gray_50: `#F9FAFB`,
diff --git a/src/view/screens/Storybook/Shadows.tsx b/src/view/screens/Storybook/Shadows.tsx
new file mode 100644
index 0000000000..f92112395f
--- /dev/null
+++ b/src/view/screens/Storybook/Shadows.tsx
@@ -0,0 +1,53 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a, useTheme} from '#/alf'
+import {H1, Text} from '#/components/Typography'
+
+export function Shadows() {
+ const t = useTheme()
+
+ return (
+
+ Shadows
+
+
+
+ shadow_sm
+
+
+
+ shadow_md
+
+
+
+ shadow_lg
+
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index bde3b33b2a..2cd956becb 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -14,6 +14,7 @@ import {Links} from './Links'
import {Forms} from './Forms'
import {Dialogs} from './Dialogs'
import {Breakpoints} from './Breakpoints'
+import {Shadows} from './Shadows'
export function Storybook() {
const t = useTheme()
@@ -53,7 +54,6 @@ export function Storybook() {
-
@@ -66,9 +66,11 @@ export function Storybook() {
+
+
From e7d9ce69f722c5d267efde28bad57a865aee78ca Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 19:13:10 -0600
Subject: [PATCH 36/68] Button focus states
---
src/components/Button.tsx | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 9d36892a55..558ec32d2b 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -105,7 +105,7 @@ export function Button({
}))
}, [setState])
- const {baseStyles, hoverStyles} = React.useMemo(() => {
+ const {baseStyles, hoverStyles, focusStyles} = React.useMemo(() => {
const baseStyles: ViewStyle[] = []
const hoverStyles: ViewStyle[] = []
const light = t.name === 'light'
@@ -260,6 +260,12 @@ export function Button({
return {
baseStyles,
hoverStyles,
+ focusStyles: [
+ ...hoverStyles,
+ {
+ outline: 0,
+ },
+ ],
}
}, [t, variant, color, size, disabled])
@@ -292,6 +298,7 @@ export function Button({
atoms.align_center,
...baseStyles,
...(state.hovered ? hoverStyles : []),
+ ...(state.focused ? focusStyles : []),
]}
onPressIn={onPressIn}
onPressOut={onPressOut}
From 4bf30a1ed332be45635ac8ddf916ed073a0b806c Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 20:15:50 -0600
Subject: [PATCH 37/68] Button pressed states
---
src/components/Button.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 558ec32d2b..7896fdc878 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -264,7 +264,7 @@ export function Button({
...hoverStyles,
{
outline: 0,
- },
+ } as ViewStyle,
],
}
}, [t, variant, color, size, disabled])
@@ -297,7 +297,7 @@ export function Button({
atoms.flex_row,
atoms.align_center,
...baseStyles,
- ...(state.hovered ? hoverStyles : []),
+ ...(state.hovered || state.pressed ? hoverStyles : []),
...(state.focused ? focusStyles : []),
]}
onPressIn={onPressIn}
From 56a139ed4be0da7cff74f834eaf7428010a503f2 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Mon, 15 Jan 2024 21:10:02 -0600
Subject: [PATCH 38/68] Gradient poc
---
src/alf/atoms.ts | 4 ++++
src/components/Button.tsx | 22 ++++++++++++++++++----
src/view/screens/Storybook/Buttons.tsx | 9 +++++++++
3 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index c0d20e9c77..203c2f282a 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -35,6 +35,10 @@ export const atoms = {
zIndex: 50,
},
+ overflow_hidden: {
+ overflow: 'hidden',
+ },
+
/*
* Width
*/
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 7896fdc878..b44fb05cac 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -7,10 +7,11 @@ import {
ViewStyle,
AccessibilityProps,
} from 'react-native'
+import LinearGradient from 'react-native-linear-gradient'
import {useTheme, atoms, tokens, web, native} from '#/alf'
-export type ButtonVariant = 'solid' | 'outline' | 'ghost'
+export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
export type ButtonColor = 'primary' | 'secondary' | 'negative'
export type ButtonSize = 'small' | 'large'
export type VariantProps = {
@@ -296,6 +297,7 @@ export function Button({
style={[
atoms.flex_row,
atoms.align_center,
+ atoms.overflow_hidden,
...baseStyles,
...(state.hovered || state.pressed ? hoverStyles : []),
...(state.focused ? focusStyles : []),
@@ -306,6 +308,18 @@ export function Button({
onHoverOut={onHoverOut}
onFocus={onFocus}
onBlur={onBlur}>
+ {variant === 'gradient' && (
+
+ )}
{typeof children === 'string' ? (
))}
+
+
+ Button
+
)
From ce049f1e1bd035def57a465fdc13dd2b5b259fd4 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 16 Jan 2024 10:41:35 -0600
Subject: [PATCH 39/68] Gradient colors and hovers
---
src/alf/tokens.ts | 50 +++++++++++++++++++++
src/components/Button.tsx | 56 ++++++++++++++++++++---
src/view/screens/Storybook/Buttons.tsx | 62 ++++++++++++++++++++++----
3 files changed, 154 insertions(+), 14 deletions(-)
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 0bcffaebef..36f6f3992a 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -98,6 +98,56 @@ export const fontWeight = {
bold: '900',
} as const
+export const gradients = {
+ sky: {
+ values: [
+ [0, '#0A7AFF'],
+ [1, '#59B9FF'],
+ ],
+ hover_value: '#0A7AFF',
+ },
+ midnight: {
+ values: [
+ [0, '#022C5E'],
+ [1, '#4079BC'],
+ ],
+ hover_value: '#022C5E',
+ },
+ sunrise: {
+ values: [
+ [0, '#4E90AE'],
+ [0.4, '#AEA3AB'],
+ [0.8, '#E6A98F'],
+ [1, '#F3A84C'],
+ ],
+ hover_value: '#AEA3AB',
+ },
+ sunset: {
+ values: [
+ [0, '#6772AF'],
+ [0.6, '#B88BB6'],
+ [1, '#FFA6AC'],
+ ],
+ hover_value: '#B88BB6',
+ },
+ nordic: {
+ values: [
+ [0, '#083367'],
+ [1, '#9EE8C1'],
+ ],
+ hover_value: '#3A7085',
+ },
+ bonfire: {
+ values: [
+ [0, '#203E4E'],
+ [0.4, '#755B62'],
+ [0.8, '#CD7765'],
+ [1, '#EF956E'],
+ ],
+ hover_value: '#755B62',
+ },
+} as const
+
export type Color = keyof typeof color
export type Space = keyof typeof space
export type FontSize = keyof typeof fontSize
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index b44fb05cac..b76839de49 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -12,7 +12,16 @@ import LinearGradient from 'react-native-linear-gradient'
import {useTheme, atoms, tokens, web, native} from '#/alf'
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
-export type ButtonColor = 'primary' | 'secondary' | 'negative'
+export type ButtonColor =
+ | 'primary'
+ | 'secondary'
+ | 'negative'
+ | 'gradient_sky'
+ | 'gradient_midnight'
+ | 'gradient_sunrise'
+ | 'gradient_sunset'
+ | 'gradient_nordic'
+ | 'gradient_bonfire'
export type ButtonSize = 'small' | 'large'
export type VariantProps = {
/**
@@ -270,6 +279,36 @@ export function Button({
}
}, [t, variant, color, size, disabled])
+ const {gradientColors, gradientHoverColors, gradientLocations} =
+ React.useMemo(() => {
+ const colors: string[] = []
+ const hoverColors: string[] = []
+ const locations: number[] = []
+ const gradient = {
+ primary: tokens.gradients.sky,
+ secondary: tokens.gradients.sky,
+ negative: tokens.gradients.sky,
+ gradient_sky: tokens.gradients.sky,
+ gradient_midnight: tokens.gradients.midnight,
+ gradient_sunrise: tokens.gradients.sunrise,
+ gradient_sunset: tokens.gradients.sunset,
+ gradient_nordic: tokens.gradients.nordic,
+ gradient_bonfire: tokens.gradients.bonfire,
+ }[color || 'primary']
+
+ if (variant === 'gradient') {
+ colors.push(...gradient.values.map(([_, color]) => color))
+ hoverColors.push(...gradient.values.map(_ => gradient.hover_value))
+ locations.push(...gradient.values.map(([location, _]) => location))
+ }
+
+ return {
+ gradientColors: colors,
+ gradientHoverColors: hoverColors,
+ gradientLocations: locations,
+ }
+ }, [variant, color])
+
const childProps = React.useMemo(
() => ({
state,
@@ -311,10 +350,11 @@ export function Button({
{variant === 'gradient' && (
Buttons
-
+
{['primary', 'secondary', 'negative'].map(color => (
{['solid', 'outline', 'ghost'].map(variant => (
@@ -37,14 +37,58 @@ export function Buttons() {
))}
-
- Button
-
+
+
+ {['gradient_sky', 'gradient_midnight', 'gradient_sunrise'].map(
+ name => (
+
+
+ Button
+
+
+ Button
+
+
+ ),
+ )}
+
+
+ {['gradient_sunset', 'gradient_nordic', 'gradient_bonfire'].map(
+ name => (
+
+
+ Button
+
+
+ Button
+
+
+ ),
+ )}
+
+
)
From 1e02cde581a7245776ff278feee78b6dab155bef Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 16 Jan 2024 10:43:31 -0600
Subject: [PATCH 40/68] Add hrefAttrs to Link
---
src/components/Link.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index 8a2e825479..2eb7b6bffd 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -145,8 +145,10 @@ export function Link({
href={href}
onPress={onPress}
{...web({
- target: isExternal ? '_blank' : undefined,
- rel: isExternal ? 'noopener noreferrer' : undefined,
+ hrefAttrs: {
+ target: isExternal ? 'blank' : undefined,
+ rel: isExternal ? 'noopener noreferrer' : undefined,
+ },
dataSet: {
// default to no underline, apply this ourselves
noUnderline: '1',
From 7315b0d7145f6df0c6a4e2818b7d4f9a01d79687 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 16 Jan 2024 11:04:54 -0600
Subject: [PATCH 41/68] Some more a11y
---
src/components/Button.tsx | 1 +
src/components/Dialog/index.web.tsx | 5 ++++-
src/components/Dialog/types.ts | 18 ++++++++++++++----
src/components/forms/InputText.tsx | 2 ++
src/components/forms/Toggle.tsx | 3 +++
src/view/screens/Storybook/Dialogs.tsx | 4 ++--
6 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index b76839de49..7c032dbad8 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -327,6 +327,7 @@ export function Button({
role="button"
{...rest}
aria-label={accessibilityLabel}
+ aria-pressed={state.pressed}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
disabled={disabled || false}
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 6ddc1b0b4a..ad205d3648 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -120,6 +120,7 @@ export function Outer({
export function Inner({
children,
style,
+ label,
accessibilityLabelledBy,
accessibilityDescribedBy,
}: DialogInnerProps) {
@@ -128,9 +129,11 @@ export function Inner({
return (
true}
diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts
index cdcad90da6..d36784183c 100644
--- a/src/components/Dialog/types.ts
+++ b/src/components/Dialog/types.ts
@@ -26,8 +26,18 @@ export type DialogOuterProps = {
webOptions?: {}
}
-export type DialogInnerProps = React.PropsWithChildren<{
+type DialogInnerPropsBase = React.PropsWithChildren<{
style?: ViewStyle
- accessibilityLabelledBy: A11yProps['aria-labelledby']
- accessibilityDescribedBy: string
-}>
+}> &
+ T
+export type DialogInnerProps =
+ | DialogInnerPropsBase<{
+ label?: undefined
+ accessibilityLabelledBy: A11yProps['aria-labelledby']
+ accessibilityDescribedBy: string
+ }>
+ | DialogInnerPropsBase<{
+ label: string
+ accessibilityLabelledBy?: undefined
+ accessibilityDescribedBy?: undefined
+ }>
diff --git a/src/components/forms/InputText.tsx b/src/components/forms/InputText.tsx
index 5a6b60d468..8933b1d085 100644
--- a/src/components/forms/InputText.tsx
+++ b/src/components/forms/InputText.tsx
@@ -166,6 +166,8 @@ export function createTextInput(Input: typeof TextInput) {
testID={testID}
aria-labelledby={labelId}
aria-label={label}
+ aria-invalid={hasError}
+ aria-placeholder={props.placeholder}
accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint}
placeholderTextColor={t.atoms.text_contrast_400.color}
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 2bcf40836f..8a9c53a608 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -41,6 +41,7 @@ type ItemProps = Omit & {
name: string
value?: boolean
onChange?: ({name, value}: {name: string; value: boolean}) => void
+ hasError?: boolean
style?: (state: ItemState) => ViewStyle
children: ((props: ItemState) => React.ReactNode) | React.ReactNode
}
@@ -51,6 +52,7 @@ function Item({
value = false,
disabled,
onChange,
+ hasError,
style,
role,
}: ItemProps) {
@@ -92,6 +94,7 @@ function Item({
aria-disabled={disabled ?? false}
aria-checked={value}
aria-labelledby={labelId}
+ aria-invalid={hasError}
role={role}
accessibilityRole={role as AccessibilityRole}
accessibilityState={{
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index 5ad6764b0d..5fd8856e55 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -51,8 +51,8 @@ export function Dialogs() {
+ accessibilityDescribedBy="dialog-description"
+ accessibilityLabelledBy="dialog-title">
Dialog
Description
From 4c76e38cc3b2bdc293640a3388ce36890bad393d Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 16 Jan 2024 11:32:39 -0600
Subject: [PATCH 42/68] Toggle invalid states
---
src/components/forms/Toggle.tsx | 159 ++++++++++++++++++---------
src/view/screens/Storybook/Forms.tsx | 12 ++
2 files changed, 117 insertions(+), 54 deletions(-)
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 8a9c53a608..875ccbbed5 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -12,6 +12,7 @@ type ItemState = {
name: string
value: boolean
disabled: boolean
+ hasError: boolean
hovered: boolean
pressed: boolean
focused: boolean
@@ -22,6 +23,7 @@ const ItemContext = React.createContext({
name: '',
value: false,
disabled: false,
+ hasError: false,
hovered: false,
pressed: false,
focused: false,
@@ -80,11 +82,12 @@ function Item({
name,
value,
disabled: disabled ?? false,
+ hasError: hasError ?? false,
hovered,
pressed,
focused,
}),
- [labelId, name, value, disabled, hovered, pressed, focused],
+ [labelId, name, value, disabled, hovered, pressed, focused, hasError],
)
return (
@@ -235,47 +238,87 @@ function createSharedToggleStyles({
focused,
value,
disabled,
+ hasError,
}: {
theme: ReturnType
value: boolean
hovered: boolean
focused: boolean
disabled: boolean
+ hasError: boolean
}) {
- return [
- hovered || focused
- ? {
- backgroundColor: t.palette.contrast_50,
- borderColor: t.palette.contrast_500,
- }
- : {},
- value
- ? {
- backgroundColor:
- t.name === 'light' ? t.palette.primary_25 : t.palette.primary_900,
- borderColor: t.palette.primary_500,
- }
- : {},
- value && (hovered || focused)
- ? {
- backgroundColor:
- t.name === 'light' ? t.palette.primary_100 : t.palette.primary_800,
- borderColor:
- t.name === 'light' ? t.palette.primary_600 : t.palette.primary_400,
- }
- : {},
- disabled
- ? {
- backgroundColor: t.palette.contrast_200,
- borderColor: t.palette.contrast_300,
- }
- : {},
- ]
+ const base: ViewStyle[] = []
+ const baseHover: ViewStyle[] = []
+ const indicator: ViewStyle[] = []
+
+ if (value) {
+ base.push({
+ backgroundColor:
+ t.name === 'light' ? t.palette.primary_25 : t.palette.primary_900,
+ borderColor: t.palette.primary_500,
+ })
+
+ if (hovered || focused) {
+ baseHover.push({
+ backgroundColor:
+ t.name === 'light' ? t.palette.primary_100 : t.palette.primary_800,
+ borderColor:
+ t.name === 'light' ? t.palette.primary_600 : t.palette.primary_400,
+ })
+ }
+ } else {
+ if (hovered || focused) {
+ baseHover.push({
+ backgroundColor: t.palette.contrast_50,
+ borderColor: t.palette.contrast_500,
+ })
+ }
+ }
+
+ if (hasError) {
+ base.push({
+ backgroundColor:
+ t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
+ borderColor:
+ t.name === 'light' ? t.palette.negative_300 : t.palette.negative_800,
+ })
+
+ if (hovered || focused) {
+ baseHover.push({
+ backgroundColor:
+ t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
+ borderColor: t.palette.negative_500,
+ })
+ }
+ }
+
+ if (disabled) {
+ base.push({
+ backgroundColor: t.palette.contrast_200,
+ borderColor: t.palette.contrast_300,
+ })
+ }
+
+ return {
+ baseStyles: base,
+ baseHoverStyles: baseHover,
+ indicatorStyles: indicator,
+ }
}
function Checkbox() {
const t = useTheme()
- const {value, hovered, focused, disabled} = React.useContext(ItemContext)
+ const {value, hovered, focused, disabled, hasError} =
+ React.useContext(ItemContext)
+ const {baseStyles, baseHoverStyles, indicatorStyles} =
+ createSharedToggleStyles({
+ theme: t,
+ hovered,
+ focused,
+ value,
+ disabled,
+ hasError,
+ })
return (
{value ? (
) : null}
@@ -318,7 +357,17 @@ function Checkbox() {
function Switch() {
const t = useTheme()
- const {value, hovered, focused, disabled} = React.useContext(ItemContext)
+ const {value, hovered, focused, disabled, hasError} =
+ React.useContext(ItemContext)
+ const {baseStyles, baseHoverStyles, indicatorStyles} =
+ createSharedToggleStyles({
+ theme: t,
+ hovered,
+ focused,
+ value,
+ disabled,
+ hasError,
+ })
return (
@@ -364,7 +409,17 @@ function Switch() {
function Radio() {
const t = useTheme()
- const {value, hovered, focused, disabled} = React.useContext(ItemContext)
+ const {value, hovered, focused, disabled, hasError} =
+ React.useContext(ItemContext)
+ const {baseStyles, baseHoverStyles, indicatorStyles} =
+ createSharedToggleStyles({
+ theme: t,
+ hovered,
+ focused,
+ value,
+ disabled,
+ hasError,
+ })
return (
{value ? (
) : null}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index a6513641be..bd15e7fec4 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -146,6 +146,10 @@ export function Forms() {
Click me
+
+
+ Click me
+
Click me
+
+
+ Click me
+
Click me
+
+
+ Click me
+
From 7c2b586467869947cb2ef32ef7a97b3c0ac52548 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 16 Jan 2024 14:03:08 -0600
Subject: [PATCH 43/68] Update dialog descriptions for demo
---
src/view/screens/Storybook/Dialogs.tsx | 14 ++++++++------
src/view/screens/Storybook/index.tsx | 2 +-
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index 5fd8856e55..56492be325 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -12,7 +12,7 @@ export function Dialogs() {
const prompt = Prompt.usePromptControl()
return (
- <>
+
- Are you sure?
+ This is a prompt
- This action cannot be undone. This action cannot be undone. This
- action cannot be undone.
+ This is a generic prompt component. It accepts a title and a
+ description, as well as two actions.
Cancel
@@ -55,7 +55,9 @@ export function Dialogs() {
accessibilityLabelledBy="dialog-title">
Dialog
- Description
+
+ A scrollable dialog with an input within it.
+
- >
+
)
}
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index 2cd956becb..fa07cd9d79 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -53,6 +53,7 @@ export function Storybook() {
Dark
+
@@ -70,7 +71,6 @@ export function Storybook() {
-
From 2d80844b4ea68c5f9cd7c8c431198832b8a29beb Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 16 Jan 2024 15:45:43 -0600
Subject: [PATCH 44/68] Icons
---
.../arrowTopRight_stoke2_corner0_rounded.svg | 1 +
.../icons/globe_stroke2_corner0_rounded.svg | 1 +
src/components/icons/ArrowTopRight.tsx | 27 ++++++++++++++++
src/components/icons/Globe.tsx | 27 ++++++++++++++++
src/components/icons/TEMPLATE.tsx | 27 ++++++++++++++++
src/components/icons/common.ts | 32 +++++++++++++++++++
src/view/screens/Storybook/Icons.tsx | 32 +++++++++++++++++++
src/view/screens/Storybook/index.tsx | 4 ++-
8 files changed, 150 insertions(+), 1 deletion(-)
create mode 100644 assets/icons/arrowTopRight_stoke2_corner0_rounded.svg
create mode 100644 assets/icons/globe_stroke2_corner0_rounded.svg
create mode 100644 src/components/icons/ArrowTopRight.tsx
create mode 100644 src/components/icons/Globe.tsx
create mode 100644 src/components/icons/TEMPLATE.tsx
create mode 100644 src/components/icons/common.ts
create mode 100644 src/view/screens/Storybook/Icons.tsx
diff --git a/assets/icons/arrowTopRight_stoke2_corner0_rounded.svg b/assets/icons/arrowTopRight_stoke2_corner0_rounded.svg
new file mode 100644
index 0000000000..554a7374ec
--- /dev/null
+++ b/assets/icons/arrowTopRight_stoke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/globe_stroke2_corner0_rounded.svg b/assets/icons/globe_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..83cb88d136
--- /dev/null
+++ b/assets/icons/globe_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/icons/ArrowTopRight.tsx b/src/components/icons/ArrowTopRight.tsx
new file mode 100644
index 0000000000..ca29730203
--- /dev/null
+++ b/src/components/icons/ArrowTopRight.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import Svg, {Path} from 'react-native-svg'
+
+import {useCommonSVGProps, Props} from '#/components/icons/common'
+
+export const ArrowTopRight_Stroke2_Corner0_Rounded = React.forwardRef(
+ function LogoImpl(props: Props, ref) {
+ const {fill, size, style, ...rest} = useCommonSVGProps(props)
+
+ return (
+
+ )
+ },
+)
diff --git a/src/components/icons/Globe.tsx b/src/components/icons/Globe.tsx
new file mode 100644
index 0000000000..3801607ea3
--- /dev/null
+++ b/src/components/icons/Globe.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import Svg, {Path} from 'react-native-svg'
+
+import {useCommonSVGProps, Props} from '#/components/icons/common'
+
+export const Globe_Stroke2_Corner0_Rounded = React.forwardRef(function LogoImpl(
+ props: Props,
+ ref,
+) {
+ const {fill, size, style, ...rest} = useCommonSVGProps(props)
+
+ return (
+
+ )
+})
diff --git a/src/components/icons/TEMPLATE.tsx b/src/components/icons/TEMPLATE.tsx
new file mode 100644
index 0000000000..c3e53336bd
--- /dev/null
+++ b/src/components/icons/TEMPLATE.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import Svg, {Path} from 'react-native-svg'
+
+import {useCommonSVGProps, Props} from '#/components/icons/common'
+
+export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef(
+ function LogoImpl(props: Props, ref) {
+ const {fill, size, style, ...rest} = useCommonSVGProps(props)
+
+ return (
+
+ )
+ },
+)
diff --git a/src/components/icons/common.ts b/src/components/icons/common.ts
new file mode 100644
index 0000000000..2eaafb2cd1
--- /dev/null
+++ b/src/components/icons/common.ts
@@ -0,0 +1,32 @@
+import {StyleSheet, TextProps} from 'react-native'
+import type {SvgProps, PathProps} from 'react-native-svg'
+
+import {tokens} from '#/alf'
+
+export type Props = {
+ fill?: PathProps['fill']
+ style?: TextProps['style']
+ size?: keyof typeof sizes
+} & Omit
+
+export const sizes = {
+ xs: 12,
+ sm: 16,
+ md: 20,
+ lg: 24,
+ xl: 28,
+}
+
+export function useCommonSVGProps(props: Props) {
+ const {fill, size, ...rest} = props
+ const style = StyleSheet.flatten(rest.style)
+ const _fill = fill || style?.color || tokens.color.blue_500
+ const _size = Number(size ? sizes[size] : rest.width || sizes.md)
+
+ return {
+ fill: _fill,
+ size: _size,
+ style,
+ ...rest,
+ }
+}
diff --git a/src/view/screens/Storybook/Icons.tsx b/src/view/screens/Storybook/Icons.tsx
new file mode 100644
index 0000000000..c3c41ce7e9
--- /dev/null
+++ b/src/view/screens/Storybook/Icons.tsx
@@ -0,0 +1,32 @@
+import React from 'react'
+import {View} from 'react-native'
+
+import {atoms as a, useTheme} from '#/alf'
+import {H1} from '#/components/Typography'
+import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
+import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
+
+export function Icons() {
+ const t = useTheme()
+ return (
+
+ Icons
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index fa07cd9d79..9f627ad2c2 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -15,6 +15,7 @@ import {Forms} from './Forms'
import {Dialogs} from './Dialogs'
import {Breakpoints} from './Breakpoints'
import {Shadows} from './Shadows'
+import {Icons} from './Icons'
export function Storybook() {
const t = useTheme()
@@ -53,7 +54,6 @@ export function Storybook() {
Dark
-
@@ -69,8 +69,10 @@ export function Storybook() {
+
+
From 0fc575084b970f2b2b2a86d4da53d51aac6a4de7 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 17 Jan 2024 11:18:55 -0600
Subject: [PATCH 45/68] WIP Toggle cleanup
---
src/alf/themes.ts | 2 +-
src/components/forms/Toggle.tsx | 297 +++++++++++++++------------
src/view/screens/Storybook/Forms.tsx | 59 ++++--
3 files changed, 213 insertions(+), 145 deletions(-)
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index dc45944563..08aee75512 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -152,7 +152,7 @@ export const light = {
backgroundColor: lightPalette.contrast_300,
},
border: {
- borderColor: lightPalette.contrast_100,
+ borderColor: lightPalette.contrast_200,
},
border_contrast: {
borderColor: lightPalette.contrast_400,
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 875ccbbed5..0b07c88987 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -5,12 +5,10 @@ import {useTheme, atoms as a, web} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {StyleProp} from 'react-native'
-import {AccessibilityRole} from 'react-native'
type ItemState = {
- id: string
name: string
- value: boolean
+ selected: boolean
disabled: boolean
hasError: boolean
hovered: boolean
@@ -19,9 +17,8 @@ type ItemState = {
}
const ItemContext = React.createContext({
- id: '',
name: '',
- value: false,
+ selected: false,
disabled: false,
hasError: false,
hovered: false,
@@ -32,15 +29,32 @@ const ItemContext = React.createContext({
const GroupContext = React.createContext<{
values: string[]
disabled: boolean
- role?: 'radio' | 'checkbox'
+ type: 'radio' | 'checkbox'
+ setFieldValue: (props: {name: string; value: boolean}) => void
}>({
+ type: 'checkbox',
values: [],
disabled: false,
- role: 'checkbox',
+ setFieldValue: () => {},
})
-type ItemProps = Omit & {
+export type GroupProps = React.PropsWithChildren<{
+ type?: 'radio' | 'checkbox'
+ values: string[]
+ maxSelections?: number
+ disabled?: boolean
+ onChange: (value: string[]) => void
+ label: string
+ style?: StyleProp
+}>
+
+export type ItemProps = Omit<
+ PressableProps,
+ 'children' | 'style' | 'onPress' | 'role'
+> & {
+ type?: 'radio' | 'checkbox'
name: string
+ label: string
value?: boolean
onChange?: ({name, value}: {name: string; value: boolean}) => void
hasError?: boolean
@@ -48,109 +62,31 @@ type ItemProps = Omit & {
children: ((props: ItemState) => React.ReactNode) | React.ReactNode
}
-function Item({
- children,
- name,
- value = false,
- disabled,
- onChange,
- hasError,
- style,
- role,
-}: ItemProps) {
- const labelId = React.useId()
- const {
- state: hovered,
- onIn: onHoverIn,
- onOut: onHoverOut,
- } = useInteractionState()
- const {
- state: pressed,
- onIn: onPressIn,
- onOut: onPressOut,
- } = useInteractionState()
- const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
-
- const onPress = React.useCallback(() => {
- const next = !value
- onChange?.({name, value: next})
- }, [name, value, onChange])
-
- const state = React.useMemo(
- () => ({
- id: labelId,
- name,
- value,
- disabled: disabled ?? false,
- hasError: hasError ?? false,
- hovered,
- pressed,
- focused,
- }),
- [labelId, name, value, disabled, hovered, pressed, focused, hasError],
- )
-
- return (
-
-
- {typeof children === 'function' ? children(state) : children}
-
-
- )
-}
-
function Group({
children,
values: initialValues,
onChange,
- disabled,
- role = 'checkbox',
+ disabled = false,
+ type = 'checkbox',
maxSelections,
style,
-}: React.PropsWithChildren<{
- values: string[]
- onChange: (value: string[]) => void
- disabled?: boolean
- role?: 'radio' | 'checkbox'
- maxSelections?: number
- style?: StyleProp
-}>) {
- const _disabled = disabled ?? false
+ label,
+}: GroupProps) {
+ if (!initialValues) {
+ throw new Error(`Don't forget to pass in 'values' to your Toggle.Group`)
+ }
+
+ const groupRole = type === 'radio' ? 'radiogroup' : undefined
const [values, setValues] = React.useState(
- role === 'radio' ? initialValues.slice(0, 1) : initialValues,
+ type === 'radio' ? initialValues.slice(0, 1) : initialValues,
)
const [maxReached, setMaxReached] = React.useState(false)
- const itemOnChange = React.useCallback<
+ const setFieldValue = React.useCallback<
Exclude
>(
({name, value}) => {
- if (role === 'checkbox') {
+ if (type === 'checkbox') {
setValues(s => {
const state = s.filter(v => v !== name)
return value ? state.concat(name) : state
@@ -159,7 +95,7 @@ function Group({
setValues([name])
}
},
- [role, setValues],
+ [type, setValues],
)
React.useEffect(() => {
@@ -167,7 +103,7 @@ function Group({
}, [values, onChange])
React.useEffect(() => {
- if (role === 'checkbox') {
+ if (type === 'checkbox') {
if (
maxSelections &&
values.length >= maxSelections &&
@@ -182,16 +118,35 @@ function Group({
setMaxReached(false)
}
}
- }, [role, values.length, maxSelections, maxReached, setMaxReached])
+ }, [type, values.length, maxSelections, maxReached, setMaxReached])
+
+ const context = React.useMemo(
+ () => ({
+ values,
+ type,
+ disabled,
+ setFieldValue,
+ }),
+ [values, disabled, type, setFieldValue],
+ )
return (
-
-
+
+
{React.Children.map(children, child => {
if (!React.isValidElement(child)) return null
const isSelected = values.includes(child.props.name)
- let isDisabled = _disabled || child.props.disabled
+ let isDisabled = disabled || child.props.disabled
if (maxReached && !isSelected) {
isDisabled = true
@@ -202,9 +157,9 @@ function Group({
{React.cloneElement(child, {
// @ts-ignore TODO figure out children types
disabled: isDisabled,
- role: role === 'radio' ? 'radio' : 'checkbox',
+ type: type === 'radio' ? 'radio' : 'checkbox',
value: isSelected,
- onChange: itemOnChange,
+ onChange: setFieldValue,
})}
) : null
@@ -214,12 +169,102 @@ function Group({
)
}
+function Item({
+ children,
+ name,
+ value = false,
+ disabled: itemDisabled = false,
+ onChange,
+ hasError,
+ style,
+ type = 'checkbox',
+ label,
+ ...rest
+}: ItemProps) {
+ // context can be empty if used outside a Group
+ const {
+ values: selectedValues,
+ type: groupType,
+ disabled: groupDisabled,
+ setFieldValue,
+ } = React.useContext(GroupContext)
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
+ const {
+ state: pressed,
+ onIn: onPressIn,
+ onOut: onPressOut,
+ } = useInteractionState()
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+
+ const role = groupType === 'radio' ? 'radio' : type
+ const selected = selectedValues.includes(name) || !!value
+ const disabled = groupDisabled || itemDisabled
+
+ const onPress = React.useCallback(() => {
+ const next = !value
+ setFieldValue({name, value: next})
+ onChange?.({name, value: next}) // TODO don't use confusing method
+ }, [name, value, onChange, setFieldValue])
+
+ const state = React.useMemo(
+ () => ({
+ name,
+ selected,
+ disabled: disabled ?? false,
+ hasError: hasError ?? false,
+ hovered,
+ pressed,
+ focused,
+ }),
+ [name, selected, disabled, hovered, pressed, focused, hasError],
+ )
+
+ return (
+
+
+ {typeof children === 'function' ? children(state) : children}
+
+
+ )
+}
+
function Label({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
- const {id, disabled} = React.useContext(ItemContext)
+ const {disabled} = React.useContext(ItemContext)
return (
- value: boolean
+ selected: boolean
hovered: boolean
focused: boolean
disabled: boolean
@@ -251,7 +296,7 @@ function createSharedToggleStyles({
const baseHover: ViewStyle[] = []
const indicator: ViewStyle[] = []
- if (value) {
+ if (selected) {
base.push({
backgroundColor:
t.name === 'light' ? t.palette.primary_25 : t.palette.primary_900,
@@ -308,14 +353,14 @@ function createSharedToggleStyles({
function Checkbox() {
const t = useTheme()
- const {value, hovered, focused, disabled, hasError} =
+ const {selected, hovered, focused, disabled, hasError} =
React.useContext(ItemContext)
const {baseStyles, baseHoverStyles, indicatorStyles} =
createSharedToggleStyles({
theme: t,
hovered,
focused,
- value,
+ selected,
disabled,
hasError,
})
@@ -330,19 +375,19 @@ function Checkbox() {
{
height: 20,
width: 20,
- backgroundColor: value ? t.palette.primary_500 : undefined,
- borderColor: value ? t.palette.primary_500 : undefined,
+ backgroundColor: selected ? t.palette.primary_500 : undefined,
+ borderColor: selected ? t.palette.primary_500 : undefined,
},
baseStyles,
hovered || focused ? baseHoverStyles : {},
]}>
- {value ? (
+ {selected ? (
- {value ? (
+ {selected ? (
Toggles
console.log(e)}
style={[a.gap_sm]}>
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
console.log(e)}
style={[a.gap_sm]}>
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
console.log(e)}
style={[a.gap_sm]}>
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
+
+
+ ToggleButton
+
+ console.log(e)}>
+
+ Hide
+
+
+ Warn
+
+
+ Show
+
+
+
)
}
From f520214738bc7caae5b9d71266ba3fa20dafd2b6 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 17 Jan 2024 11:31:46 -0600
Subject: [PATCH 46/68] Refactor toggle to not rely on immediate children
---
src/components/forms/Toggle.tsx | 43 +++++++++-------------------
src/view/screens/Storybook/Forms.tsx | 5 ++++
2 files changed, 18 insertions(+), 30 deletions(-)
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 0b07c88987..0e913854a6 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -30,11 +30,13 @@ const GroupContext = React.createContext<{
values: string[]
disabled: boolean
type: 'radio' | 'checkbox'
+ maxSelectionsReached: boolean
setFieldValue: (props: {name: string; value: boolean}) => void
}>({
type: 'checkbox',
values: [],
disabled: false,
+ maxSelectionsReached: false,
setFieldValue: () => {},
})
@@ -56,7 +58,7 @@ export type ItemProps = Omit<
name: string
label: string
value?: boolean
- onChange?: ({name, value}: {name: string; value: boolean}) => void
+ onChange?: (selected: boolean) => void
hasError?: boolean
style?: (state: ItemState) => ViewStyle
children: ((props: ItemState) => React.ReactNode) | React.ReactNode
@@ -83,7 +85,7 @@ function Group({
const [maxReached, setMaxReached] = React.useState(false)
const setFieldValue = React.useCallback<
- Exclude
+ (props: {name: string; value: boolean}) => void
>(
({name, value}) => {
if (type === 'checkbox') {
@@ -125,9 +127,10 @@ function Group({
values,
type,
disabled,
+ maxSelectionsReached: maxReached,
setFieldValue,
}),
- [values, disabled, type, setFieldValue],
+ [values, disabled, type, maxReached, setFieldValue],
)
return (
@@ -142,28 +145,7 @@ function Group({
accessibilityRole: groupRole,
}
: {})}>
- {React.Children.map(children, child => {
- if (!React.isValidElement(child)) return null
-
- const isSelected = values.includes(child.props.name)
- let isDisabled = disabled || child.props.disabled
-
- if (maxReached && !isSelected) {
- isDisabled = true
- }
-
- return React.isValidElement(child) ? (
-
- {React.cloneElement(child, {
- // @ts-ignore TODO figure out children types
- disabled: isDisabled,
- type: type === 'radio' ? 'radio' : 'checkbox',
- value: isSelected,
- onChange: setFieldValue,
- })}
-
- ) : null
- })}
+ {children}
)
@@ -181,12 +163,12 @@ function Item({
label,
...rest
}: ItemProps) {
- // context can be empty if used outside a Group
const {
values: selectedValues,
type: groupType,
disabled: groupDisabled,
setFieldValue,
+ maxSelectionsReached,
} = React.useContext(GroupContext)
const {
state: hovered,
@@ -202,13 +184,14 @@ function Item({
const role = groupType === 'radio' ? 'radio' : type
const selected = selectedValues.includes(name) || !!value
- const disabled = groupDisabled || itemDisabled
+ const disabled =
+ groupDisabled || itemDisabled || (!selected && maxSelectionsReached)
const onPress = React.useCallback(() => {
- const next = !value
+ const next = !selected
setFieldValue({name, value: next})
- onChange?.({name, value: next}) // TODO don't use confusing method
- }, [name, value, onChange, setFieldValue])
+ onChange?.(next)
+ }, [name, selected, onChange, setFieldValue])
const state = React.useMemo(
() => ({
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index fd0b1ca706..b76509c3a6 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -125,6 +125,11 @@ export function Forms() {
Toggles
+
+
+ Uncontrolled toggle
+
+
Date: Wed, 17 Jan 2024 14:03:00 -0600
Subject: [PATCH 47/68] Make Toggle controlled
---
src/components/forms/Toggle.tsx | 29 ++---
src/components/forms/ToggleButton.tsx | 78 ++++++++++++++
src/view/screens/Storybook/Forms.tsx | 150 ++++++++++++++------------
3 files changed, 168 insertions(+), 89 deletions(-)
create mode 100644 src/components/forms/ToggleButton.tsx
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 0e913854a6..4145895d81 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -4,7 +4,6 @@ import {Pressable, PressableProps, View, ViewStyle} from 'react-native'
import {useTheme, atoms as a, web} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
-import {StyleProp} from 'react-native'
type ItemState = {
name: string
@@ -47,7 +46,6 @@ export type GroupProps = React.PropsWithChildren<{
disabled?: boolean
onChange: (value: string[]) => void
label: string
- style?: StyleProp
}>
export type ItemProps = Omit<
@@ -66,22 +64,15 @@ export type ItemProps = Omit<
function Group({
children,
- values: initialValues,
+ values: providedValues,
onChange,
disabled = false,
type = 'checkbox',
maxSelections,
- style,
label,
}: GroupProps) {
- if (!initialValues) {
- throw new Error(`Don't forget to pass in 'values' to your Toggle.Group`)
- }
-
const groupRole = type === 'radio' ? 'radiogroup' : undefined
- const [values, setValues] = React.useState(
- type === 'radio' ? initialValues.slice(0, 1) : initialValues,
- )
+ const values = type === 'radio' ? providedValues.slice(0, 1) : providedValues
const [maxReached, setMaxReached] = React.useState(false)
const setFieldValue = React.useCallback<
@@ -89,21 +80,16 @@ function Group({
>(
({name, value}) => {
if (type === 'checkbox') {
- setValues(s => {
- const state = s.filter(v => v !== name)
- return value ? state.concat(name) : state
- })
+ const pruned = values.filter(v => v !== name)
+ const next = value ? pruned.concat(name) : pruned
+ onChange(next)
} else {
- setValues([name])
+ onChange([name])
}
},
- [type, setValues],
+ [type, onChange, values],
)
- React.useEffect(() => {
- onChange(values)
- }, [values, onChange])
-
React.useEffect(() => {
if (type === 'checkbox') {
if (
@@ -137,7 +123,6 @@ function Group({
&
+ AccessibilityProps &
+ React.PropsWithChildren<{}>
+
+export type GroupProps = Omit & {
+ multiple?: boolean
+}
+
+export function Group({children, multiple, ...props}: GroupProps) {
+ const t = useTheme()
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+export function Button({children, ...props}: ItemProps) {
+ const t = useTheme()
+ return (
+
+ {state => (
+
+ {typeof children === 'string' ? (
+
+ {children}
+
+ ) : (
+ children
+ )}
+
+ )}
+
+ )
+}
+
+export default {
+ Group,
+ Button,
+}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index b76509c3a6..1c2a9f4c46 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -9,8 +9,11 @@ import {InputGroup} from '#/components/forms/InputGroup'
import {Logo} from '#/view/icons/Logo'
import Toggle from '#/components/forms/Toggle'
import ToggleButton from '#/components/forms/ToggleButton'
+import {Button} from '#/components/Button'
export function Forms() {
+ const [toggleGroupValues, setToggleGroupValues] = React.useState(['a'])
+
return (
Forms
@@ -135,85 +138,98 @@ export function Forms() {
type="checkbox"
maxSelections={2}
values={['a', 'b']}
- onChange={e => console.log(e)}
- style={[a.gap_sm]}>
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
+ onChange={e => console.log(e)}>
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
console.log(e)}
- style={[a.gap_sm]}>
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
+ values={toggleGroupValues}
+ onChange={setToggleGroupValues}>
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+ setToggleGroupValues(['a'])}>
+ Reset
+
+
console.log(e)}
- style={[a.gap_sm]}>
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
-
-
- Click me
-
+ onChange={e => console.log(e)}>
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
+
+ Click me
+
+
From d3d329dc500be1f025fed2ded79cd9bbd58c9afe Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 17 Jan 2024 14:09:59 -0600
Subject: [PATCH 48/68] Clean up Toggles storybook
---
src/components/forms/Toggle.tsx | 10 ++---
src/view/screens/Storybook/Forms.tsx | 56 ++++++++++++++--------------
2 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 4145895d81..7f506f862a 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {Pressable, PressableProps, View, ViewStyle} from 'react-native'
+import {Pressable, View, ViewStyle} from 'react-native'
import {useTheme, atoms as a, web} from '#/alf'
import {Text} from '#/components/Typography'
@@ -48,14 +48,12 @@ export type GroupProps = React.PropsWithChildren<{
label: string
}>
-export type ItemProps = Omit<
- PressableProps,
- 'children' | 'style' | 'onPress' | 'role'
-> & {
+export type ItemProps = {
type?: 'radio' | 'checkbox'
name: string
label: string
value?: boolean
+ disabled?: boolean
onChange?: (selected: boolean) => void
hasError?: boolean
style?: (state: ItemState) => ViewStyle
@@ -314,7 +312,7 @@ function createSharedToggleStyles({
return {
baseStyles: base,
- baseHoverStyles: baseHover,
+ baseHoverStyles: disabled ? [] : baseHover,
indicatorStyles: indicator,
}
}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 1c2a9f4c46..d02a4bfe7a 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -12,7 +12,9 @@ import ToggleButton from '#/components/forms/ToggleButton'
import {Button} from '#/components/Button'
export function Forms() {
- const [toggleGroupValues, setToggleGroupValues] = React.useState(['a'])
+ const [toggleGroupAValues, setToggleGroupAValues] = React.useState(['a'])
+ const [toggleGroupBValues, setToggleGroupBValues] = React.useState(['a', 'b'])
+ const [toggleGroupCValues, setToggleGroupCValues] = React.useState(['a', 'b'])
return (
@@ -137,29 +139,39 @@ export function Forms() {
label="Toggle"
type="checkbox"
maxSelections={2}
- values={['a', 'b']}
- onChange={e => console.log(e)}>
+ values={toggleGroupAValues}
+ onChange={setToggleGroupAValues}>
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
+
+ setToggleGroupAValues(['a'])}>
+ Reset
+
@@ -167,47 +179,37 @@ export function Forms() {
label="Toggle"
type="checkbox"
maxSelections={2}
- values={toggleGroupValues}
- onChange={setToggleGroupValues}>
+ values={toggleGroupBValues}
+ onChange={setToggleGroupBValues}>
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
-
+
Click me
-
- setToggleGroupValues(['a'])}>
- Reset
-
console.log(e)}>
+ values={toggleGroupCValues}
+ onChange={setToggleGroupCValues}>
From 623047977beacd1b0aace7380a5ff1e41c888911 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 17 Jan 2024 14:53:51 -0600
Subject: [PATCH 49/68] ToggleButton styles
---
src/components/forms/Toggle.tsx | 12 +--
src/components/forms/ToggleButton.tsx | 108 +++++++++++++++++++-------
src/view/screens/Storybook/Forms.tsx | 5 +-
3 files changed, 89 insertions(+), 36 deletions(-)
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 7f506f862a..ed678e7fd6 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -60,6 +60,10 @@ export type ItemProps = {
children: ((props: ItemState) => React.ReactNode) | React.ReactNode
}
+export function useItemContext() {
+ return React.useContext(ItemContext)
+}
+
function Group({
children,
values: providedValues,
@@ -228,7 +232,7 @@ function Item({
function Label({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
- const {disabled} = React.useContext(ItemContext)
+ const {disabled} = useItemContext()
return (
&
@@ -36,39 +37,88 @@ export function Group({children, multiple, ...props}: GroupProps) {
}
export function Button({children, ...props}: ItemProps) {
- const t = useTheme()
return (
- {state => (
- {children}
+
+ )
+}
+
+function ButtonInner({children}: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ const state = useItemContext()
+
+ const {baseStyles, hoverStyles, activeStyles, textStyles} =
+ React.useMemo(() => {
+ const base: ViewStyle[] = []
+ const hover: ViewStyle[] = []
+ const active: ViewStyle[] = []
+ const text: TextStyle[] = []
+
+ hover.push(t.atoms.bg_contrast_100)
+
+ if (state.selected) {
+ active.push({
+ backgroundColor: t.palette.contrast_800,
+ })
+ text.push(t.atoms.text_inverted)
+ hover.push({
+ backgroundColor: t.palette.contrast_800,
+ })
+
+ if (state.disabled) {
+ active.push({
+ backgroundColor: t.palette.contrast_500,
+ })
+ }
+ }
+
+ if (state.disabled) {
+ base.push({
+ backgroundColor: t.palette.contrast_100,
+ })
+ text.push({
+ opacity: 0.5,
+ })
+ }
+
+ return {
+ baseStyles: base,
+ hoverStyles: hover,
+ activeStyles: active,
+ textStyles: text,
+ }
+ }, [t, state])
+
+ return (
+
+ {typeof children === 'string' ? (
+
- {typeof children === 'string' ? (
-
- {children}
-
- ) : (
- children
- )}
-
+ {children}
+
+ ) : (
+ children
)}
-
+
)
}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index d02a4bfe7a..c7dd998b6e 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -15,6 +15,7 @@ export function Forms() {
const [toggleGroupAValues, setToggleGroupAValues] = React.useState(['a'])
const [toggleGroupBValues, setToggleGroupBValues] = React.useState(['a', 'b'])
const [toggleGroupCValues, setToggleGroupCValues] = React.useState(['a', 'b'])
+ const [toggleGroupDValues, setToggleGroupDValues] = React.useState(['warn'])
return (
@@ -240,8 +241,8 @@ export function Forms() {
console.log(e)}>
+ values={toggleGroupDValues}
+ onChange={setToggleGroupDValues}>
Hide
From 449c29b94b91b1ab2aac5224dae54b280c053af8 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 17 Jan 2024 15:22:28 -0600
Subject: [PATCH 50/68] Improve a11y labels
---
src/components/Button.tsx | 18 +++++++-----------
src/components/Dialog/index.web.tsx | 5 +++--
src/components/Prompt.tsx | 6 ++----
src/components/forms/Toggle.tsx | 2 +-
src/view/screens/Storybook/Buttons.tsx | 18 ++++++------------
src/view/screens/Storybook/Forms.tsx | 3 +--
src/view/screens/Storybook/Links.tsx | 15 +++++----------
7 files changed, 25 insertions(+), 42 deletions(-)
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 7c032dbad8..06226ad1e0 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -38,10 +38,8 @@ export type VariantProps = {
size?: ButtonSize
}
-export type ButtonProps = Omit<
- PressableProps,
- 'children' | 'style' | 'accessibilityLabel' | 'accessibilityHint'
-> &
+export type ButtonProps = Pick &
+ AccessibilityProps &
VariantProps & {
children:
| ((props: {
@@ -56,8 +54,7 @@ export type ButtonProps = Omit<
}) => React.ReactNode)
| React.ReactNode
| string
- accessibilityLabel: Required['accessibilityLabel']
- accessibilityHint: Required['accessibilityHint']
+ label: string
}
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
@@ -66,8 +63,7 @@ export function Button({
variant,
color,
size,
- accessibilityLabel,
- accessibilityHint,
+ label,
disabled = false,
...rest
}: ButtonProps) {
@@ -325,11 +321,11 @@ export function Button({
return (
{children}
@@ -108,8 +107,7 @@ export function Action({
variant="solid"
color="primary"
size="small"
- accessibilityLabel="Confirm"
- accessibilityHint="Confirm this action"
+ label="Confirm"
onPress={handleOnPress}>
{children}
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index ed678e7fd6..8fe43f2f41 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -196,6 +196,7 @@ function Item({
return (
+ label="Click here">
Button
+ label="Click here">
Button
@@ -46,8 +44,7 @@ export function Buttons() {
variant="gradient"
color={name as ButtonColor}
size="large"
- accessibilityLabel="Click here"
- accessibilityHint="Opens something">
+ label="Click here">
Button
+ label="Click here">
Button
@@ -71,8 +67,7 @@ export function Buttons() {
variant="gradient"
color={name as ButtonColor}
size="large"
- accessibilityLabel="Click here"
- accessibilityHint="Opens something">
+ label="Click here">
Button
+ label="Click here">
Button
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index c7dd998b6e..66268cb802 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -168,8 +168,7 @@ export function Forms() {
variant="solid"
color="primary"
size="small"
- accessibilityLabel="Reset"
- accessibilityHint="Reset"
+ label="Reset"
onPress={() => setToggleGroupAValues(['a'])}>
Reset
diff --git a/src/view/screens/Storybook/Links.tsx b/src/view/screens/Storybook/Links.tsx
index b5c183425a..2b9da2a9de 100644
--- a/src/view/screens/Storybook/Links.tsx
+++ b/src/view/screens/Storybook/Links.tsx
@@ -13,31 +13,27 @@ export function Links() {
External
External with custom children
https://blueskyweb.xyz
@@ -45,8 +41,7 @@ export function Links() {
Date: Wed, 17 Jan 2024 15:23:52 -0600
Subject: [PATCH 51/68] ToggleButton hover darkmode
---
src/components/forms/ToggleButton.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/components/forms/ToggleButton.tsx b/src/components/forms/ToggleButton.tsx
index 01edb45ad1..f6578c90e2 100644
--- a/src/components/forms/ToggleButton.tsx
+++ b/src/components/forms/ToggleButton.tsx
@@ -55,7 +55,9 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
const active: ViewStyle[] = []
const text: TextStyle[] = []
- hover.push(t.atoms.bg_contrast_100)
+ hover.push(
+ t.name === 'light' ? t.atoms.bg_contrast_100 : t.atoms.bg_contrast_25,
+ )
if (state.selected) {
active.push({
From 13409f889c6114b98d5f40aa26b7f7f12e741dd2 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 17 Jan 2024 15:30:52 -0600
Subject: [PATCH 52/68] Some i18n
---
src/components/Dialog/index.web.tsx | 11 ++++++-----
src/components/Prompt.tsx | 8 ++++++--
src/view/screens/Storybook/Forms.tsx | 22 +++++++++++++---------
3 files changed, 25 insertions(+), 16 deletions(-)
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 88ebbbc1d2..bb7b6d33df 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -2,6 +2,8 @@ import React, {useImperativeHandle} from 'react'
import {View, TouchableWithoutFeedback} from 'react-native'
import {FocusScope} from '@tamagui/focus-scope'
import Animated, {FadeInDown, FadeIn} from 'react-native-reanimated'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
import {useTheme, atoms as a, useBreakpoints, web} from '#/alf'
import {Text} from '#/components/Typography'
@@ -22,6 +24,7 @@ export function Outer({
onClose,
children,
}: React.PropsWithChildren) {
+ const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const [isOpen, setIsOpen] = React.useState(false)
@@ -74,7 +77,7 @@ export function Outer({
-
+
{() => (
) {
export function Cancel({
children,
}: React.PropsWithChildren<{onPress?: PressableProps['onPress']}>) {
+ const {_} = useLingui()
const {close} = Dialog.useDialogContext()
return (
{children}
@@ -97,6 +100,7 @@ export function Action({
children,
onPress,
}: React.PropsWithChildren<{onPress?: () => void}>) {
+ const {_} = useLingui()
const {close} = Dialog.useDialogContext()
const handleOnPress = React.useCallback(() => {
close()
@@ -107,7 +111,7 @@ export function Action({
variant="solid"
color="primary"
size="small"
- label="Confirm"
+ label={_(msg`Confirm`)}
onPress={handleOnPress}>
{children}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 66268cb802..cee24af288 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -163,15 +163,6 @@ export function Forms() {
Click me
-
- setToggleGroupAValues(['a'])}>
- Reset
-
@@ -235,6 +226,19 @@ export function Forms() {
+ {
+ setToggleGroupAValues(['a'])
+ setToggleGroupBValues(['a', 'b'])
+ setToggleGroupCValues(['a'])
+ }}>
+ Reset all toggles
+
+
ToggleButton
From 1e5f213f12837ddecab69424c84891237ea7be8c Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 11:06:54 -0600
Subject: [PATCH 53/68] Refactor input
---
src/components/forms/InputText.tsx | 52 ++--
src/components/forms/TextField.tsx | 316 +++++++++++++++++++++++++
src/components/forms/types.ts | 21 +-
src/components/icons/ArrowTopRight.tsx | 8 +-
src/components/icons/Globe.tsx | 7 +-
src/components/icons/TEMPLATE.tsx | 4 +-
src/view/screens/Storybook/Forms.tsx | 100 +++-----
7 files changed, 394 insertions(+), 114 deletions(-)
create mode 100644 src/components/forms/TextField.tsx
diff --git a/src/components/forms/InputText.tsx b/src/components/forms/InputText.tsx
index 8933b1d085..67b50176f8 100644
--- a/src/components/forms/InputText.tsx
+++ b/src/components/forms/InputText.tsx
@@ -7,7 +7,7 @@ import {
LayoutChangeEvent,
} from 'react-native'
-import {useTheme, atoms, web, tokens} from '#/alf'
+import {useTheme, atoms as a, web, tokens} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
@@ -15,7 +15,7 @@ import {BaseProps} from '#/components/forms/types'
type Props = BaseProps &
Omit & {
- placeholder: Required['placeholder']
+ placeholder?: Required['placeholder']
icon?: React.FunctionComponent
suffix?: React.FunctionComponent
}
@@ -25,8 +25,6 @@ export function createTextInput(Input: typeof TextInput) {
value: initialValue,
onChange,
testID,
- accessibilityLabel,
- accessibilityHint,
label,
hasError,
icon: Icon,
@@ -146,21 +144,22 @@ export function createTextInput(Input: typeof TextInput) {
)
return (
-
+
{label && (
{label}
)}
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
new file mode 100644
index 0000000000..0568caaf61
--- /dev/null
+++ b/src/components/forms/TextField.tsx
@@ -0,0 +1,316 @@
+import React from 'react'
+import {
+ View,
+ TextInput,
+ TextInputProps,
+ TextStyle,
+ ViewStyle,
+ Pressable,
+ StyleSheet,
+ AccessibilityProps,
+} from 'react-native'
+
+import {isWeb} from '#/platform/detection'
+import {useTheme, atoms as a, web, tokens} from '#/alf'
+import {Text} from '#/components/Typography'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
+import {Props as SVGIconProps} from '#/components/icons/common'
+
+const Context = React.createContext<{
+ inputRef: React.RefObject | null
+ isInvalid: boolean
+ hovered: boolean
+ onHoverIn: () => void
+ onHoverOut: () => void
+ focused: boolean
+ onFocus: () => void
+ onBlur: () => void
+}>({
+ inputRef: null,
+ isInvalid: false,
+ hovered: false,
+ onHoverIn: () => {},
+ onHoverOut: () => {},
+ focused: false,
+ onFocus: () => {},
+ onBlur: () => {},
+})
+
+export type RootProps = React.PropsWithChildren<{isInvalid?: boolean}>
+
+function Root({children, isInvalid = false}: RootProps) {
+ const inputRef = React.useRef(null)
+ const rootRef = React.useRef(null)
+ const {
+ state: hovered,
+ onIn: onHoverIn,
+ onOut: onHoverOut,
+ } = useInteractionState()
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+
+ const context = React.useMemo(
+ () => ({
+ inputRef,
+ hovered,
+ onHoverIn,
+ onHoverOut,
+ focused,
+ onFocus,
+ onBlur,
+ isInvalid,
+ }),
+ [
+ inputRef,
+ hovered,
+ onHoverIn,
+ onHoverOut,
+ focused,
+ onFocus,
+ onBlur,
+ isInvalid,
+ ],
+ )
+
+ React.useLayoutEffect(() => {
+ const root = rootRef.current
+ if (!root || !isWeb) return
+ // @ts-ignore web only
+ root.tabIndex = -1
+ }, [])
+
+ return (
+
+ inputRef.current?.focus()}
+ onHoverIn={onHoverIn}
+ onHoverOut={onHoverOut}>
+ {children}
+
+
+ )
+}
+
+function useSharedInputStyles() {
+ const t = useTheme()
+ return React.useMemo(() => {
+ const hover: ViewStyle[] = [
+ {
+ borderColor: t.palette.contrast_100,
+ },
+ ]
+ const focus: ViewStyle[] = [
+ {
+ backgroundColor: t.palette.contrast_50,
+ borderColor: t.palette.primary_500,
+ },
+ ]
+ const error: ViewStyle[] = [
+ {
+ backgroundColor:
+ t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
+ borderColor:
+ t.name === 'light' ? t.palette.negative_300 : t.palette.negative_800,
+ },
+ ]
+ const errorHover: ViewStyle[] = [
+ {
+ backgroundColor:
+ t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
+ borderColor: tokens.color.red_500,
+ },
+ ]
+
+ return {
+ chromeHover: StyleSheet.flatten(hover),
+ chromeFocus: StyleSheet.flatten(focus),
+ chromeError: StyleSheet.flatten(error),
+ chromeErrorHover: StyleSheet.flatten(errorHover),
+ }
+ }, [t])
+}
+
+export type InputProps = Omit & {
+ label: string
+ value: string
+ onChangeText: (value: string) => void
+ isInvalid?: boolean
+}
+
+function Input({
+ label,
+ placeholder,
+ value,
+ onChangeText,
+ isInvalid,
+ ...rest
+}: InputProps) {
+ const t = useTheme()
+ const ctx = React.useContext(Context)
+ const withinRoot = Boolean(ctx.inputRef)
+
+ const {chromeHover, chromeFocus, chromeError, chromeErrorHover} =
+ useSharedInputStyles()
+
+ if (!withinRoot) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
+function Icon({icon: Comp}: {icon: React.ComponentType}) {
+ const t = useTheme()
+ const ctx = React.useContext(Context)
+ const {hover, focus, errorHover, errorFocus} = React.useMemo(() => {
+ const hover: TextStyle[] = [
+ {
+ color: t.palette.contrast_800,
+ },
+ ]
+ const focus: TextStyle[] = [
+ {
+ color: t.palette.primary_500,
+ },
+ ]
+ const errorHover: TextStyle[] = [
+ {
+ color: t.palette.negative_500,
+ },
+ ]
+ const errorFocus: TextStyle[] = [
+ {
+ color: t.palette.negative_500,
+ },
+ ]
+
+ return {
+ hover,
+ focus,
+ errorHover,
+ errorFocus,
+ }
+ }, [t])
+
+ return (
+
+
+
+ )
+}
+
+function Suffix({
+ children,
+ label,
+ accessibilityHint,
+}: React.PropsWithChildren<{
+ label: string
+ accessibilityHint?: AccessibilityProps['accessibilityHint']
+}>) {
+ const t = useTheme()
+ const ctx = React.useContext(Context)
+ return (
+
+ {children}
+
+ )
+}
+
+export default {
+ Root,
+ Input,
+ Icon,
+ Suffix,
+}
diff --git a/src/components/forms/types.ts b/src/components/forms/types.ts
index c43b760b0b..8d94bf0d1c 100644
--- a/src/components/forms/types.ts
+++ b/src/components/forms/types.ts
@@ -2,17 +2,10 @@ import {AccessibilityProps} from 'react-native'
export type RequiredAccessibilityProps = Required
-export type BaseProps = Omit<
- AccessibilityProps,
- 'accessibilityLabel' | 'accessibilityHint'
-> &
- Pick<
- RequiredAccessibilityProps,
- 'accessibilityLabel' | 'accessibilityHint'
- > & {
- value: T
- onChange: (value: T) => void
- testID: string
- label?: string
- hasError?: boolean
- }
+export type BaseProps = AccessibilityProps & {
+ value: T
+ onChange: (value: T) => void
+ testID?: string
+ label: string
+ hasError?: boolean
+}
diff --git a/src/components/icons/ArrowTopRight.tsx b/src/components/icons/ArrowTopRight.tsx
index ca29730203..368c45308d 100644
--- a/src/components/icons/ArrowTopRight.tsx
+++ b/src/components/icons/ArrowTopRight.tsx
@@ -14,11 +14,13 @@ export const ArrowTopRight_Stroke2_Corner0_Rounded = React.forwardRef(
// @ts-ignore it's fiiiiine
ref={ref}
viewBox="0 0 24 24"
- style={[{width: size}, style]}>
+ width={size}
+ height={size}
+ style={[style]}>
diff --git a/src/components/icons/Globe.tsx b/src/components/icons/Globe.tsx
index 3801607ea3..28187266ac 100644
--- a/src/components/icons/Globe.tsx
+++ b/src/components/icons/Globe.tsx
@@ -16,10 +16,13 @@ export const Globe_Stroke2_Corner0_Rounded = React.forwardRef(function LogoImpl(
// @ts-ignore it's fiiiiine
ref={ref}
viewBox="0 0 24 24"
- style={[{width: size}, style]}>
+ width={size}
+ height={size}
+ style={[style]}>
diff --git a/src/components/icons/TEMPLATE.tsx b/src/components/icons/TEMPLATE.tsx
index c3e53336bd..c47849aca4 100644
--- a/src/components/icons/TEMPLATE.tsx
+++ b/src/components/icons/TEMPLATE.tsx
@@ -14,7 +14,9 @@ export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef(
// @ts-ignore it's fiiiiine
ref={ref}
viewBox="0 0 24 24"
- style={[{width: size}, style]}>
+ width={size}
+ height={size}
+ style={[style]}>
Forms
@@ -24,77 +27,43 @@ export function Forms() {
InputText
- console.log(text)}
- />
- console.log(text)}
- />
- console.log(text)}
- icon={Logo}
- />
- console.log(text)}
- icon={Logo}
- />
- console.log(text)}
- icon={Logo}
- suffix={() => .bksy.social}
- />
- console.log(text)}
+
+
+
+
+
+
+
+
+
+ @gmail.com
+
+
InputDate
console.log(date)}
- accessibilityLabel="Date"
- accessibilityHint="Enter a date"
+ label="Input"
/>
console.log(date)}
- accessibilityLabel="Date"
- accessibilityHint="Enter a date"
+ label="Input"
/>
@@ -103,24 +72,21 @@ export function Forms() {
console.log(text)}
/>
console.log(text)}
/>
console.log(text)}
From 43dc77491ee33c7eda55efa4b5ec290078c41b70 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 11:17:27 -0600
Subject: [PATCH 54/68] Allow extension of input
---
src/components/Dialog/index.tsx | 4 +-
src/components/forms/TextField.tsx | 132 +++++++++++++------------
src/view/screens/Storybook/Dialogs.tsx | 18 +---
3 files changed, 75 insertions(+), 79 deletions(-)
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 87d0a08372..44e4dc8a70 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -10,7 +10,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
import {Portal} from '#/components/Portal'
-import {createTextInput} from '#/components/forms/InputText'
+import {createInput} from '#/components/forms/TextField'
import {
DialogOuterProps,
@@ -22,7 +22,7 @@ import {Context} from '#/components/Dialog/context'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
// @ts-ignore
-export const InputText = createTextInput(BottomSheetTextInput)
+export const Input = createInput(BottomSheetTextInput)
export function Outer({
children,
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index 0568caaf61..bcf7dafe0d 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -149,79 +149,85 @@ export type InputProps = Omit & {
isInvalid?: boolean
}
-function Input({
- label,
- placeholder,
- value,
- onChangeText,
- isInvalid,
- ...rest
-}: InputProps) {
- const t = useTheme()
- const ctx = React.useContext(Context)
- const withinRoot = Boolean(ctx.inputRef)
+export function createInput(Component: typeof TextInput) {
+ return function Input({
+ label,
+ placeholder,
+ value,
+ onChangeText,
+ isInvalid,
+ ...rest
+ }: InputProps) {
+ const t = useTheme()
+ const ctx = React.useContext(Context)
+ const withinRoot = Boolean(ctx.inputRef)
- const {chromeHover, chromeFocus, chromeError, chromeErrorHover} =
- useSharedInputStyles()
+ const {chromeHover, chromeFocus, chromeError, chromeErrorHover} =
+ useSharedInputStyles()
+
+ if (!withinRoot) {
+ return (
+
+
+
+ )
+ }
- if (!withinRoot) {
return (
-
-
+
+
+
-
+ >
)
}
-
- return (
- <>
-
-
-
- >
- )
}
+const Input = createInput(TextInput)
+
function Icon({icon: Comp}: {icon: React.ComponentType}) {
const t = useTheme()
const ctx = React.useContext(Context)
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index 56492be325..d4dcc91938 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -18,8 +18,7 @@ export function Dialogs() {
color="secondary"
size="small"
onPress={() => control.open()}
- accessibilityLabel="Open basic dialog"
- accessibilityHint="Open basic dialog">
+ label="Open basic dialog">
Open basic dialog
@@ -28,8 +27,7 @@ export function Dialogs() {
color="primary"
size="small"
onPress={() => prompt.open()}
- accessibilityLabel="Open prompt"
- accessibilityHint="Open prompt">
+ label="Open prompt">
Open prompt
@@ -58,14 +56,7 @@ export function Dialogs() {
A scrollable dialog with an input within it.
- {}}
- placeholder="Type here"
- accessibilityLabel="Type"
- accessibilityHint="Type"
- />
+ {}} label="Type here" />
control.close()}
- accessibilityLabel="Open basic dialog"
- accessibilityHint="Open basic dialog">
+ label="Open basic dialog">
Close basic dialog
From 7faaf1996a8f6edf50098b5852a51f57ef422d57 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 11:19:23 -0600
Subject: [PATCH 55/68] Remove old input
---
src/components/forms/InputText.tsx | 248 ---------------------------
src/view/screens/Storybook/Forms.tsx | 29 ----
2 files changed, 277 deletions(-)
delete mode 100644 src/components/forms/InputText.tsx
diff --git a/src/components/forms/InputText.tsx b/src/components/forms/InputText.tsx
deleted file mode 100644
index 67b50176f8..0000000000
--- a/src/components/forms/InputText.tsx
+++ /dev/null
@@ -1,248 +0,0 @@
-import React from 'react'
-import {
- View,
- TextInput,
- TextInputProps,
- TextStyle,
- LayoutChangeEvent,
-} from 'react-native'
-
-import {useTheme, atoms as a, web, tokens} from '#/alf'
-import {Text} from '#/components/Typography'
-import {useInteractionState} from '#/components/hooks/useInteractionState'
-
-import {BaseProps} from '#/components/forms/types'
-
-type Props = BaseProps &
- Omit & {
- placeholder?: Required['placeholder']
- icon?: React.FunctionComponent
- suffix?: React.FunctionComponent
- }
-
-export function createTextInput(Input: typeof TextInput) {
- return function InputText({
- value: initialValue,
- onChange,
- testID,
- label,
- hasError,
- icon: Icon,
- suffix: Suffix,
- ...props
- }: Props) {
- const labelId = React.useId()
- const t = useTheme()
- const [value, setValue] = React.useState(initialValue)
- const [suffixPadding, setSuffixPadding] = React.useState(0)
- const {
- state: hovered,
- onIn: onHoverIn,
- onOut: onHoverOut,
- } = useInteractionState()
- const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
- const hasIcon = !!Icon
-
- const handleSuffixLayout = React.useCallback(
- (e: LayoutChangeEvent) => {
- setSuffixPadding(e.nativeEvent.layout.width + 16)
- },
- [setSuffixPadding],
- )
-
- const {inputBaseStyles, inputHoverStyles, inputFocusStyles} =
- React.useMemo(() => {
- const base: TextStyle[] = []
- const hover: TextStyle[] = [
- {
- borderColor: t.palette.contrast_300,
- },
- ]
- const focus: TextStyle[] = [
- {
- backgroundColor: t.palette.contrast_50,
- borderColor: t.palette.primary_500,
- },
- ]
-
- if (hasIcon) {
- base.push({
- paddingLeft: 40,
- })
- }
-
- if (hasError) {
- base.push({
- backgroundColor:
- t.name === 'light'
- ? t.palette.negative_25
- : t.palette.negative_900,
- borderColor:
- t.name === 'light'
- ? t.palette.negative_300
- : t.palette.negative_800,
- })
- hover.push({
- borderColor: tokens.color.red_500,
- })
- focus.push({
- backgroundColor:
- t.name === 'light'
- ? t.palette.negative_25
- : t.palette.negative_900,
- borderColor: tokens.color.red_500,
- })
- }
-
- return {
- inputBaseStyles: base,
- inputHoverStyles: hover,
- inputFocusStyles: focus,
- }
- }, [t, hasError, hasIcon])
-
- const {iconBaseStyles, iconHoverStyles, iconFocusStyles} =
- React.useMemo(() => {
- const base: TextStyle[] = []
- const hover: TextStyle[] = [
- {
- color: t.palette.contrast_500,
- },
- ]
- const focus: TextStyle[] = [
- {
- color: t.palette.primary_500,
- },
- ]
-
- if (hasError) {
- base.push({
- color: t.palette.negative_400,
- })
- hover.push({
- color: t.palette.negative_500,
- })
- focus.push({
- color: t.palette.negative_500,
- })
- }
-
- return {
- iconBaseStyles: base,
- iconHoverStyles: hover,
- iconFocusStyles: focus,
- }
- }, [t, hasError])
-
- const handleOnChange = React.useCallback(
- (e: any) => {
- const value = e.currentTarget.value
- onChange(value)
- setValue(value)
- },
- [onChange, setValue],
- )
-
- return (
-
- {label && (
-
- {label}
-
- )}
-
-
-
- {Icon && (
-
-
-
- )}
-
- {Suffix && (
-
-
-
- )}
-
- )
- }
-}
-
-export const InputText = createTextInput(TextInput)
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 9e50a5abc7..68fd1190f9 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -3,10 +3,8 @@ import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {H1, H3} from '#/components/Typography'
-import {InputText} from '#/components/forms/InputText'
import TextField from '#/components/forms/TextField'
import {InputDate, utils} from '#/components/forms/InputDate'
-import {InputGroup} from '#/components/forms/InputGroup'
import Toggle from '#/components/forms/Toggle'
import ToggleButton from '#/components/forms/ToggleButton'
import {Button} from '#/components/Button'
@@ -67,33 +65,6 @@ export function Forms() {
/>
-
- InputGroup (WIP)
-
- console.log(text)}
- />
- console.log(text)}
- />
- console.log(text)}
- />
-
-
-
Toggles
From fb2e366b0e9dd8874045bb6a02c7898ddcb61bd6 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 11:33:01 -0600
Subject: [PATCH 56/68] Improve icons, add CalendarDays
---
.../calendarDays_stroke2_corner0_rounded.svg | 1 +
src/components/Dialog/index.web.tsx | 3 +-
src/components/icons/ArrowTopRight.tsx | 32 +++----------------
src/components/icons/CalendarDays.tsx | 5 +++
src/components/icons/Globe.tsx | 31 ++----------------
src/components/icons/TEMPLATE.tsx | 23 +++++++++++--
src/components/icons/common.ts | 2 +-
src/view/screens/Storybook/Icons.tsx | 9 ++++++
8 files changed, 46 insertions(+), 60 deletions(-)
create mode 100644 assets/icons/calendarDays_stroke2_corner0_rounded.svg
create mode 100644 src/components/icons/CalendarDays.tsx
diff --git a/assets/icons/calendarDays_stroke2_corner0_rounded.svg b/assets/icons/calendarDays_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..09d9c0f490
--- /dev/null
+++ b/assets/icons/calendarDays_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index bb7b6d33df..3350785ec2 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -12,10 +12,11 @@ import {Button} from '#/components/Button'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
import {Context, useDialogContext} from '#/components/Dialog/context'
+import TextField from '#/components/forms/TextField'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
-export {InputText} from '#/components/forms/InputText'
+export const Input = TextField.Input
const stopPropagation = (e: any) => e.stopPropagation()
diff --git a/src/components/icons/ArrowTopRight.tsx b/src/components/icons/ArrowTopRight.tsx
index 368c45308d..92ad30a129 100644
--- a/src/components/icons/ArrowTopRight.tsx
+++ b/src/components/icons/ArrowTopRight.tsx
@@ -1,29 +1,5 @@
-import React from 'react'
-import Svg, {Path} from 'react-native-svg'
+import {createSinglePathSVG} from './TEMPLATE'
-import {useCommonSVGProps, Props} from '#/components/icons/common'
-
-export const ArrowTopRight_Stroke2_Corner0_Rounded = React.forwardRef(
- function LogoImpl(props: Props, ref) {
- const {fill, size, style, ...rest} = useCommonSVGProps(props)
-
- return (
-
- )
- },
-)
+export const ArrowTopRight_Stroke2_Corner0_Rounded = createSinglePathSVG({
+ path: 'M8 6a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v9a1 1 0 1 1-2 0V8.414l-9.793 9.793a1 1 0 0 1-1.414-1.414L15.586 7H9a1 1 0 0 1-1-1Z',
+})
diff --git a/src/components/icons/CalendarDays.tsx b/src/components/icons/CalendarDays.tsx
new file mode 100644
index 0000000000..72cc48e261
--- /dev/null
+++ b/src/components/icons/CalendarDays.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const CalendarDays_Stroke2_Corner0_Rounded = createSinglePathSVG({
+ path: 'M4 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Zm1 16V9h14v10H5ZM5 7h14V5H5v2Zm3 10.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM17.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM12 13.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM9.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM12 17.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z',
+})
diff --git a/src/components/icons/Globe.tsx b/src/components/icons/Globe.tsx
index 28187266ac..f81b3ff7a0 100644
--- a/src/components/icons/Globe.tsx
+++ b/src/components/icons/Globe.tsx
@@ -1,30 +1,5 @@
-import React from 'react'
-import Svg, {Path} from 'react-native-svg'
+import {createSinglePathSVG} from './TEMPLATE'
-import {useCommonSVGProps, Props} from '#/components/icons/common'
-
-export const Globe_Stroke2_Corner0_Rounded = React.forwardRef(function LogoImpl(
- props: Props,
- ref,
-) {
- const {fill, size, style, ...rest} = useCommonSVGProps(props)
-
- return (
-
- )
+export const Globe_Stroke2_Corner0_Rounded = createSinglePathSVG({
+ path: 'M4.062 11h2.961c.103-2.204.545-4.218 1.235-5.77.06-.136.123-.269.188-.399A8.007 8.007 0 0 0 4.062 11ZM12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2Zm0 2c-.227 0-.518.1-.868.432-.354.337-.719.872-1.047 1.61-.561 1.263-.958 2.991-1.06 4.958h5.95c-.102-1.967-.499-3.695-1.06-4.958-.328-.738-.693-1.273-1.047-1.61C12.518 4.099 12.227 4 12 4Zm4.977 7c-.103-2.204-.545-4.218-1.235-5.77a9.78 9.78 0 0 0-.188-.399A8.006 8.006 0 0 1 19.938 11h-2.961Zm-2.003 2H9.026c.101 1.966.498 3.695 1.06 4.958.327.738.692 1.273 1.046 1.61.35.333.641.432.868.432.227 0 .518-.1.868-.432.354-.337.719-.872 1.047-1.61.561-1.263.958-2.991 1.06-4.958Zm.58 6.169c.065-.13.128-.263.188-.399.69-1.552 1.132-3.566 1.235-5.77h2.961a8.006 8.006 0 0 1-4.384 6.169Zm-7.108 0a9.877 9.877 0 0 1-.188-.399c-.69-1.552-1.132-3.566-1.235-5.77H4.062a8.006 8.006 0 0 0 4.384 6.169Z',
})
diff --git a/src/components/icons/TEMPLATE.tsx b/src/components/icons/TEMPLATE.tsx
index c47849aca4..9fc1470375 100644
--- a/src/components/icons/TEMPLATE.tsx
+++ b/src/components/icons/TEMPLATE.tsx
@@ -19,11 +19,30 @@ export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef(
style={[style]}>
)
},
)
+
+export function createSinglePathSVG({path}: {path: string}) {
+ return React.forwardRef
+
+
+
+
+
+
+
+
)
}
From ead02d766c4db751612bf78d35347f533072070e Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 13:14:18 -0600
Subject: [PATCH 57/68] Refactor DateField, web done
---
.../forms/DateField/index.android.tsx | 161 ++++++++++++++++++
src/components/forms/DateField/index.tsx | 63 +++++++
src/components/forms/DateField/index.web.tsx | 71 ++++++++
src/components/forms/DateField/types.ts | 10 ++
src/components/forms/DateField/utils.ts | 16 ++
src/components/forms/TextField.tsx | 16 +-
src/view/screens/Storybook/Forms.tsx | 18 +-
7 files changed, 342 insertions(+), 13 deletions(-)
create mode 100644 src/components/forms/DateField/index.android.tsx
create mode 100644 src/components/forms/DateField/index.tsx
create mode 100644 src/components/forms/DateField/index.web.tsx
create mode 100644 src/components/forms/DateField/types.ts
create mode 100644 src/components/forms/DateField/utils.ts
diff --git a/src/components/forms/DateField/index.android.tsx b/src/components/forms/DateField/index.android.tsx
new file mode 100644
index 0000000000..3547557841
--- /dev/null
+++ b/src/components/forms/DateField/index.android.tsx
@@ -0,0 +1,161 @@
+import React from 'react'
+import {View, TextStyle, Pressable} from 'react-native'
+import DateTimePicker, {
+ BaseProps as DateTimePickerProps,
+} from '@react-native-community/datetimepicker'
+
+import {Logo} from '#/view/icons/Logo'
+import {useTheme, atoms, tokens} from '#/alf'
+import {Text} from '#/components/Typography'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
+
+import {InputDateProps} from '#/components/forms/InputDate/types'
+import {
+ localizeDate,
+ toSimpleDateString,
+} from '#/components/forms/InputDate/utils'
+
+export * as utils from '#/components/forms/InputDate/utils'
+
+export function InputDate({
+ value: initialValue,
+ onChange,
+ testID,
+ label,
+ hasError,
+ accessibilityLabel,
+ accessibilityHint,
+ ...props
+}: InputDateProps) {
+ const labelId = React.useId()
+ const t = useTheme()
+ const [open, setOpen] = React.useState(false)
+ const [value, setValue] = React.useState(initialValue)
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+
+ const {inputStyles, iconStyles} = React.useMemo(() => {
+ const input: TextStyle[] = [
+ {
+ paddingLeft: 40,
+ },
+ ]
+ const icon: TextStyle[] = []
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_200,
+ })
+ icon.push({
+ color: tokens.color.red_400,
+ })
+ }
+
+ if (focused) {
+ input.push({
+ borderColor: t.atoms.border_contrast.borderColor,
+ })
+
+ if (hasError) {
+ input.push({
+ borderColor: tokens.color.red_500,
+ })
+ }
+ }
+
+ return {inputStyles: input, iconStyles: icon}
+ }, [t, focused, hasError])
+
+ const onChangeInternal = React.useCallback<
+ Required['onChange']
+ >(
+ (_event, date) => {
+ setOpen(false)
+
+ if (date) {
+ const formatted = toSimpleDateString(date)
+ onChange(formatted)
+ setValue(formatted)
+ }
+ },
+ [onChange, setOpen, setValue],
+ )
+
+ return (
+
+ {label && (
+
+ {label}
+
+ )}
+
+ setOpen(true)}
+ onFocus={onFocus}
+ onBlur={onBlur}
+ style={[
+ {
+ paddingTop: atoms.pt_md.paddingTop + 2,
+ },
+ atoms.w_full,
+ atoms.px_lg,
+ atoms.pb_md,
+ atoms.rounded_sm,
+ t.atoms.bg_contrast_100,
+ ...inputStyles,
+ ]}>
+ {localizeDate(value)}
+
+
+
+
+
+
+ {open && (
+
+ )}
+
+ )
+}
diff --git a/src/components/forms/DateField/index.tsx b/src/components/forms/DateField/index.tsx
new file mode 100644
index 0000000000..dcf509b7d8
--- /dev/null
+++ b/src/components/forms/DateField/index.tsx
@@ -0,0 +1,63 @@
+import React from 'react'
+import {View} from 'react-native'
+import DateTimePicker, {
+ DateTimePickerEvent,
+} from '@react-native-community/datetimepicker'
+
+import {useTheme, atoms} from '#/alf'
+import {toSimpleDateString} from '#/components/forms/InputDate/utils'
+import {InputDateProps} from '#/components/forms/InputDate/types'
+import TextField from '#/components/forms/TextField'
+
+export * as utils from '#/components/forms/InputDate/utils'
+export const Label = TextField.Label
+
+/**
+ * Date-only input. Accepts a date in the format YYYY-MM-DD, and reports date
+ * changes in the same format.
+ *
+ * For dates of unknown format, convert with the
+ * `utils.toSimpleDateString(Date)` export of this file.
+ */
+export function DateField({
+ value: initialValue,
+ onChange,
+ testID,
+ label,
+ accessibilityLabel,
+ accessibilityHint,
+}: InputDateProps) {
+ const labelId = React.useId()
+ const t = useTheme()
+ const [value, setValue] = React.useState(initialValue)
+
+ const onChangeInternal = React.useCallback(
+ (event: DateTimePickerEvent, date: Date | undefined) => {
+ if (date) {
+ const formatted = toSimpleDateString(date)
+ onChange(formatted)
+ setValue(formatted)
+ }
+ },
+ [onChange],
+ )
+
+ return (
+
+
+
+ )
+}
diff --git a/src/components/forms/DateField/index.web.tsx b/src/components/forms/DateField/index.web.tsx
new file mode 100644
index 0000000000..f8809cb7ca
--- /dev/null
+++ b/src/components/forms/DateField/index.web.tsx
@@ -0,0 +1,71 @@
+import React from 'react'
+import {TextInput, TextInputProps, StyleSheet} from 'react-native'
+// @ts-ignore
+import {unstable_createElement} from 'react-native-web'
+
+import TextField, {createInput} from '#/components/forms/TextField'
+
+import {toSimpleDateString} from '#/components/forms/InputDate/utils'
+
+export * as utils from '#/components/forms/InputDate/utils'
+export const Label = TextField.Label
+
+const InputBase = React.forwardRef(
+ ({style, ...props}, ref) => {
+ return unstable_createElement('input', {
+ ...props,
+ ref,
+ type: 'date',
+ style: [
+ StyleSheet.flatten(style),
+ {
+ background: 'transparent',
+ border: 0,
+ },
+ ],
+ })
+ },
+)
+
+InputBase.displayName = 'InputBase'
+
+const Input = createInput(InputBase as unknown as typeof TextInput)
+
+export function DateField({
+ value,
+ onChange,
+ label,
+ isInvalid,
+ testID,
+}: {
+ value: string
+ onChange: (value: string) => void
+ label: string
+ isInvalid?: boolean
+ testID?: string
+}) {
+ const handleOnChange = React.useCallback(
+ (e: any) => {
+ const date = e.target.valueAsDate || e.target.value
+
+ if (date) {
+ const formatted = toSimpleDateString(date)
+ onChange(formatted)
+ }
+ },
+ [onChange],
+ )
+
+ return (
+
+ {}}
+ isInvalid={isInvalid}
+ testID={testID}
+ />
+
+ )
+}
diff --git a/src/components/forms/DateField/types.ts b/src/components/forms/DateField/types.ts
new file mode 100644
index 0000000000..2ef0a4b3ba
--- /dev/null
+++ b/src/components/forms/DateField/types.ts
@@ -0,0 +1,10 @@
+import {TextInputProps} from 'react-native'
+
+import {BaseProps} from '#/components/forms/types'
+
+export type InputDateProps = BaseProps & {
+ /**
+ * **NOTE:** Available only on web
+ */
+ autoFocus?: TextInputProps['autoFocus']
+}
diff --git a/src/components/forms/DateField/utils.ts b/src/components/forms/DateField/utils.ts
new file mode 100644
index 0000000000..c787272fe8
--- /dev/null
+++ b/src/components/forms/DateField/utils.ts
@@ -0,0 +1,16 @@
+import {getLocales} from 'expo-localization'
+
+const LOCALE = getLocales()[0]
+
+// we need the date in the form yyyy-MM-dd to pass to the input
+export function toSimpleDateString(date: Date | string): string {
+ const _date = typeof date === 'string' ? new Date(date) : date
+ return _date.toISOString().split('T')[0]
+}
+
+export function localizeDate(date: Date | string): string {
+ const _date = typeof date === 'string' ? new Date(date) : date
+ return new Intl.DateTimeFormat(LOCALE.languageTag, {
+ timeZone: 'UTC',
+ }).format(_date)
+}
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index bcf7dafe0d..cc3aae2e59 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -94,7 +94,7 @@ function Root({children, isInvalid = false}: RootProps) {
paddingVertical: 14,
},
]}
- onPressIn={() => inputRef.current?.focus()}
+ onPress={() => inputRef.current?.focus()}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}>
{children}
@@ -103,7 +103,7 @@ function Root({children, isInvalid = false}: RootProps) {
)
}
-function useSharedInputStyles() {
+export function useSharedInputStyles() {
const t = useTheme()
return React.useMemo(() => {
const hover: ViewStyle[] = [
@@ -228,6 +228,15 @@ export function createInput(Component: typeof TextInput) {
const Input = createInput(TextInput)
+function Label({children}: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ return (
+
+ {children}
+
+ )
+}
+
function Icon({icon: Comp}: {icon: React.ComponentType}) {
const t = useTheme()
const ctx = React.useContext(Context)
@@ -266,7 +275,7 @@ function Icon({icon: Comp}: {icon: React.ComponentType}) {
@@ -51,16 +52,13 @@ export function Forms() {
InputDate
- console.log(date)}
- label="Input"
- />
- console.log(date)}
+ value={date}
+ onChange={date => {
+ console.log(date)
+ setDate(date)
+ }}
label="Input"
/>
From 8a5e1885bae89107c8475010b6c3ff2e9b0e8891 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 13:20:12 -0600
Subject: [PATCH 58/68] Add label example
---
src/view/screens/Storybook/Forms.tsx | 47 ++++++++++++++++------------
1 file changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 28a908c414..4339f389d3 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -4,7 +4,7 @@ import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {H1, H3} from '#/components/Typography'
import TextField from '#/components/forms/TextField'
-import {DateField} from '#/components/forms/DateField'
+import {DateField, Label} from '#/components/forms/DateField'
import Toggle from '#/components/forms/Toggle'
import ToggleButton from '#/components/forms/ToggleButton'
import {Button} from '#/components/Button'
@@ -41,26 +41,33 @@ export function Forms() {
/>
-
-
-
+ Text field
+
+
+
+ @gmail.com
+
+
+
+ DateField
+
+
+
+ {
+ console.log(date)
+ setDate(date)
+ }}
+ label="Input"
/>
- @gmail.com
-
-
- InputDate
- {
- console.log(date)
- setDate(date)
- }}
- label="Input"
- />
+
From 175466ade6d67a0ec86989b7f97fcf62d5493d64 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 14:02:39 -0600
Subject: [PATCH 59/68] Clean up old InputDate, DateField android, text area
example
---
.../forms/DateField/index.android.tsx | 144 +++++-----------
src/components/forms/DateField/index.tsx | 29 ++--
src/components/forms/DateField/index.web.tsx | 23 +--
src/components/forms/DateField/types.ts | 15 +-
.../forms/InputDate/index.android.tsx | 161 ------------------
src/components/forms/InputDate/index.tsx | 75 --------
src/components/forms/InputDate/index.web.tsx | 152 -----------------
src/components/forms/InputDate/types.ts | 10 --
src/components/forms/InputDate/utils.ts | 16 --
src/components/forms/TextField.tsx | 4 +-
src/view/screens/Storybook/Forms.tsx | 18 +-
11 files changed, 87 insertions(+), 560 deletions(-)
delete mode 100644 src/components/forms/InputDate/index.android.tsx
delete mode 100644 src/components/forms/InputDate/index.tsx
delete mode 100644 src/components/forms/InputDate/index.web.tsx
delete mode 100644 src/components/forms/InputDate/types.ts
delete mode 100644 src/components/forms/InputDate/utils.ts
diff --git a/src/components/forms/DateField/index.android.tsx b/src/components/forms/DateField/index.android.tsx
index 3547557841..b1116411cc 100644
--- a/src/components/forms/DateField/index.android.tsx
+++ b/src/components/forms/DateField/index.android.tsx
@@ -1,69 +1,41 @@
import React from 'react'
-import {View, TextStyle, Pressable} from 'react-native'
+import {View, Pressable} from 'react-native'
import DateTimePicker, {
BaseProps as DateTimePickerProps,
} from '@react-native-community/datetimepicker'
-import {Logo} from '#/view/icons/Logo'
-import {useTheme, atoms, tokens} from '#/alf'
+import {useTheme, atoms} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
+import TextField, {useSharedInputStyles} from '#/components/forms/TextField'
+import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
-import {InputDateProps} from '#/components/forms/InputDate/types'
+import {DateFieldProps} from '#/components/forms/DateField/types'
import {
localizeDate,
toSimpleDateString,
-} from '#/components/forms/InputDate/utils'
+} from '#/components/forms/DateField/utils'
-export * as utils from '#/components/forms/InputDate/utils'
+export * as utils from '#/components/forms/DateField/utils'
+export const Label = TextField.Label
-export function InputDate({
- value: initialValue,
- onChange,
- testID,
+export function DateField({
+ value,
+ onChangeDate,
label,
- hasError,
- accessibilityLabel,
- accessibilityHint,
- ...props
-}: InputDateProps) {
- const labelId = React.useId()
+ isInvalid,
+ testID,
+}: DateFieldProps) {
const t = useTheme()
const [open, setOpen] = React.useState(false)
- const [value, setValue] = React.useState(initialValue)
+ const {
+ state: pressed,
+ onIn: onPressIn,
+ onOut: onPressOut,
+ } = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
- const {inputStyles, iconStyles} = React.useMemo(() => {
- const input: TextStyle[] = [
- {
- paddingLeft: 40,
- },
- ]
- const icon: TextStyle[] = []
-
- if (hasError) {
- input.push({
- borderColor: tokens.color.red_200,
- })
- icon.push({
- color: tokens.color.red_400,
- })
- }
-
- if (focused) {
- input.push({
- borderColor: t.atoms.border_contrast.borderColor,
- })
-
- if (hasError) {
- input.push({
- borderColor: tokens.color.red_500,
- })
- }
- }
-
- return {inputStyles: input, iconStyles: icon}
- }, [t, focused, hasError])
+ const {chromeFocus, chromeError, chromeErrorHover} = useSharedInputStyles()
const onChangeInternal = React.useCallback<
Required['onChange']
@@ -73,75 +45,53 @@ export function InputDate({
if (date) {
const formatted = toSimpleDateString(date)
- onChange(formatted)
- setValue(formatted)
+ onChangeDate(formatted)
}
},
- [onChange, setOpen, setValue],
+ [onChangeDate, setOpen],
)
return (
- {label && (
-
- {label}
-
- )}
-
setOpen(true)}
+ onPressIn={onPressIn}
+ onPressOut={onPressOut}
onFocus={onFocus}
onBlur={onBlur}
style={[
{
- paddingTop: atoms.pt_md.paddingTop + 2,
+ paddingTop: 16,
+ paddingBottom: 16,
+ borderColor: 'transparent',
+ borderWidth: 2,
},
+ atoms.flex_row,
+ atoms.flex_1,
atoms.w_full,
atoms.px_lg,
- atoms.pb_md,
atoms.rounded_sm,
- t.atoms.bg_contrast_100,
- ...inputStyles,
+ t.atoms.bg_contrast_50,
+ focused || pressed ? chromeFocus : {},
+ isInvalid ? chromeError : {},
+ isInvalid && (focused || pressed) ? chromeErrorHover : {},
]}>
- {localizeDate(value)}
-
+
-
-
-
+
+ {localizeDate(value)}
+
+
{open && (
)}
diff --git a/src/components/forms/DateField/index.tsx b/src/components/forms/DateField/index.tsx
index dcf509b7d8..8a8db876b2 100644
--- a/src/components/forms/DateField/index.tsx
+++ b/src/components/forms/DateField/index.tsx
@@ -5,11 +5,11 @@ import DateTimePicker, {
} from '@react-native-community/datetimepicker'
import {useTheme, atoms} from '#/alf'
-import {toSimpleDateString} from '#/components/forms/InputDate/utils'
-import {InputDateProps} from '#/components/forms/InputDate/types'
import TextField from '#/components/forms/TextField'
+import {toSimpleDateString} from '#/components/forms/DateField/utils'
+import {DateFieldProps} from '#/components/forms/DateField/types'
-export * as utils from '#/components/forms/InputDate/utils'
+export * as utils from '#/components/forms/DateField/utils'
export const Label = TextField.Label
/**
@@ -20,43 +20,36 @@ export const Label = TextField.Label
* `utils.toSimpleDateString(Date)` export of this file.
*/
export function DateField({
- value: initialValue,
- onChange,
+ value,
+ onChangeDate,
testID,
label,
- accessibilityLabel,
- accessibilityHint,
-}: InputDateProps) {
- const labelId = React.useId()
+}: DateFieldProps) {
const t = useTheme()
- const [value, setValue] = React.useState(initialValue)
const onChangeInternal = React.useCallback(
(event: DateTimePickerEvent, date: Date | undefined) => {
if (date) {
const formatted = toSimpleDateString(date)
- onChange(formatted)
- setValue(formatted)
+ onChangeDate(formatted)
}
},
- [onChange],
+ [onChangeDate],
)
return (
)
diff --git a/src/components/forms/DateField/index.web.tsx b/src/components/forms/DateField/index.web.tsx
index f8809cb7ca..4dd1980393 100644
--- a/src/components/forms/DateField/index.web.tsx
+++ b/src/components/forms/DateField/index.web.tsx
@@ -4,10 +4,10 @@ import {TextInput, TextInputProps, StyleSheet} from 'react-native'
import {unstable_createElement} from 'react-native-web'
import TextField, {createInput} from '#/components/forms/TextField'
+import {toSimpleDateString} from '#/components/forms/DateField/utils'
+import {DateFieldProps} from '#/components/forms/DateField/types'
-import {toSimpleDateString} from '#/components/forms/InputDate/utils'
-
-export * as utils from '#/components/forms/InputDate/utils'
+export * as utils from '#/components/forms/DateField/utils'
export const Label = TextField.Label
const InputBase = React.forwardRef(
@@ -33,37 +33,30 @@ const Input = createInput(InputBase as unknown as typeof TextInput)
export function DateField({
value,
- onChange,
+ onChangeDate,
label,
isInvalid,
testID,
-}: {
- value: string
- onChange: (value: string) => void
- label: string
- isInvalid?: boolean
- testID?: string
-}) {
+}: DateFieldProps) {
const handleOnChange = React.useCallback(
(e: any) => {
const date = e.target.valueAsDate || e.target.value
if (date) {
const formatted = toSimpleDateString(date)
- onChange(formatted)
+ onChangeDate(formatted)
}
},
- [onChange],
+ [onChangeDate],
)
return (
-
+
{}}
- isInvalid={isInvalid}
testID={testID}
/>
diff --git a/src/components/forms/DateField/types.ts b/src/components/forms/DateField/types.ts
index 2ef0a4b3ba..129f5672d4 100644
--- a/src/components/forms/DateField/types.ts
+++ b/src/components/forms/DateField/types.ts
@@ -1,10 +1,7 @@
-import {TextInputProps} from 'react-native'
-
-import {BaseProps} from '#/components/forms/types'
-
-export type InputDateProps = BaseProps & {
- /**
- * **NOTE:** Available only on web
- */
- autoFocus?: TextInputProps['autoFocus']
+export type DateFieldProps = {
+ value: string
+ onChangeDate: (date: string) => void
+ label: string
+ isInvalid?: boolean
+ testID?: string
}
diff --git a/src/components/forms/InputDate/index.android.tsx b/src/components/forms/InputDate/index.android.tsx
deleted file mode 100644
index 3547557841..0000000000
--- a/src/components/forms/InputDate/index.android.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import React from 'react'
-import {View, TextStyle, Pressable} from 'react-native'
-import DateTimePicker, {
- BaseProps as DateTimePickerProps,
-} from '@react-native-community/datetimepicker'
-
-import {Logo} from '#/view/icons/Logo'
-import {useTheme, atoms, tokens} from '#/alf'
-import {Text} from '#/components/Typography'
-import {useInteractionState} from '#/components/hooks/useInteractionState'
-
-import {InputDateProps} from '#/components/forms/InputDate/types'
-import {
- localizeDate,
- toSimpleDateString,
-} from '#/components/forms/InputDate/utils'
-
-export * as utils from '#/components/forms/InputDate/utils'
-
-export function InputDate({
- value: initialValue,
- onChange,
- testID,
- label,
- hasError,
- accessibilityLabel,
- accessibilityHint,
- ...props
-}: InputDateProps) {
- const labelId = React.useId()
- const t = useTheme()
- const [open, setOpen] = React.useState(false)
- const [value, setValue] = React.useState(initialValue)
- const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
-
- const {inputStyles, iconStyles} = React.useMemo(() => {
- const input: TextStyle[] = [
- {
- paddingLeft: 40,
- },
- ]
- const icon: TextStyle[] = []
-
- if (hasError) {
- input.push({
- borderColor: tokens.color.red_200,
- })
- icon.push({
- color: tokens.color.red_400,
- })
- }
-
- if (focused) {
- input.push({
- borderColor: t.atoms.border_contrast.borderColor,
- })
-
- if (hasError) {
- input.push({
- borderColor: tokens.color.red_500,
- })
- }
- }
-
- return {inputStyles: input, iconStyles: icon}
- }, [t, focused, hasError])
-
- const onChangeInternal = React.useCallback<
- Required['onChange']
- >(
- (_event, date) => {
- setOpen(false)
-
- if (date) {
- const formatted = toSimpleDateString(date)
- onChange(formatted)
- setValue(formatted)
- }
- },
- [onChange, setOpen, setValue],
- )
-
- return (
-
- {label && (
-
- {label}
-
- )}
-
- setOpen(true)}
- onFocus={onFocus}
- onBlur={onBlur}
- style={[
- {
- paddingTop: atoms.pt_md.paddingTop + 2,
- },
- atoms.w_full,
- atoms.px_lg,
- atoms.pb_md,
- atoms.rounded_sm,
- t.atoms.bg_contrast_100,
- ...inputStyles,
- ]}>
- {localizeDate(value)}
-
-
-
-
-
-
- {open && (
-
- )}
-
- )
-}
diff --git a/src/components/forms/InputDate/index.tsx b/src/components/forms/InputDate/index.tsx
deleted file mode 100644
index 727dcd6736..0000000000
--- a/src/components/forms/InputDate/index.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react'
-import {View} from 'react-native'
-import DateTimePicker, {
- DateTimePickerEvent,
-} from '@react-native-community/datetimepicker'
-
-import {useTheme, atoms} from '#/alf'
-import {Text} from '#/components/Typography'
-import {toSimpleDateString} from '#/components/forms/InputDate/utils'
-
-import {InputDateProps} from '#/components/forms/InputDate/types'
-export * as utils from '#/components/forms/InputDate/utils'
-
-/**
- * Date-only input. Accepts a date in the format YYYY-MM-DD, and reports date
- * changes in the same format.
- *
- * For dates of unknown format, convert with the
- * `utils.toSimpleDateString(Date)` export of this file.
- */
-export function InputDate({
- value: initialValue,
- onChange,
- testID,
- label,
- accessibilityLabel,
- accessibilityHint,
-}: InputDateProps) {
- const labelId = React.useId()
- const t = useTheme()
- const [value, setValue] = React.useState(initialValue)
-
- const onChangeInternal = React.useCallback(
- (event: DateTimePickerEvent, date: Date | undefined) => {
- if (date) {
- const formatted = toSimpleDateString(date)
- onChange(formatted)
- setValue(formatted)
- }
- },
- [onChange],
- )
-
- return (
-
- {label && (
-
- {label}
-
- )}
-
-
-
- )
-}
diff --git a/src/components/forms/InputDate/index.web.tsx b/src/components/forms/InputDate/index.web.tsx
deleted file mode 100644
index 95a4d9e1b4..0000000000
--- a/src/components/forms/InputDate/index.web.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-import React from 'react'
-import {View, TextStyle} from 'react-native'
-// @ts-ignore
-import {unstable_createElement} from 'react-native-web'
-
-import {Logo} from '#/view/icons/Logo'
-import {useTheme, atoms, tokens} from '#/alf'
-import {Text} from '#/components/Typography'
-import {useInteractionState} from '#/components/hooks/useInteractionState'
-
-import {InputDateProps} from '#/components/forms/InputDate/types'
-import {toSimpleDateString} from '#/components/forms/InputDate/utils'
-
-export * as utils from '#/components/forms/InputDate/utils'
-
-export function InputDate({
- label,
- hasError,
- testID,
- value: initialValue,
- onChange,
- accessibilityLabel,
- accessibilityHint,
- ...props
-}: InputDateProps) {
- const labelId = React.useId()
- const t = useTheme()
- const [value, setValue] = React.useState(initialValue)
- const {
- state: hovered,
- onIn: onHoverIn,
- onOut: onHoverOut,
- } = useInteractionState()
- const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
-
- const {inputStyles, iconStyles} = React.useMemo(() => {
- const input: TextStyle[] = [
- {
- paddingLeft: 40,
- },
- ]
- const icon: TextStyle[] = []
-
- if (hasError) {
- input.push({
- borderColor: tokens.color.red_200,
- })
- icon.push({
- color: tokens.color.red_400,
- })
- }
-
- if (hovered || focused) {
- input.push({
- borderColor: t.atoms.border_contrast.borderColor,
- })
-
- if (hasError) {
- input.push({
- borderColor: tokens.color.red_500,
- })
- }
- }
-
- return {inputStyles: input, iconStyles: icon}
- }, [t, hovered, focused, hasError])
-
- const handleOnChange = React.useCallback(
- (e: any) => {
- const date = e.currentTarget.valueAsDate
-
- if (date) {
- const formatted = toSimpleDateString(date)
- onChange(formatted)
- setValue(formatted)
- }
- },
- [onChange, setValue],
- )
-
- return (
-
- {label && (
-
- {label}
-
- )}
-
- {unstable_createElement('input', {
- ...props,
- testID: `${testID}-datepicker`,
- 'aria-labelledby': labelId,
- 'aria-label': label,
- accessibilityLabel: accessibilityLabel,
- accessibilityHint: accessibilityHint,
- type: 'date',
- value: value,
- onFocus: onFocus,
- onBlur: onBlur,
- onChange: handleOnChange,
- onMouseEnter: onHoverIn,
- onMouseLeave: onHoverOut,
- style: [
- {
- outline: 0,
- border: 0,
- appearance: 'none',
- boxSizing: 'border-box',
- lineHeight: atoms.text_md.lineHeight * 1.1875,
- paddingTop: atoms.pt_md.paddingTop - 1,
- },
- atoms.w_full,
- atoms.px_lg,
- atoms.pb_md,
- atoms.rounded_sm,
- atoms.text_md,
- t.atoms.bg_contrast_100,
- t.atoms.text,
- ...inputStyles,
- ],
- })}
-
-
-
-
-
- )
-}
diff --git a/src/components/forms/InputDate/types.ts b/src/components/forms/InputDate/types.ts
deleted file mode 100644
index 2ef0a4b3ba..0000000000
--- a/src/components/forms/InputDate/types.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import {TextInputProps} from 'react-native'
-
-import {BaseProps} from '#/components/forms/types'
-
-export type InputDateProps = BaseProps & {
- /**
- * **NOTE:** Available only on web
- */
- autoFocus?: TextInputProps['autoFocus']
-}
diff --git a/src/components/forms/InputDate/utils.ts b/src/components/forms/InputDate/utils.ts
deleted file mode 100644
index c787272fe8..0000000000
--- a/src/components/forms/InputDate/utils.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {getLocales} from 'expo-localization'
-
-const LOCALE = getLocales()[0]
-
-// we need the date in the form yyyy-MM-dd to pass to the input
-export function toSimpleDateString(date: Date | string): string {
- const _date = typeof date === 'string' ? new Date(date) : date
- return _date.toISOString().split('T')[0]
-}
-
-export function localizeDate(date: Date | string): string {
- const _date = typeof date === 'string' ? new Date(date) : date
- return new Intl.DateTimeFormat(LOCALE.languageTag, {
- timeZone: 'UTC',
- }).format(_date)
-}
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index cc3aae2e59..941c16bd30 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -215,8 +215,8 @@ export function createInput(Component: typeof TextInput) {
{borderColor: 'transparent', borderWidth: 2},
ctx.hovered ? chromeHover : {},
ctx.focused ? chromeFocus : {},
- ctx.isInvalid ? chromeError : {},
- ctx.isInvalid && (ctx.hovered || ctx.focused)
+ ctx.isInvalid || isInvalid ? chromeError : {},
+ (ctx.isInvalid || isInvalid) && (ctx.hovered || ctx.focused)
? chromeErrorHover
: {},
]}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 4339f389d3..0e9bf54b01 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -41,7 +41,7 @@ export function Forms() {
/>
-
+
Text field
@@ -54,14 +54,26 @@ export function Forms() {
+
+ Textarea
+
+
+
DateField
-
+
{
+ onChangeDate={date => {
console.log(date)
setDate(date)
}}
From 1514ffd6e965a3955b6905f916f2e5bb15741659 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 14:10:48 -0600
Subject: [PATCH 60/68] Consistent imports
---
src/components/Dialog/index.web.tsx | 3 +-
.../forms/DateField/index.android.tsx | 5 +-
src/components/forms/DateField/index.tsx | 2 +-
src/components/forms/DateField/index.web.tsx | 4 +-
src/components/forms/TextField.tsx | 18 ++----
src/components/forms/Toggle.tsx | 57 ++++++++-----------
src/components/forms/ToggleButton.tsx | 17 ++----
src/components/forms/types.ts | 11 ----
src/view/screens/Storybook/Forms.tsx | 12 ++--
src/view/screens/Storybook/index.tsx | 9 +--
10 files changed, 49 insertions(+), 89 deletions(-)
delete mode 100644 src/components/forms/types.ts
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 3350785ec2..6099efdd86 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -12,11 +12,10 @@ import {Button} from '#/components/Button'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
import {Context, useDialogContext} from '#/components/Dialog/context'
-import TextField from '#/components/forms/TextField'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
-export const Input = TextField.Input
+export {Input} from '#/components/forms/TextField'
const stopPropagation = (e: any) => e.stopPropagation()
diff --git a/src/components/forms/DateField/index.android.tsx b/src/components/forms/DateField/index.android.tsx
index b1116411cc..83fa285f5f 100644
--- a/src/components/forms/DateField/index.android.tsx
+++ b/src/components/forms/DateField/index.android.tsx
@@ -7,7 +7,7 @@ import DateTimePicker, {
import {useTheme, atoms} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
-import TextField, {useSharedInputStyles} from '#/components/forms/TextField'
+import * as TextField from '#/components/forms/TextField'
import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
import {DateFieldProps} from '#/components/forms/DateField/types'
@@ -35,7 +35,8 @@ export function DateField({
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
- const {chromeFocus, chromeError, chromeErrorHover} = useSharedInputStyles()
+ const {chromeFocus, chromeError, chromeErrorHover} =
+ TextField.useSharedInputStyles()
const onChangeInternal = React.useCallback<
Required['onChange']
diff --git a/src/components/forms/DateField/index.tsx b/src/components/forms/DateField/index.tsx
index 8a8db876b2..c359a9d460 100644
--- a/src/components/forms/DateField/index.tsx
+++ b/src/components/forms/DateField/index.tsx
@@ -5,7 +5,7 @@ import DateTimePicker, {
} from '@react-native-community/datetimepicker'
import {useTheme, atoms} from '#/alf'
-import TextField from '#/components/forms/TextField'
+import * as TextField from '#/components/forms/TextField'
import {toSimpleDateString} from '#/components/forms/DateField/utils'
import {DateFieldProps} from '#/components/forms/DateField/types'
diff --git a/src/components/forms/DateField/index.web.tsx b/src/components/forms/DateField/index.web.tsx
index 4dd1980393..32f38a5d16 100644
--- a/src/components/forms/DateField/index.web.tsx
+++ b/src/components/forms/DateField/index.web.tsx
@@ -3,7 +3,7 @@ import {TextInput, TextInputProps, StyleSheet} from 'react-native'
// @ts-ignore
import {unstable_createElement} from 'react-native-web'
-import TextField, {createInput} from '#/components/forms/TextField'
+import * as TextField from '#/components/forms/TextField'
import {toSimpleDateString} from '#/components/forms/DateField/utils'
import {DateFieldProps} from '#/components/forms/DateField/types'
@@ -29,7 +29,7 @@ const InputBase = React.forwardRef(
InputBase.displayName = 'InputBase'
-const Input = createInput(InputBase as unknown as typeof TextInput)
+const Input = TextField.createInput(InputBase as unknown as typeof TextInput)
export function DateField({
value,
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index 941c16bd30..133ca09cc4 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -38,7 +38,7 @@ const Context = React.createContext<{
export type RootProps = React.PropsWithChildren<{isInvalid?: boolean}>
-function Root({children, isInvalid = false}: RootProps) {
+export function Root({children, isInvalid = false}: RootProps) {
const inputRef = React.useRef(null)
const rootRef = React.useRef(null)
const {
@@ -226,9 +226,9 @@ export function createInput(Component: typeof TextInput) {
}
}
-const Input = createInput(TextInput)
+export const Input = createInput(TextInput)
-function Label({children}: React.PropsWithChildren<{}>) {
+export function Label({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
return (
@@ -237,7 +237,7 @@ function Label({children}: React.PropsWithChildren<{}>) {
)
}
-function Icon({icon: Comp}: {icon: React.ComponentType}) {
+export function Icon({icon: Comp}: {icon: React.ComponentType}) {
const t = useTheme()
const ctx = React.useContext(Context)
const {hover, focus, errorHover, errorFocus} = React.useMemo(() => {
@@ -286,7 +286,7 @@ function Icon({icon: Comp}: {icon: React.ComponentType}) {
)
}
-function Suffix({
+export function Suffix({
children,
label,
accessibilityHint,
@@ -322,11 +322,3 @@ function Suffix({
)
}
-
-export default {
- Root,
- Input,
- Label,
- Icon,
- Suffix,
-}
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 8fe43f2f41..6fdbaa2eed 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -5,11 +5,11 @@ import {useTheme, atoms as a, web} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
-type ItemState = {
+export type ItemState = {
name: string
selected: boolean
disabled: boolean
- hasError: boolean
+ isInvalid: boolean
hovered: boolean
pressed: boolean
focused: boolean
@@ -19,7 +19,7 @@ const ItemContext = React.createContext({
name: '',
selected: false,
disabled: false,
- hasError: false,
+ isInvalid: false,
hovered: false,
pressed: false,
focused: false,
@@ -55,7 +55,7 @@ export type ItemProps = {
value?: boolean
disabled?: boolean
onChange?: (selected: boolean) => void
- hasError?: boolean
+ isInvalid?: boolean
style?: (state: ItemState) => ViewStyle
children: ((props: ItemState) => React.ReactNode) | React.ReactNode
}
@@ -64,7 +64,7 @@ export function useItemContext() {
return React.useContext(ItemContext)
}
-function Group({
+export function Group({
children,
values: providedValues,
onChange,
@@ -138,13 +138,13 @@ function Group({
)
}
-function Item({
+export function Item({
children,
name,
value = false,
disabled: itemDisabled = false,
onChange,
- hasError,
+ isInvalid,
style,
type = 'checkbox',
label,
@@ -185,12 +185,12 @@ function Item({
name,
selected,
disabled: disabled ?? false,
- hasError: hasError ?? false,
+ isInvalid: isInvalid ?? false,
hovered,
pressed,
focused,
}),
- [name, selected, disabled, hovered, pressed, focused, hasError],
+ [name, selected, disabled, hovered, pressed, focused, isInvalid],
)
return (
@@ -201,7 +201,7 @@ function Item({
disabled={disabled}
aria-disabled={disabled ?? false}
aria-checked={selected}
- aria-invalid={hasError}
+ aria-invalid={isInvalid}
aria-label={label}
role={role}
accessibilityRole={role}
@@ -230,7 +230,7 @@ function Item({
)
}
-function Label({children}: React.PropsWithChildren<{}>) {
+export function Label({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
const {disabled} = useItemContext()
return (
@@ -247,20 +247,20 @@ function Label({children}: React.PropsWithChildren<{}>) {
)
}
-function createSharedToggleStyles({
+export function createSharedToggleStyles({
theme: t,
hovered,
focused,
selected,
disabled,
- hasError,
+ isInvalid,
}: {
theme: ReturnType
selected: boolean
hovered: boolean
focused: boolean
disabled: boolean
- hasError: boolean
+ isInvalid: boolean
}) {
const base: ViewStyle[] = []
const baseHover: ViewStyle[] = []
@@ -290,7 +290,7 @@ function createSharedToggleStyles({
}
}
- if (hasError) {
+ if (isInvalid) {
base.push({
backgroundColor:
t.name === 'light' ? t.palette.negative_25 : t.palette.negative_900,
@@ -321,9 +321,9 @@ function createSharedToggleStyles({
}
}
-function Checkbox() {
+export function Checkbox() {
const t = useTheme()
- const {selected, hovered, focused, disabled, hasError} = useItemContext()
+ const {selected, hovered, focused, disabled, isInvalid} = useItemContext()
const {baseStyles, baseHoverStyles, indicatorStyles} =
createSharedToggleStyles({
theme: t,
@@ -331,7 +331,7 @@ function Checkbox() {
focused,
selected,
disabled,
- hasError,
+ isInvalid,
})
return (
)
}
-
-export default {
- Item,
- Checkbox,
- Label,
- Switch,
- Radio,
- Group,
-}
diff --git a/src/components/forms/ToggleButton.tsx b/src/components/forms/ToggleButton.tsx
index f6578c90e2..2694dd021c 100644
--- a/src/components/forms/ToggleButton.tsx
+++ b/src/components/forms/ToggleButton.tsx
@@ -4,17 +4,13 @@ import {View, AccessibilityProps, TextStyle, ViewStyle} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
-import Toggle, {
- GroupProps as BaseGroupProps,
- ItemProps as BaseItemProps,
- useItemContext,
-} from '#/components/forms/Toggle'
+import * as Toggle from '#/components/forms/Toggle'
-export type ItemProps = Omit &
+export type ItemProps = Omit &
AccessibilityProps &
React.PropsWithChildren<{}>
-export type GroupProps = Omit & {
+export type GroupProps = Omit & {
multiple?: boolean
}
@@ -46,7 +42,7 @@ export function Button({children, ...props}: ItemProps) {
function ButtonInner({children}: React.PropsWithChildren<{}>) {
const t = useTheme()
- const state = useItemContext()
+ const state = Toggle.useItemContext()
const {baseStyles, hoverStyles, activeStyles, textStyles} =
React.useMemo(() => {
@@ -123,8 +119,3 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
)
}
-
-export default {
- Group,
- Button,
-}
diff --git a/src/components/forms/types.ts b/src/components/forms/types.ts
deleted file mode 100644
index 8d94bf0d1c..0000000000
--- a/src/components/forms/types.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import {AccessibilityProps} from 'react-native'
-
-export type RequiredAccessibilityProps = Required
-
-export type BaseProps = AccessibilityProps & {
- value: T
- onChange: (value: T) => void
- testID?: string
- label: string
- hasError?: boolean
-}
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index 0e9bf54b01..6862e3fef9 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -3,10 +3,10 @@ import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {H1, H3} from '#/components/Typography'
-import TextField from '#/components/forms/TextField'
+import * as TextField from '#/components/forms/TextField'
import {DateField, Label} from '#/components/forms/DateField'
-import Toggle from '#/components/forms/Toggle'
-import ToggleButton from '#/components/forms/ToggleButton'
+import * as Toggle from '#/components/forms/Toggle'
+import * as ToggleButton from '#/components/forms/ToggleButton'
import {Button} from '#/components/Button'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
@@ -113,7 +113,7 @@ export function Forms() {
Click me
-
+
Click me
@@ -143,7 +143,7 @@ export function Forms() {
Click me
-
+
Click me
@@ -172,7 +172,7 @@ export function Forms() {
Click me
-
+
Click me
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index 9f627ad2c2..d8898f20e8 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -30,8 +30,7 @@ export function Storybook() {
variant="outline"
color="primary"
size="small"
- accessibilityLabel='Set theme to "system"'
- accessibilityHint="Set theme to system default"
+ label='Set theme to "system"'
onPress={() => setColorMode('system')}>
System
@@ -39,8 +38,7 @@ export function Storybook() {
variant="solid"
color="secondary"
size="small"
- accessibilityLabel='Set theme to "system"'
- accessibilityHint="Set theme to system default"
+ label='Set theme to "system"'
onPress={() => setColorMode('light')}>
Light
@@ -48,8 +46,7 @@ export function Storybook() {
variant="solid"
color="secondary"
size="small"
- accessibilityLabel='Set theme to "system"'
- accessibilityHint="Set theme to system default"
+ label='Set theme to "system"'
onPress={() => setColorMode('dark')}>
Dark
From d6925a00d7da42895cafb8f68c98492a964f1e71 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 15:12:42 -0600
Subject: [PATCH 61/68] Button context, icons
---
src/components/Button.tsx | 175 +++++++++++++------------
src/components/Link.tsx | 65 +++++----
src/view/screens/Storybook/Buttons.tsx | 37 +++++-
src/view/screens/Storybook/Links.tsx | 14 +-
4 files changed, 171 insertions(+), 120 deletions(-)
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 06226ad1e0..a37017decc 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -6,10 +6,14 @@ import {
TextProps,
ViewStyle,
AccessibilityProps,
+ View,
+ TextStyle,
+ StyleSheet,
} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
-import {useTheme, atoms, tokens, web, native} from '#/alf'
+import {useTheme, atoms as a, tokens, web, native} from '#/alf'
+import {Props as SVGIconProps} from '#/components/icons/common'
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
export type ButtonColor =
@@ -38,25 +42,32 @@ export type VariantProps = {
size?: ButtonSize
}
-export type ButtonProps = Pick &
- AccessibilityProps &
+export type ButtonProps = React.PropsWithChildren<
+ Pick &
+ AccessibilityProps &
+ VariantProps & {
+ label: string
+ }
+>
+export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
+
+const Context = React.createContext<
VariantProps & {
- children:
- | ((props: {
- state: {
- pressed: boolean
- hovered: boolean
- focused: boolean
- }
- props: VariantProps & {
- disabled?: boolean
- }
- }) => React.ReactNode)
- | React.ReactNode
- | string
- label: string
+ hovered: boolean
+ focused: boolean
+ pressed: boolean
+ disabled: boolean
}
-export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
+>({
+ hovered: false,
+ focused: false,
+ pressed: false,
+ disabled: false,
+})
+
+export function useButtonContext() {
+ return React.useContext(Context)
+}
export function Button({
children,
@@ -131,21 +142,21 @@ export function Button({
})
}
} else if (variant === 'outline') {
- baseStyles.push(atoms.border, t.atoms.bg, {
+ baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
})
if (!disabled) {
- baseStyles.push(atoms.border, {
+ baseStyles.push(a.border, {
borderColor: tokens.color.blue_500,
})
- hoverStyles.push(atoms.border, {
+ hoverStyles.push(a.border, {
backgroundColor: light
? t.palette.primary_100
: t.palette.primary_900,
})
} else {
- baseStyles.push(atoms.border, {
+ baseStyles.push(a.border, {
borderColor: light ? tokens.color.blue_200 : tokens.color.blue_900,
})
}
@@ -180,17 +191,17 @@ export function Button({
})
}
} else if (variant === 'outline') {
- baseStyles.push(atoms.border, t.atoms.bg, {
+ baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
})
if (!disabled) {
- baseStyles.push(atoms.border, {
+ baseStyles.push(a.border, {
borderColor: light ? tokens.color.gray_500 : tokens.color.gray_500,
})
- hoverStyles.push(atoms.border, t.atoms.bg_contrast_50)
+ hoverStyles.push(a.border, t.atoms.bg_contrast_50)
} else {
- baseStyles.push(atoms.border, {
+ baseStyles.push(a.border, {
borderColor: light ? tokens.color.gray_200 : tokens.color.gray_800,
})
}
@@ -219,19 +230,19 @@ export function Button({
})
}
} else if (variant === 'outline') {
- baseStyles.push(atoms.border, t.atoms.bg, {
+ baseStyles.push(a.border, t.atoms.bg, {
borderWidth: 1,
})
if (!disabled) {
- baseStyles.push(atoms.border, {
+ baseStyles.push(a.border, {
borderColor: t.palette.negative_600,
})
- hoverStyles.push(atoms.border, {
+ hoverStyles.push(a.border, {
backgroundColor: light ? t.palette.negative_50 : '#2D0614', // darker red
})
} else {
- baseStyles.push(atoms.border, {
+ baseStyles.push(a.border, {
borderColor: light
? t.palette.negative_200
: t.palette.negative_900,
@@ -248,19 +259,9 @@ export function Button({
}
if (size === 'large') {
- baseStyles.push(
- {paddingVertical: 15},
- atoms.px_2xl,
- atoms.rounded_sm,
- atoms.gap_sm,
- )
+ baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_sm)
} else if (size === 'small') {
- baseStyles.push(
- {paddingVertical: 9},
- atoms.px_md,
- atoms.rounded_sm,
- atoms.gap_xs,
- )
+ baseStyles.push({paddingVertical: 9}, a.px_md, a.rounded_sm, a.gap_sm)
}
return {
@@ -305,15 +306,13 @@ export function Button({
}
}, [variant, color])
- const childProps = React.useMemo(
+ const context = React.useMemo(
() => ({
- state,
- props: {
- variant,
- color,
- size,
- disabled: disabled || false,
- },
+ ...state,
+ variant,
+ color,
+ size,
+ disabled: disabled || false,
}),
[state, variant, color, size, disabled],
)
@@ -331,9 +330,9 @@ export function Button({
disabled: disabled || false,
}}
style={[
- atoms.flex_row,
- atoms.align_center,
- atoms.overflow_hidden,
+ a.flex_row,
+ a.align_center,
+ a.overflow_hidden,
...baseStyles,
...(state.hovered || state.pressed ? hoverStyles : []),
...(state.focused ? focusStyles : []),
@@ -354,39 +353,25 @@ export function Button({
locations={gradientLocations}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
- style={[atoms.absolute, atoms.inset_0]}
+ style={[a.absolute, a.inset_0]}
/>
)}
- {typeof children === 'string' ? (
-
- {children}
-
- ) : typeof children === 'function' ? (
- children(childProps)
- ) : (
- children
- )}
+
+ {typeof children === 'string' ? (
+ {children}
+ ) : (
+ children
+ )}
+
)
}
-export function ButtonText({
- children,
- style,
- variant,
- color,
- size,
- disabled,
- ...rest
-}: ButtonTextProps) {
+export function useSharedButtonTextStyles() {
const t = useTheme()
-
- const textStyles = React.useMemo(() => {
- const baseStyles = []
+ const {color, variant, disabled, size} = useButtonContext()
+ return React.useMemo(() => {
+ const baseStyles: TextStyle[] = []
const light = t.name === 'light'
if (color === 'primary') {
@@ -473,26 +458,46 @@ export function ButtonText({
if (size === 'large') {
baseStyles.push(
- atoms.text_md,
+ a.text_md,
web({paddingBottom: 1}),
native({marginTop: 2}),
)
} else {
baseStyles.push(
- atoms.text_md,
+ a.text_md,
web({paddingBottom: 1}),
native({marginTop: 2}),
)
}
- return baseStyles
+ return StyleSheet.flatten(baseStyles)
}, [t, variant, color, size, disabled])
+}
+
+export function ButtonText({children, style, ...rest}: ButtonTextProps) {
+ const textStyles = useSharedButtonTextStyles()
return (
-
+
{children}
)
}
+
+export function ButtonIcon({
+ icon: Comp,
+}: {
+ icon: React.ComponentType
+}) {
+ const {size} = useButtonContext()
+ const textStyles = useSharedButtonTextStyles()
+
+ return (
+
+
+
+ )
+}
diff --git a/src/components/Link.tsx b/src/components/Link.tsx
index 2eb7b6bffd..8f686f3c4c 100644
--- a/src/components/Link.tsx
+++ b/src/components/Link.tsx
@@ -14,8 +14,8 @@ import {
import {sanitizeUrl} from '@braintree/sanitize-url'
import {isWeb} from '#/platform/detection'
-import {useTheme, web} from '#/alf'
-import {Button, ButtonProps} from '#/components/Button'
+import {useTheme, web, flatten} from '#/alf'
+import {Button, ButtonProps, useButtonContext} from '#/components/Button'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {
convertBskyAppUrlIfNeeded,
@@ -25,7 +25,10 @@ import {
import {useModalControls} from '#/state/modals'
import {router} from '#/routes'
-export type LinkProps = Omit & {
+export type LinkProps = Omit<
+ ButtonProps,
+ 'style' | 'onPress' | 'disabled' | 'label'
+> & {
/**
* `TextStyle` to apply to the anchor element itself. Does not apply to any children.
*/
@@ -39,6 +42,7 @@ export type LinkProps = Omit & {
* works for Links with children that are strings i.e. text links.
*/
warnOnMismatchingTextChild?: boolean
+ label?: ButtonProps['label']
} & Pick>[0], 'to'>
/**
@@ -52,12 +56,11 @@ export type LinkProps = Omit & {
export function Link({
children,
to,
- style,
action = 'push',
warnOnMismatchingTextChild,
+ style,
...rest
}: LinkProps) {
- const t = useTheme()
const navigation = useNavigation()
const {href} = useLinkProps({
to:
@@ -67,12 +70,12 @@ export function Link({
const {openModal, closeModal} = useModalControls()
const onPress = React.useCallback(
(e: GestureResponderEvent) => {
- const label = typeof children === 'string' ? children : ''
+ const stringChildren = typeof children === 'string' ? children : ''
const requiresWarning = Boolean(
warnOnMismatchingTextChild &&
- label &&
+ stringChildren &&
isExternal &&
- linkRequiresWarning(href, label),
+ linkRequiresWarning(href, stringChildren),
)
if (requiresWarning) {
@@ -80,7 +83,7 @@ export function Link({
openModal({
name: 'link-warning',
- text: label,
+ text: stringChildren,
href: href,
})
} else {
@@ -139,6 +142,7 @@ export function Link({
return (
- {typeof children === 'string'
- ? ({state}) => (
-
- {children as string}
-
- )
- : children}
+ {typeof children === 'string' ? (
+ {children}
+ ) : (
+ children
+ )}
)
}
+
+function LinkText({
+ children,
+ style,
+}: React.PropsWithChildren<{
+ style?: StyleProp
+}>) {
+ const t = useTheme()
+ const {hovered} = useButtonContext()
+ return (
+
+ {children as string}
+
+ )
+}
diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx
index 168d088408..fbdc84eb4b 100644
--- a/src/view/screens/Storybook/Buttons.tsx
+++ b/src/view/screens/Storybook/Buttons.tsx
@@ -2,8 +2,16 @@ import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
-import {Button, ButtonVariant, ButtonColor} from '#/components/Button'
+import {
+ Button,
+ ButtonVariant,
+ ButtonColor,
+ ButtonIcon,
+ ButtonText,
+} from '#/components/Button'
import {H1} from '#/components/Typography'
+import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
+import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
export function Buttons() {
return (
@@ -83,6 +91,33 @@ export function Buttons() {
)}
+
+
+ Link out
+
+
+
+
+ Link out
+
+
+
+
+
+ See the world
+
)
diff --git a/src/view/screens/Storybook/Links.tsx b/src/view/screens/Storybook/Links.tsx
index 2b9da2a9de..c3b1c0e0f3 100644
--- a/src/view/screens/Storybook/Links.tsx
+++ b/src/view/screens/Storybook/Links.tsx
@@ -13,27 +13,21 @@ export function Links() {
External
-
+
External with custom children
+ style={[a.text_lg]}>
https://blueskyweb.xyz
@@ -41,12 +35,12 @@ export function Links() {
- {({props}) => Link as a button}
+ Link as a button
From 6d3f19c695ae667163788b7eabb0ff1c45bf60c3 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 15:14:42 -0600
Subject: [PATCH 62/68] Add todo
---
src/components/forms/Toggle.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 6fdbaa2eed..7c77207749 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -247,6 +247,7 @@ export function Label({children}: React.PropsWithChildren<{}>) {
)
}
+// TODO(eric) refactor to memoize styles without knowledge of state
export function createSharedToggleStyles({
theme: t,
hovered,
From 83c2b20c393dec76f1b46645927817ab0acf2f6a Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 15:45:37 -0600
Subject: [PATCH 63/68] Add closeAllDialogs control
---
src/App.native.tsx | 17 ++++++----
src/App.web.tsx | 17 ++++++----
src/components/Dialog/context.ts | 12 +++++++
src/state/dialogs/index.tsx | 44 ++++++++++++++++++++++++++
src/view/screens/Storybook/Dialogs.tsx | 16 +++++++++-
src/view/screens/Storybook/Forms.tsx | 1 -
6 files changed, 91 insertions(+), 16 deletions(-)
create mode 100644 src/state/dialogs/index.tsx
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 1c3a3f3e74..41b78fc98a 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -27,6 +27,7 @@ import {queryClient} from 'lib/react-query'
import {TestCtrls} from 'view/com/testing/TestCtrls'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as ModalStateProvider} from 'state/modals'
+import {Provider as DialogStateProvider} from 'state/dialogs'
import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites'
@@ -115,13 +116,15 @@ function App() {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 756d4954eb..1efa0567c7 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -17,6 +17,7 @@ import {ThemeProvider} from 'lib/ThemeContext'
import {queryClient} from 'lib/react-query'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as ModalStateProvider} from 'state/modals'
+import {Provider as DialogStateProvider} from 'state/dialogs'
import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites'
@@ -93,13 +94,15 @@ function App() {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts
index 76473aa243..b28b9f5a25 100644
--- a/src/components/Dialog/context.ts
+++ b/src/components/Dialog/context.ts
@@ -1,4 +1,6 @@
import React from 'react'
+
+import {useDialogStateContext} from '#/state/dialogs'
import {DialogContextProps, DialogControlProps} from '#/components/Dialog/types'
export const Context = React.createContext({
@@ -10,10 +12,20 @@ export function useDialogContext() {
}
export function useDialogControl() {
+ const id = React.useId()
const control = React.useRef({
open: () => {},
close: () => {},
})
+ const {activeDialogs} = useDialogStateContext()
+
+ React.useEffect(() => {
+ activeDialogs.current.set(id, control)
+ return () => {
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ activeDialogs.current.delete(id)
+ }
+ }, [id, activeDialogs])
return {
ref: control,
diff --git a/src/state/dialogs/index.tsx b/src/state/dialogs/index.tsx
new file mode 100644
index 0000000000..4cafaa0861
--- /dev/null
+++ b/src/state/dialogs/index.tsx
@@ -0,0 +1,44 @@
+import React from 'react'
+import {DialogControlProps} from '#/components/Dialog'
+
+const DialogContext = React.createContext<{
+ activeDialogs: React.MutableRefObject<
+ Map>
+ >
+}>({
+ activeDialogs: {
+ current: new Map(),
+ },
+})
+
+const DialogControlContext = React.createContext<{
+ closeAllDialogs(): void
+}>({
+ closeAllDialogs: () => {},
+})
+
+export function useDialogStateContext() {
+ return React.useContext(DialogContext)
+}
+
+export function useDialogStateControlContext() {
+ return React.useContext(DialogControlContext)
+}
+
+export function Provider({children}: React.PropsWithChildren<{}>) {
+ const activeDialogs = React.useRef<
+ Map>
+ >(new Map())
+ const closeAllDialogs = React.useCallback(() => {
+ activeDialogs.current.forEach(dialog => dialog.current.close())
+ }, [])
+ const context = React.useMemo(() => ({activeDialogs}), [])
+ const controls = React.useMemo(() => ({closeAllDialogs}), [closeAllDialogs])
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx
index d4dcc91938..db568c6bd5 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -6,10 +6,12 @@ import {Button} from '#/components/Button'
import {H3, P} from '#/components/Typography'
import * as Dialog from '#/components/Dialog'
import * as Prompt from '#/components/Prompt'
+import {useDialogStateControlContext} from '#/state/dialogs'
export function Dialogs() {
const control = Dialog.useDialogControl()
const prompt = Prompt.usePromptControl()
+ const {closeAllDialogs} = useDialogStateControlContext()
return (
@@ -17,7 +19,10 @@ export function Dialogs() {
variant="outline"
color="secondary"
size="small"
- onPress={() => control.open()}
+ onPress={() => {
+ control.open()
+ prompt.open()
+ }}
label="Open basic dialog">
Open basic dialog
@@ -57,6 +62,15 @@ export function Dialogs() {
A scrollable dialog with an input within it.
{}} label="Type here" />
+
+
+ Close all dialogs
+
{
From 34c9eece6fe07bec19c44b9408298757464366c3 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 15:56:25 -0600
Subject: [PATCH 64/68] Alignment
---
src/components/forms/TextField.tsx | 5 ++++-
src/components/forms/Toggle.tsx | 9 ++++-----
src/components/forms/ToggleButton.tsx | 13 ++++++++-----
3 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index 133ca09cc4..d5b6bcbf8c 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -201,7 +201,10 @@ export function createInput(Component: typeof TextInput) {
a.text_md,
t.atoms.text,
a.px_xs,
- {lineHeight: a.text_md.lineHeight * 1.1875},
+ {
+ lineHeight: a.text_md.lineHeight * 1.1875,
+ textAlignVertical: 'top',
+ },
]}
/>
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index 7c77207749..f653d115d4 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import {Pressable, View, ViewStyle} from 'react-native'
-import {useTheme, atoms as a, web} from '#/alf'
+import {useTheme, atoms as a, web, native} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
@@ -241,6 +241,9 @@ export function Label({children}: React.PropsWithChildren<{}>) {
userSelect: 'none',
color: disabled ? t.palette.contrast_400 : t.palette.contrast_600,
},
+ native({
+ paddingTop: 3,
+ }),
]}>
{children}
@@ -345,8 +348,6 @@ export function Checkbox() {
{
height: 20,
width: 20,
- backgroundColor: selected ? t.palette.primary_500 : undefined,
- borderColor: selected ? t.palette.primary_500 : undefined,
},
baseStyles,
hovered || focused ? baseHoverStyles : {},
@@ -445,8 +446,6 @@ export function Radio() {
{
height: 20,
width: 20,
- backgroundColor: selected ? t.palette.primary_500 : undefined,
- borderColor: selected ? t.palette.primary_500 : undefined,
},
baseStyles,
hovered || focused ? baseHoverStyles : {},
diff --git a/src/components/forms/ToggleButton.tsx b/src/components/forms/ToggleButton.tsx
index 2694dd021c..615fedae8b 100644
--- a/src/components/forms/ToggleButton.tsx
+++ b/src/components/forms/ToggleButton.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import {View, AccessibilityProps, TextStyle, ViewStyle} from 'react-native'
-import {atoms as a, useTheme} from '#/alf'
+import {atoms as a, useTheme, native} from '#/alf'
import {Text} from '#/components/Typography'
import * as Toggle from '#/components/forms/Toggle'
@@ -91,14 +91,17 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
return (
Date: Thu, 18 Jan 2024 18:30:25 -0600
Subject: [PATCH 65/68] Expand color palette
---
src/alf/themes.ts | 91 ++++++++++++++--------
src/alf/tokens.ts | 104 ++++++++++++++-----------
src/components/Button.tsx | 40 +++++-----
src/components/Prompt.tsx | 2 +-
src/components/forms/TextField.tsx | 3 +-
src/components/forms/Toggle.tsx | 7 +-
src/view/screens/Storybook/Palette.tsx | 57 ++++++++++++++
7 files changed, 202 insertions(+), 102 deletions(-)
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index 08aee75512..acd180333f 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -9,8 +9,8 @@ export type ReadonlyPalette = typeof lightPalette
export type Palette = Mutable
export const lightPalette = {
- white: tokens.color.white,
- black: tokens.color.black,
+ white: tokens.color.gray_0,
+ black: tokens.color.gray_1000,
contrast_25: tokens.color.gray_25,
contrast_50: tokens.color.gray_50,
@@ -23,6 +23,8 @@ export const lightPalette = {
contrast_700: tokens.color.gray_700,
contrast_800: tokens.color.gray_800,
contrast_900: tokens.color.gray_900,
+ contrast_950: tokens.color.gray_950,
+ contrast_975: tokens.color.gray_975,
primary_25: tokens.color.blue_25,
primary_50: tokens.color.blue_50,
@@ -35,6 +37,8 @@ export const lightPalette = {
primary_700: tokens.color.blue_700,
primary_800: tokens.color.blue_800,
primary_900: tokens.color.blue_900,
+ primary_950: tokens.color.blue_950,
+ primary_975: tokens.color.blue_975,
positive_25: tokens.color.green_25,
positive_50: tokens.color.green_50,
@@ -47,6 +51,8 @@ export const lightPalette = {
positive_700: tokens.color.green_700,
positive_800: tokens.color.green_800,
positive_900: tokens.color.green_900,
+ positive_950: tokens.color.green_950,
+ positive_975: tokens.color.green_975,
negative_25: tokens.color.red_25,
negative_50: tokens.color.red_50,
@@ -59,23 +65,27 @@ export const lightPalette = {
negative_700: tokens.color.red_700,
negative_800: tokens.color.red_800,
negative_900: tokens.color.red_900,
+ negative_950: tokens.color.red_950,
+ negative_975: tokens.color.red_975,
} as const
export const darkPalette: Palette = {
- white: tokens.color.white,
- black: tokens.color.black,
+ white: tokens.color.gray_0,
+ black: tokens.color.gray_1000,
- contrast_25: tokens.color.gray_900,
- contrast_50: tokens.color.gray_800,
- contrast_100: tokens.color.gray_700,
- contrast_200: tokens.color.gray_600,
- contrast_300: tokens.color.gray_500,
- contrast_400: tokens.color.gray_400,
- contrast_500: tokens.color.gray_300,
- contrast_600: tokens.color.gray_200,
- contrast_700: tokens.color.gray_100,
- contrast_800: tokens.color.gray_50,
- contrast_900: tokens.color.gray_25,
+ contrast_25: tokens.color.gray_975,
+ contrast_50: tokens.color.gray_950,
+ contrast_100: tokens.color.gray_900,
+ contrast_200: tokens.color.gray_800,
+ contrast_300: tokens.color.gray_700,
+ contrast_400: tokens.color.gray_600,
+ contrast_500: tokens.color.gray_500,
+ contrast_600: tokens.color.gray_400,
+ contrast_700: tokens.color.gray_300,
+ contrast_800: tokens.color.gray_200,
+ contrast_900: tokens.color.gray_100,
+ contrast_950: tokens.color.gray_50,
+ contrast_975: tokens.color.gray_25,
primary_25: tokens.color.blue_25,
primary_50: tokens.color.blue_50,
@@ -88,6 +98,8 @@ export const darkPalette: Palette = {
primary_700: tokens.color.blue_700,
primary_800: tokens.color.blue_800,
primary_900: tokens.color.blue_900,
+ primary_950: tokens.color.blue_950,
+ primary_975: tokens.color.blue_975,
positive_25: tokens.color.green_25,
positive_50: tokens.color.green_50,
@@ -100,6 +112,8 @@ export const darkPalette: Palette = {
positive_700: tokens.color.green_700,
positive_800: tokens.color.green_800,
positive_900: tokens.color.green_900,
+ positive_950: tokens.color.green_950,
+ positive_975: tokens.color.green_975,
negative_25: tokens.color.red_25,
negative_50: tokens.color.red_50,
@@ -112,6 +126,8 @@ export const darkPalette: Palette = {
negative_700: tokens.color.red_700,
negative_800: tokens.color.red_800,
negative_900: tokens.color.red_900,
+ negative_950: tokens.color.red_950,
+ negative_975: tokens.color.red_975,
} as const
export const light = {
@@ -121,6 +137,9 @@ export const light = {
text: {
color: lightPalette.black,
},
+ text_contrast_700: {
+ color: lightPalette.contrast_700,
+ },
text_contrast_600: {
color: lightPalette.contrast_600,
},
@@ -179,41 +198,44 @@ export const dim: Theme = {
text: {
color: darkPalette.white,
},
+ text_contrast_700: {
+ color: darkPalette.contrast_800,
+ },
text_contrast_600: {
- color: darkPalette.contrast_600,
+ color: darkPalette.contrast_700,
},
text_contrast_500: {
- color: darkPalette.contrast_500,
+ color: darkPalette.contrast_600,
},
text_contrast_400: {
- color: darkPalette.contrast_400,
+ color: darkPalette.contrast_500,
},
text_inverted: {
color: darkPalette.black,
},
bg: {
- backgroundColor: darkPalette.contrast_25,
+ backgroundColor: darkPalette.contrast_50,
},
bg_contrast_25: {
- backgroundColor: darkPalette.contrast_50,
+ backgroundColor: darkPalette.contrast_100,
},
bg_contrast_50: {
- backgroundColor: darkPalette.contrast_100,
+ backgroundColor: darkPalette.contrast_200,
},
bg_contrast_100: {
- backgroundColor: darkPalette.contrast_200,
+ backgroundColor: darkPalette.contrast_300,
},
bg_contrast_200: {
- backgroundColor: darkPalette.contrast_300,
+ backgroundColor: darkPalette.contrast_400,
},
bg_contrast_300: {
- backgroundColor: darkPalette.contrast_400,
+ backgroundColor: darkPalette.contrast_500,
},
border: {
- borderColor: darkPalette.contrast_50,
+ borderColor: darkPalette.contrast_200,
},
border_contrast: {
- borderColor: darkPalette.contrast_300,
+ borderColor: darkPalette.contrast_400,
},
shadow_sm: {
...atoms.shadow_sm,
@@ -240,6 +262,9 @@ export const dark: Theme = {
text: {
color: darkPalette.white,
},
+ text_contrast_700: {
+ color: darkPalette.contrast_700,
+ },
text_contrast_600: {
color: darkPalette.contrast_600,
},
@@ -253,25 +278,25 @@ export const dark: Theme = {
color: darkPalette.black,
},
bg: {
- backgroundColor: darkPalette.black,
+ backgroundColor: darkPalette.contrast_25,
},
bg_contrast_25: {
- backgroundColor: darkPalette.contrast_25,
+ backgroundColor: darkPalette.contrast_50,
},
bg_contrast_50: {
- backgroundColor: darkPalette.contrast_50,
+ backgroundColor: darkPalette.contrast_100,
},
bg_contrast_100: {
- backgroundColor: darkPalette.contrast_100,
+ backgroundColor: darkPalette.contrast_200,
},
bg_contrast_200: {
- backgroundColor: darkPalette.contrast_200,
+ backgroundColor: darkPalette.contrast_300,
},
bg_contrast_300: {
- backgroundColor: darkPalette.contrast_300,
+ backgroundColor: darkPalette.contrast_400,
},
border: {
- borderColor: darkPalette.contrast_50,
+ borderColor: darkPalette.contrast_100,
},
border_contrast: {
borderColor: darkPalette.contrast_300,
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 36f6f3992a..0e370cdc17 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -1,55 +1,67 @@
+const BLUE_HUE = 211
+const RED_HUE = 346
+const GREEN_HUE = 152
+
export const color = {
- white: '#FFFFFF',
- black: '#080B12',
trueBlack: '#000000',
- gray_25: `#FCFCFD`,
- gray_50: `#F9FAFB`,
- gray_100: `#F3F4F6`,
- gray_200: `#E5E7EB`,
- gray_300: `#D2D6DB`,
- gray_400: `#9DA4AE`,
- gray_500: `#6C737F`,
- gray_600: `#4D5761`,
- gray_700: `#384250`,
- gray_800: `#1F2A37`,
- gray_900: `#111927`,
+ gray_0: `hsl(${BLUE_HUE}, 20%, 100%)`,
+ gray_25: `hsl(${BLUE_HUE}, 20%, 97%)`,
+ gray_50: `hsl(${BLUE_HUE}, 20%, 95%)`,
+ gray_100: `hsl(${BLUE_HUE}, 20%, 90%)`,
+ gray_200: `hsl(${BLUE_HUE}, 20%, 80%)`,
+ gray_300: `hsl(${BLUE_HUE}, 20%, 70%)`,
+ gray_400: `hsl(${BLUE_HUE}, 20%, 60%)`,
+ gray_500: `hsl(${BLUE_HUE}, 20%, 50%)`,
+ gray_600: `hsl(${BLUE_HUE}, 20%, 42%)`,
+ gray_700: `hsl(${BLUE_HUE}, 20%, 34%)`,
+ gray_800: `hsl(${BLUE_HUE}, 20%, 26%)`,
+ gray_900: `hsl(${BLUE_HUE}, 20%, 18%)`,
+ gray_950: `hsl(${BLUE_HUE}, 20%, 10%)`,
+ gray_975: `hsl(${BLUE_HUE}, 20%, 7%)`,
+ gray_1000: `hsl(${BLUE_HUE}, 20%, 4%)`,
- blue_25: `#F5FAFF`,
- blue_50: `#EFF8FF`,
- blue_100: `#D1E9FF`,
- blue_200: `#B2DDFF`,
- blue_300: `#84CAFF`,
- blue_400: `#53B1FD`,
- blue_500: `#1185FE`,
- blue_600: `#1073DB`,
- blue_700: `#0B5CB2`,
- blue_800: `#084585`,
- blue_900: `#08305A`,
+ blue_25: `hsl(${BLUE_HUE}, 99%, 97%)`,
+ blue_50: `hsl(${BLUE_HUE}, 99%, 95%)`,
+ blue_100: `hsl(${BLUE_HUE}, 99%, 90%)`,
+ blue_200: `hsl(${BLUE_HUE}, 99%, 80%)`,
+ blue_300: `hsl(${BLUE_HUE}, 99%, 70%)`,
+ blue_400: `hsl(${BLUE_HUE}, 99%, 60%)`,
+ blue_500: `hsl(${BLUE_HUE}, 99%, 53%)`,
+ blue_600: `hsl(${BLUE_HUE}, 99%, 42%)`,
+ blue_700: `hsl(${BLUE_HUE}, 99%, 34%)`,
+ blue_800: `hsl(${BLUE_HUE}, 99%, 26%)`,
+ blue_900: `hsl(${BLUE_HUE}, 99%, 18%)`,
+ blue_950: `hsl(${BLUE_HUE}, 99%, 10%)`,
+ blue_975: `hsl(${BLUE_HUE}, 99%, 7%)`,
- green_25: `#F6FEF9`,
- green_50: `#ECFDF3`,
- green_100: `#D1FADF`,
- green_200: `#A6F4C5`,
- green_300: `#6CE9A6`,
- green_400: `#32D583`,
- green_500: `#12B76A`,
- green_600: `#039855`,
- green_700: `#027A48`,
- green_800: `#05603A`,
- green_900: `#054F31`,
+ green_25: `hsl(${GREEN_HUE}, 82%, 97%)`,
+ green_50: `hsl(${GREEN_HUE}, 82%, 95%)`,
+ green_100: `hsl(${GREEN_HUE}, 82%, 90%)`,
+ green_200: `hsl(${GREEN_HUE}, 82%, 80%)`,
+ green_300: `hsl(${GREEN_HUE}, 82%, 70%)`,
+ green_400: `hsl(${GREEN_HUE}, 82%, 60%)`,
+ green_500: `hsl(${GREEN_HUE}, 82%, 50%)`,
+ green_600: `hsl(${GREEN_HUE}, 82%, 42%)`,
+ green_700: `hsl(${GREEN_HUE}, 82%, 34%)`,
+ green_800: `hsl(${GREEN_HUE}, 82%, 26%)`,
+ green_900: `hsl(${GREEN_HUE}, 82%, 18%)`,
+ green_950: `hsl(${GREEN_HUE}, 82%, 10%)`,
+ green_975: `hsl(${GREEN_HUE}, 82%, 7%)`,
- red_25: `#FFF5F6`,
- red_50: `#FFF1F3`,
- red_100: `#FFE4E8`,
- red_200: `#FECDD6`,
- red_300: `#FEA3B4`,
- red_400: `#FD6F8E`,
- red_500: `#F63D68`,
- red_600: `#E31B54`,
- red_700: `#C01048`,
- red_800: `#A11043`,
- red_900: `#89123E`,
+ red_25: `hsl(${RED_HUE}, 91%, 97%)`,
+ red_50: `hsl(${RED_HUE}, 91%, 95%)`,
+ red_100: `hsl(${RED_HUE}, 91%, 90%)`,
+ red_200: `hsl(${RED_HUE}, 91%, 80%)`,
+ red_300: `hsl(${RED_HUE}, 91%, 70%)`,
+ red_400: `hsl(${RED_HUE}, 91%, 60%)`,
+ red_500: `hsl(${RED_HUE}, 91%, 50%)`,
+ red_600: `hsl(${RED_HUE}, 91%, 42%)`,
+ red_700: `hsl(${RED_HUE}, 91%, 34%)`,
+ red_800: `hsl(${RED_HUE}, 91%, 26%)`,
+ red_900: `hsl(${RED_HUE}, 91%, 18%)`,
+ red_950: `hsl(${RED_HUE}, 91%, 10%)`,
+ red_975: `hsl(${RED_HUE}, 91%, 7%)`,
} as const
export const space = {
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index a37017decc..d2100f0b42 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -152,8 +152,8 @@ export function Button({
})
hoverStyles.push(a.border, {
backgroundColor: light
- ? t.palette.primary_100
- : t.palette.primary_900,
+ ? t.palette.primary_50
+ : t.palette.primary_950,
})
} else {
baseStyles.push(a.border, {
@@ -175,19 +175,19 @@ export function Button({
if (!disabled) {
baseStyles.push({
backgroundColor: light
- ? tokens.color.gray_200
- : tokens.color.gray_800,
+ ? tokens.color.gray_100
+ : tokens.color.gray_900,
})
hoverStyles.push({
backgroundColor: light
- ? tokens.color.gray_300
- : tokens.color.gray_900,
+ ? tokens.color.gray_200
+ : tokens.color.gray_950,
})
} else {
baseStyles.push({
backgroundColor: light
? tokens.color.gray_300
- : tokens.color.gray_900,
+ : tokens.color.gray_950,
})
}
} else if (variant === 'outline') {
@@ -211,7 +211,7 @@ export function Button({
hoverStyles.push({
backgroundColor: light
? tokens.color.gray_100
- : tokens.color.gray_800,
+ : tokens.color.gray_900,
})
}
}
@@ -219,14 +219,14 @@ export function Button({
if (variant === 'solid') {
if (!disabled) {
baseStyles.push({
- backgroundColor: t.palette.negative_500,
+ backgroundColor: t.palette.negative_400,
})
hoverStyles.push({
- backgroundColor: t.palette.negative_600,
+ backgroundColor: t.palette.negative_500,
})
} else {
baseStyles.push({
- backgroundColor: t.palette.negative_700,
+ backgroundColor: t.palette.negative_600,
})
}
} else if (variant === 'outline') {
@@ -236,10 +236,12 @@ export function Button({
if (!disabled) {
baseStyles.push(a.border, {
- borderColor: t.palette.negative_600,
+ borderColor: t.palette.negative_400,
})
hoverStyles.push(a.border, {
- backgroundColor: light ? t.palette.negative_50 : '#2D0614', // darker red
+ backgroundColor: light
+ ? t.palette.negative_50
+ : t.palette.negative_975,
})
} else {
baseStyles.push(a.border, {
@@ -252,7 +254,9 @@ export function Button({
if (!disabled) {
baseStyles.push(t.atoms.bg)
hoverStyles.push({
- backgroundColor: light ? t.palette.negative_50 : '#2D0614', // darker red
+ backgroundColor: light
+ ? t.palette.negative_100
+ : t.palette.negative_950,
})
}
}
@@ -437,15 +441,15 @@ export function useSharedButtonTextStyles() {
}
} else if (variant === 'outline') {
if (!disabled) {
- baseStyles.push({color: t.palette.negative_500})
+ baseStyles.push({color: t.palette.negative_400})
} else {
- baseStyles.push({color: t.palette.negative_500, opacity: 0.5})
+ baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
}
} else if (variant === 'ghost') {
if (!disabled) {
- baseStyles.push({color: t.palette.negative_500})
+ baseStyles.push({color: t.palette.negative_400})
} else {
- baseStyles.push({color: t.palette.negative_500, opacity: 0.5})
+ baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
}
}
} else {
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index 27169d2fe4..7115f61901 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -55,7 +55,7 @@ export function Title({children}: React.PropsWithChildren<{}>) {
return (
+ style={[a.font_bold, t.atoms.text_contrast_700, a.pb_sm]}>
{children}
)
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index d5b6bcbf8c..00abc8a5d2 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -94,6 +94,7 @@ export function Root({children, isInvalid = false}: RootProps) {
paddingVertical: 14,
},
]}
+ // onPressIn/out don't work on android web
onPress={() => inputRef.current?.focus()}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}>
@@ -214,7 +215,7 @@ export function createInput(Component: typeof TextInput) {
a.absolute,
a.inset_0,
a.rounded_sm,
- t.atoms.bg_contrast_50,
+ t.atoms.bg_contrast_25,
{borderColor: 'transparent', borderWidth: 2},
ctx.hovered ? chromeHover : {},
ctx.focused ? chromeFocus : {},
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index f653d115d4..f2ca0fd927 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -288,7 +288,8 @@ export function createSharedToggleStyles({
} else {
if (hovered || focused) {
baseHover.push({
- backgroundColor: t.palette.contrast_50,
+ backgroundColor:
+ t.name === 'light' ? t.palette.contrast_50 : t.palette.contrast_100,
borderColor: t.palette.contrast_500,
})
}
@@ -313,8 +314,8 @@ export function createSharedToggleStyles({
if (disabled) {
base.push({
- backgroundColor: t.palette.contrast_200,
- borderColor: t.palette.contrast_300,
+ backgroundColor: t.palette.contrast_100,
+ borderColor: t.palette.contrast_400,
})
}
diff --git a/src/view/screens/Storybook/Palette.tsx b/src/view/screens/Storybook/Palette.tsx
index 1844f8d900..b521fe8607 100644
--- a/src/view/screens/Storybook/Palette.tsx
+++ b/src/view/screens/Storybook/Palette.tsx
@@ -8,6 +8,9 @@ export function Palette() {
return (
+
+
+
+
@@ -143,6 +164,18 @@ export function Palette() {
{height: 60, backgroundColor: tokens.color.blue_900},
]}
/>
+
+
+
+
+
+
)
From 3ce125ca3dfc93acb89f8c33ded62fc5249f89ef Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 19:14:13 -0600
Subject: [PATCH 66/68] Hitslops, add shortcut to Storybook in dev
---
.../colorPalette_stroke2_corner0_rounded.svg | 1 +
src/components/forms/TextField.tsx | 15 +++++++++---
src/components/forms/Toggle.tsx | 2 ++
src/components/icons/ColorPalette.tsx | 5 ++++
src/view/com/pager/FeedsTabBarMobile.tsx | 23 +++++++++++++++++--
5 files changed, 41 insertions(+), 5 deletions(-)
create mode 100644 assets/icons/colorPalette_stroke2_corner0_rounded.svg
create mode 100644 src/components/icons/ColorPalette.tsx
diff --git a/assets/icons/colorPalette_stroke2_corner0_rounded.svg b/assets/icons/colorPalette_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..b1056e1a96
--- /dev/null
+++ b/assets/icons/colorPalette_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index 00abc8a5d2..10e0a8a6f5 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -10,8 +10,9 @@ import {
AccessibilityProps,
} from 'react-native'
+import {HITSLOP_20} from 'lib/constants'
import {isWeb} from '#/platform/detection'
-import {useTheme, atoms as a, web, tokens} from '#/alf'
+import {useTheme, atoms as a, web, tokens, android} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Props as SVGIconProps} from '#/components/icons/common'
@@ -95,7 +96,11 @@ export function Root({children, isInvalid = false}: RootProps) {
},
]}
// onPressIn/out don't work on android web
- onPress={() => inputRef.current?.focus()}
+ onPress={e => {
+ e.preventDefault()
+ e.stopPropagation()
+ inputRef.current?.focus()
+ }}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}>
{children}
@@ -195,6 +200,7 @@ export function createInput(Component: typeof TextInput) {
onBlur={ctx.onBlur}
placeholder={placeholder || label}
placeholderTextColor={t.palette.contrast_500}
+ hitSlop={HITSLOP_20}
style={[
a.relative,
a.z_20,
@@ -202,9 +208,12 @@ export function createInput(Component: typeof TextInput) {
a.text_md,
t.atoms.text,
a.px_xs,
+ android({
+ paddingBottom: 2,
+ }),
{
lineHeight: a.text_md.lineHeight * 1.1875,
- textAlignVertical: 'top',
+ textAlignVertical: rest.multiline ? 'top' : undefined,
},
]}
/>
diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx
index f2ca0fd927..ad82bdff5f 100644
--- a/src/components/forms/Toggle.tsx
+++ b/src/components/forms/Toggle.tsx
@@ -1,6 +1,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 {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
@@ -197,6 +198,7 @@ export function Item({
void},
) {
@@ -68,7 +73,7 @@ export function FeedsTabBar(
headerHeight.value = e.nativeEvent.layout.height
}}>
-
+
-
+
+ {IS_DEV && (
+
+
+
+ )}
+
{hasSession && (
Date: Thu, 18 Jan 2024 19:17:51 -0600
Subject: [PATCH 67/68] Fix multiline on ios
---
src/components/forms/TextField.tsx | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index 10e0a8a6f5..1ee58303ae 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -96,11 +96,7 @@ export function Root({children, isInvalid = false}: RootProps) {
},
]}
// onPressIn/out don't work on android web
- onPress={e => {
- e.preventDefault()
- e.stopPropagation()
- inputRef.current?.focus()
- }}
+ onPress={() => inputRef.current?.focus()}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}>
{children}
@@ -214,6 +210,7 @@ export function createInput(Component: typeof TextInput) {
{
lineHeight: a.text_md.lineHeight * 1.1875,
textAlignVertical: rest.multiline ? 'top' : undefined,
+ minHeight: rest.multiline ? 60 : undefined,
},
]}
/>
From 462c70a3645a0ed986839697f058f1e0ddd72bc3 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 18 Jan 2024 19:37:02 -0600
Subject: [PATCH 68/68] Mark dialog close button as unused
---
src/components/Dialog/index.web.tsx | 61 +++++++++++------------------
1 file changed, 23 insertions(+), 38 deletions(-)
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 6099efdd86..305c00e97a 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -6,12 +6,10 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useTheme, atoms as a, useBreakpoints, web} from '#/alf'
-import {Text} from '#/components/Typography'
import {Portal} from '#/components/Portal'
-import {Button} from '#/components/Button'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
-import {Context, useDialogContext} from '#/components/Dialog/context'
+import {Context} from '#/components/Dialog/context'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
@@ -172,38 +170,25 @@ export function Handle() {
return null
}
-export function Close() {
- const {_} = useLingui()
- const t = useTheme()
- const {close} = useDialogContext()
- return (
-
-
- {() => (
-
- X
-
- )}
-
-
- )
-}
+/**
+ * TODO(eric) unused rn
+ */
+// export function Close() {
+// const {_} = useLingui()
+// const t = useTheme()
+// const {close} = useDialogContext()
+// return (
+//
+//
+//
+//
+// )
+// }