-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2a4d336
commit 2eb242a
Showing
3 changed files
with
166 additions
and
90 deletions.
There are no files selected for viewing
117 changes: 117 additions & 0 deletions
117
packages/bento-design-system/src/TextField/InternalTextInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { useBentoConfig } from "../BentoConfigContext"; | ||
import { Box } from "../Box/Box"; | ||
import { Children } from "../util/Children"; | ||
import { LocalizedString } from "../util/LocalizedString"; | ||
import { getRadiusPropsFromConfig } from "../util/BorderRadiusConfig"; | ||
import { inputRecipe } from "../Field/Field.css"; | ||
import { bodyRecipe } from "../Typography/Body/Body.css"; | ||
import { getReadOnlyBackgroundStyle } from "../Field/utils"; | ||
import useDimensions from "react-cool-dimensions"; | ||
import { match } from "ts-pattern"; | ||
import { Columns } from "../Layout/Columns"; | ||
import { IconButton } from "../IconButton/IconButton"; | ||
import { useDefaultMessages } from "../util/useDefaultMessages"; | ||
import { useState } from "react"; | ||
|
||
type Props = { | ||
inputProps: React.InputHTMLAttributes<HTMLInputElement>; | ||
inputRef: React.Ref<HTMLInputElement>; | ||
placeholder?: LocalizedString; | ||
validationState?: "valid" | "invalid"; | ||
type?: "text" | "email" | "url" | "password"; | ||
disabled?: boolean; | ||
isReadOnly?: boolean; | ||
rightAccessory?: Children; | ||
showPasswordLabel?: never; | ||
hidePasswordLabel?: never; | ||
}; | ||
|
||
export function InternalTextInput(props: Props) { | ||
const config = useBentoConfig().input; | ||
const { defaultMessages } = useDefaultMessages(); | ||
|
||
const { observe: rightAccessoryRef, width: rightAccessoryWidth } = useDimensions({ | ||
// This is needed to include the padding in the width | ||
useBorderBoxSize: true, | ||
}); | ||
|
||
const [showPassword, setShowPassword] = useState(false); | ||
const passwordIcon = showPassword ? config.passwordHideIcon : config.passwordShowIcon; | ||
const passwordIconLabel = showPassword | ||
? props.hidePasswordLabel ?? defaultMessages.TextField.hidePasswordLabel | ||
: props.showPasswordLabel ?? defaultMessages.TextField.showPasswordLabel; | ||
|
||
const type = match(props.type ?? "text") | ||
.with("password", () => (showPassword ? "text" : "password")) | ||
.with("text", "email", "url", () => props.type) | ||
.exhaustive(); | ||
|
||
const rightAccessory = match(props.type ?? "text") | ||
.with("password", () => ( | ||
// if we have both a rightAccessory and type='password', display the accessory on the left of the password toggle field | ||
<Columns space={config.paddingX} alignY="center"> | ||
<IconButton | ||
size={config.passwordIconSize} | ||
icon={passwordIcon} | ||
onPress={() => setShowPassword((prevValue) => !prevValue)} | ||
kind="transparent" | ||
hierarchy="secondary" | ||
label={passwordIconLabel} | ||
/> | ||
{props.rightAccessory} | ||
</Columns> | ||
)) | ||
.with("email", "text", "url", () => props.rightAccessory) | ||
.exhaustive(); | ||
|
||
return ( | ||
<Box position="relative" display="flex"> | ||
<Box | ||
as="input" | ||
{...props.inputProps} | ||
ref={props.inputRef} | ||
placeholder={props.placeholder} | ||
type={type} | ||
// NOTE(gabro): this is to please TS, since the inputProps type is very broad | ||
color={undefined} | ||
width={undefined} | ||
height={undefined} | ||
className={[ | ||
inputRecipe({ | ||
validation: props.isReadOnly ? "notSet" : props.validationState ?? "notSet", | ||
}), | ||
bodyRecipe({ | ||
color: props.disabled ? "disabled" : "primary", | ||
weight: "default", | ||
size: config.fontSize, | ||
ellipsis: false, | ||
}), | ||
]} | ||
{...getRadiusPropsFromConfig(config.radius)} | ||
paddingX={config.paddingX} | ||
paddingY={config.paddingY} | ||
background={config.background.default} | ||
style={{ | ||
paddingRight: rightAccessory ? rightAccessoryWidth : undefined, | ||
flexGrow: 1, | ||
...getReadOnlyBackgroundStyle(config), | ||
}} | ||
/> | ||
{rightAccessory && ( | ||
<Box | ||
ref={rightAccessoryRef} | ||
position="absolute" | ||
display="flex" | ||
justifyContent="center" | ||
alignItems="center" | ||
paddingX={config.paddingX} | ||
top={0} | ||
bottom={0} | ||
right={0} | ||
> | ||
{rightAccessory} | ||
</Box> | ||
)} | ||
</Box> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { HTMLAttributes, useRef } from "react"; | ||
import { AtLeast } from "../util/AtLeast"; | ||
import { FieldProps } from "../Field/FieldProps"; | ||
import { InternalTextInput } from "./InternalTextInput"; | ||
import { useTextField } from "@react-aria/textfield"; | ||
import { LocalizedString } from "../util/LocalizedString"; | ||
import { Children } from "../util/Children"; | ||
|
||
type Props = AtLeast<Pick<HTMLAttributes<HTMLInputElement>, "aria-label" | "aria-labelledby">> & | ||
Pick<FieldProps<string>, "autoFocus" | "disabled" | "onBlur" | "onChange" | "value"> & { | ||
validationState: "valid" | "invalid"; | ||
placeholder?: LocalizedString; | ||
isReadOnly?: boolean; | ||
type?: "text" | "email" | "url" | "password"; | ||
rightAccessory?: Children; | ||
showPasswordLabel?: never; | ||
hidePasswordLabel?: never; | ||
}; | ||
|
||
/** | ||
* Standalone text input component. | ||
* | ||
* Since it has no label, users must pass either `aria-label` or `aria-labelledby` in order to | ||
* preserve accessibility. | ||
*/ | ||
export function TextInput(props: Props) { | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const { inputProps } = useTextField( | ||
{ | ||
...props, | ||
validationState: props.validationState, | ||
isDisabled: props.disabled, | ||
}, | ||
inputRef | ||
); | ||
|
||
return <InternalTextInput inputProps={inputProps} inputRef={inputRef} {...props} />; | ||
} | ||
|
||
export type { Props as TextInputProps }; |