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() { +