Check handle as you type (#8601)
* check handle as you type * metrics * add metric types * fix overflow * only check reserved handles for bsky.social, fix test * change validation check name * tweak input * move ghosttext component to textfield * tweak styles to try and match latest * add suggestions * improvements, metrics * share logic between typeahead and next button * Apply suggestions from code review Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * update checks, disable button if unavailable * convert to lowercase * fix bug with checkHandleAvailability * add gate * move files around to make clearer * fix bad import * Fix flashing next button * Enable for TF --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Hailey <me@haileyok.com> Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const Circle_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Z',
|
||||
})
|
||||
Reference in New Issue
Block a user