move ghosttext component to textfield

This commit is contained in:
Samuel Newman
2025-07-17 19:05:44 +03:00
parent acdd320e83
commit 1d0e0c8b47
2 changed files with 79 additions and 70 deletions
+75 -9
View File
@@ -1,4 +1,4 @@
import React from 'react'
import {createContext, useContext, useMemo, useRef} from 'react'
import {
type AccessibilityProps,
StyleSheet,
@@ -16,7 +16,9 @@ import {
applyFonts,
atoms as a,
ios,
platform,
type TextStyleProp,
tokens,
useAlf,
useTheme,
web,
@@ -25,7 +27,7 @@ import {useInteractionState} from '#/components/hooks/useInteractionState'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {Text} from '#/components/Typography'
const Context = React.createContext<{
const Context = createContext<{
inputRef: React.RefObject<TextInput> | null
isInvalid: boolean
hovered: boolean
@@ -48,7 +50,7 @@ const Context = React.createContext<{
export type RootProps = React.PropsWithChildren<{isInvalid?: boolean}>
export function Root({children, isInvalid = false}: RootProps) {
const inputRef = React.useRef<TextInput>(null)
const inputRef = useRef<TextInput>(null)
const {
state: hovered,
onIn: onHoverIn,
@@ -56,7 +58,7 @@ export function Root({children, isInvalid = false}: RootProps) {
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const context = React.useMemo(
const context = useMemo(
() => ({
inputRef,
hovered,
@@ -96,7 +98,7 @@ export function Root({children, isInvalid = false}: RootProps) {
export function useSharedInputStyles() {
const t = useTheme()
return React.useMemo(() => {
return useMemo(() => {
const hover: ViewStyle[] = [
{
borderColor: t.palette.contrast_100,
@@ -158,7 +160,7 @@ export function createInput(Component: typeof TextInput) {
}: InputProps) {
const t = useTheme()
const {fonts} = useAlf()
const ctx = React.useContext(Context)
const ctx = useContext(Context)
const withinRoot = Boolean(ctx.inputRef)
const {chromeHover, chromeFocus, chromeError, chromeErrorHover} =
@@ -283,8 +285,8 @@ export function LabelText({
export function Icon({icon: Comp}: {icon: React.ComponentType<SVGIconProps>}) {
const t = useTheme()
const ctx = React.useContext(Context)
const {hover, focus, errorHover, errorFocus} = React.useMemo(() => {
const ctx = useContext(Context)
const {hover, focus, errorHover, errorFocus} = useMemo(() => {
const hover: TextStyle[] = [
{
color: t.palette.contrast_800,
@@ -342,7 +344,7 @@ export function SuffixText({
}
>) {
const t = useTheme()
const ctx = React.useContext(Context)
const ctx = useContext(Context)
return (
<Text
accessibilityLabel={label}
@@ -362,3 +364,67 @@ export function SuffixText({
</Text>
)
}
export function GhostText({
children,
value,
}: {
children: string
value: string
}) {
const t = useTheme()
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text
return (
<View
style={[
a.pointer_events_none,
a.absolute,
a.z_10,
{
paddingLeft: platform({
native:
// input padding
tokens.space.md +
// icon
tokens.space.xl +
// icon padding
tokens.space.xs +
// text input padding
tokens.space.xs,
web:
// icon
tokens.space.xl +
// icon padding
tokens.space.xs +
// text input padding
tokens.space.xs,
}),
},
web(a.pr_md),
a.overflow_hidden,
a.max_w_full,
]}
aria-hidden={true}
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants">
<Text
style={[
{color: 'transparent'},
a.text_md,
{lineHeight: a.text_md.fontSize * 1.1875},
a.w_full,
]}
numberOfLines={1}>
{children}
<Text
style={[
t.atoms.text_contrast_low,
a.text_md,
{lineHeight: a.text_md.fontSize * 1.1875},
]}>
{value}
</Text>
</Text>
</View>
)
}
+4 -61
View File
@@ -22,7 +22,7 @@ import {useHandleAvailabilityQuery} from '#/state/queries/handle-availablility'
import {useAgent} from '#/state/session'
import {ScreenTransition} from '#/screens/Login/ScreenTransition'
import {useSignupContext} from '#/screens/Signup/state'
import {atoms as a, platform, tokens, useTheme, web} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import * as TextField from '#/components/forms/TextField'
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {At_Stroke2_Corner0_Rounded as AtIcon} from '#/components/icons/At'
@@ -152,9 +152,10 @@ export function StepHandle() {
autoFocus
autoComplete="off"
/>
{draftValue.length > 0 && (
<GhostText value={state.userDomain}>{draftValue}</GhostText>
<TextField.GhostText value={state.userDomain}>
{draftValue}
</TextField.GhostText>
)}
</TextField.Root>
</View>
@@ -297,61 +298,3 @@ function RequirementIcon({
return <Loader size="sm" style={t.atoms.text_contrast_low} />
}
}
function GhostText({children, value}: {children: string; value: string}) {
const t = useTheme()
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text
return (
<View
style={[
a.pointer_events_none,
a.absolute,
a.z_10,
{
paddingLeft: platform({
native:
// input padding
tokens.space.md +
// icon
tokens.space.xl +
// icon padding
tokens.space.xs +
// text input padding
tokens.space.xs,
web:
// icon
tokens.space.xl +
// icon padding
tokens.space.xs +
// text input padding
tokens.space.xs,
}),
},
web(a.pr_md),
a.overflow_hidden,
a.max_w_full,
]}
aria-hidden={true}
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants">
<Text
style={[
{color: 'transparent'},
a.text_md,
{lineHeight: a.text_md.fontSize * 1.1875},
a.w_full,
]}
numberOfLines={1}>
{children}
<Text
style={[
t.atoms.text_contrast_low,
a.text_md,
{lineHeight: a.text_md.fontSize * 1.1875},
]}>
{value}
</Text>
</Text>
</View>
)
}