[refacto] Introduce stateless TextInputV2 (#5013)
## Context As discussed with @lucasbordeau and @charlesBochet we are looking at making low level UI components stateless when possible. Therefore TextInput should not handle a hotkey state. Instead hotkeys should be defined in the parent component (as done here in CreateProfile). Introducing here TextInputV2 that is stateless and that can already replace TextInput without any behaviour change everywhere it is used with `disableHotkey` prop. ## How was it tested? Locally + Storybook --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -5,7 +5,7 @@ import { Key } from 'ts-key-enum';
|
||||
import { FieldAddressDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
||||
import { FieldAddressValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { CountrySelect } from '@/ui/input/components/internal/country/components/CountrySelect';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -179,7 +179,7 @@ export const AddressInput = ({
|
||||
|
||||
return (
|
||||
<StyledAddressContainer ref={wrapperRef}>
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={internalValue.addressStreet1 ?? ''}
|
||||
ref={inputRefs['addressStreet1']}
|
||||
@ -187,46 +187,41 @@ export const AddressInput = ({
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressStreet1')}
|
||||
onFocus={getFocusHandler('addressStreet1')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
value={internalValue.addressStreet2 ?? ''}
|
||||
ref={inputRefs['addressStreet2']}
|
||||
label="ADDRESS 2"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressStreet2')}
|
||||
onFocus={getFocusHandler('addressStreet2')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<StyledHalfRowContainer>
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
value={internalValue.addressCity ?? ''}
|
||||
ref={inputRefs['addressCity']}
|
||||
label="CITY"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressCity')}
|
||||
onFocus={getFocusHandler('addressCity')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
value={internalValue.addressState ?? ''}
|
||||
ref={inputRefs['addressState']}
|
||||
label="STATE"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressState')}
|
||||
onFocus={getFocusHandler('addressState')}
|
||||
disableHotkeys
|
||||
/>
|
||||
</StyledHalfRowContainer>
|
||||
<StyledHalfRowContainer>
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
value={internalValue.addressPostcode ?? ''}
|
||||
ref={inputRefs['addressPostcode']}
|
||||
label="POST CODE"
|
||||
fullWidth
|
||||
onChange={getChangeHandler('addressPostcode')}
|
||||
onFocus={getFocusHandler('addressPostcode')}
|
||||
disableHotkeys
|
||||
/>
|
||||
<CountrySelect
|
||||
onChange={getChangeHandler('addressCountry')}
|
||||
|
||||
@ -1,141 +1,26 @@
|
||||
import {
|
||||
ChangeEvent,
|
||||
FocusEventHandler,
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
InputHTMLAttributes,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { FocusEventHandler, forwardRef, useRef } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconAlertCircle, IconComponent, IconEye, IconEyeOff } from 'twenty-ui';
|
||||
|
||||
import {
|
||||
TextInputV2,
|
||||
TextInputV2ComponentProps,
|
||||
} from '@/ui/input/components/TextInputV2';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
|
||||
import { InputHotkeyScope } from '../types/InputHotkeyScope';
|
||||
|
||||
const StyledContainer = styled.div<Pick<TextInputComponentProps, 'fullWidth'>>`
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')};
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input<Pick<TextInputComponentProps, 'fullWidth'>>`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-right: none;
|
||||
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
height: 32px;
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
|
||||
&::placeholder,
|
||||
&::-webkit-input-placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledErrorHelper = styled.div`
|
||||
color: ${({ theme }) => theme.color.red};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledTrailingIconContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-left: none;
|
||||
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledTrailingIcon = styled.div`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const INPUT_TYPE_PASSWORD = 'password';
|
||||
|
||||
export type TextInputComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'onKeyDown'
|
||||
> & {
|
||||
className?: string;
|
||||
label?: string;
|
||||
onChange?: (text: string) => void;
|
||||
fullWidth?: boolean;
|
||||
export type TextInputComponentProps = TextInputV2ComponentProps & {
|
||||
disableHotkeys?: boolean;
|
||||
error?: string;
|
||||
RightIcon?: IconComponent;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: () => void;
|
||||
};
|
||||
|
||||
const TextInputComponent = (
|
||||
{
|
||||
className,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
fullWidth,
|
||||
error,
|
||||
required,
|
||||
type,
|
||||
disableHotkeys = false,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
disabled,
|
||||
tabIndex,
|
||||
RightIcon,
|
||||
}: TextInputComponentProps,
|
||||
// eslint-disable-next-line @nx/workspace-component-props-naming
|
||||
ref: ForwardedRef<HTMLInputElement>,
|
||||
): JSX.Element => {
|
||||
const theme = useTheme();
|
||||
|
||||
const TextInputComponent = ({
|
||||
onFocus,
|
||||
onBlur,
|
||||
disableHotkeys = false,
|
||||
...props
|
||||
}: TextInputComponentProps): JSX.Element => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const combinedRef = useCombinedRefs(ref, inputRef);
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
@ -167,57 +52,8 @@ const TextInputComponent = (
|
||||
{ enabled: !disableHotkeys },
|
||||
);
|
||||
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
|
||||
const handleTogglePasswordVisibility = () => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer className={className} fullWidth={fullWidth ?? false}>
|
||||
{label && <StyledLabel>{label + (required ? '*' : '')}</StyledLabel>}
|
||||
<StyledInputContainer>
|
||||
<StyledInput
|
||||
autoComplete="off"
|
||||
ref={combinedRef}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
type={passwordVisible ? 'text' : type}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange?.(event.target.value);
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
{...{ autoFocus, disabled, placeholder, required, value }}
|
||||
/>
|
||||
<StyledTrailingIconContainer>
|
||||
{error && (
|
||||
<StyledTrailingIcon>
|
||||
<IconAlertCircle size={16} color={theme.color.red} />
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
{!error && type === INPUT_TYPE_PASSWORD && (
|
||||
<StyledTrailingIcon
|
||||
onClick={handleTogglePasswordVisibility}
|
||||
data-testid="reveal-password-button"
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<IconEyeOff size={theme.icon.size.md} />
|
||||
) : (
|
||||
<IconEye size={theme.icon.size.md} />
|
||||
)}
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
{!error && type !== INPUT_TYPE_PASSWORD && !!RightIcon && (
|
||||
<StyledTrailingIcon>
|
||||
<RightIcon size={theme.icon.size.md} />
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
</StyledTrailingIconContainer>
|
||||
</StyledInputContainer>
|
||||
{error && <StyledErrorHelper>{error}</StyledErrorHelper>}
|
||||
</StyledContainer>
|
||||
);
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <TextInputV2 {...props} onFocus={handleFocus} onBlur={handleBlur} />;
|
||||
};
|
||||
|
||||
export const TextInput = forwardRef(TextInputComponent);
|
||||
|
||||
@ -0,0 +1,188 @@
|
||||
import {
|
||||
ChangeEvent,
|
||||
FocusEventHandler,
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
InputHTMLAttributes,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconAlertCircle, IconComponent, IconEye, IconEyeOff } from 'twenty-ui';
|
||||
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
|
||||
const StyledContainer = styled.div<
|
||||
Pick<TextInputV2ComponentProps, 'fullWidth'>
|
||||
>`
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')};
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input<Pick<TextInputV2ComponentProps, 'fullWidth'>>`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-right: none;
|
||||
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
height: 32px;
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
|
||||
&::placeholder,
|
||||
&::-webkit-input-placeholder {
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledErrorHelper = styled.div`
|
||||
color: ${({ theme }) => theme.color.red};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledTrailingIconContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-left: none;
|
||||
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledTrailingIcon = styled.div`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const INPUT_TYPE_PASSWORD = 'password';
|
||||
|
||||
export type TextInputV2ComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'onKeyDown'
|
||||
> & {
|
||||
className?: string;
|
||||
label?: string;
|
||||
onChange?: (text: string) => void;
|
||||
fullWidth?: boolean;
|
||||
error?: string;
|
||||
RightIcon?: IconComponent;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
};
|
||||
|
||||
const TextInputV2Component = (
|
||||
{
|
||||
className,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
fullWidth,
|
||||
error,
|
||||
required,
|
||||
type,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
disabled,
|
||||
tabIndex,
|
||||
RightIcon,
|
||||
}: TextInputV2ComponentProps,
|
||||
// eslint-disable-next-line @nx/workspace-component-props-naming
|
||||
ref: ForwardedRef<HTMLInputElement>,
|
||||
): JSX.Element => {
|
||||
const theme = useTheme();
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const combinedRef = useCombinedRefs(ref, inputRef);
|
||||
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
|
||||
const handleTogglePasswordVisibility = () => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer className={className} fullWidth={fullWidth ?? false}>
|
||||
{label && <StyledLabel>{label + (required ? '*' : '')}</StyledLabel>}
|
||||
<StyledInputContainer>
|
||||
<StyledInput
|
||||
autoComplete="off"
|
||||
ref={combinedRef}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
type={passwordVisible ? 'text' : type}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange?.(event.target.value);
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
{...{ autoFocus, disabled, placeholder, required, value }}
|
||||
/>
|
||||
<StyledTrailingIconContainer>
|
||||
{error && (
|
||||
<StyledTrailingIcon>
|
||||
<IconAlertCircle size={16} color={theme.color.red} />
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
{!error && type === INPUT_TYPE_PASSWORD && (
|
||||
<StyledTrailingIcon
|
||||
onClick={handleTogglePasswordVisibility}
|
||||
data-testid="reveal-password-button"
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<IconEyeOff size={theme.icon.size.md} />
|
||||
) : (
|
||||
<IconEye size={theme.icon.size.md} />
|
||||
)}
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
{!error && type !== INPUT_TYPE_PASSWORD && !!RightIcon && (
|
||||
<StyledTrailingIcon>
|
||||
<RightIcon size={theme.icon.size.md} />
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
</StyledTrailingIconContainer>
|
||||
</StyledInputContainer>
|
||||
{error && <StyledErrorHelper>{error}</StyledErrorHelper>}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const TextInputV2 = forwardRef(TextInputV2Component);
|
||||
@ -0,0 +1,42 @@
|
||||
import { useState } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import {
|
||||
TextInputV2,
|
||||
TextInputV2ComponentProps,
|
||||
} from '@/ui/input/components/TextInputV2';
|
||||
|
||||
type RenderProps = TextInputV2ComponentProps;
|
||||
|
||||
const Render = (args: RenderProps) => {
|
||||
const [value, setValue] = useState(args.value);
|
||||
const handleChange = (text: string) => {
|
||||
args.onChange?.(text);
|
||||
setValue(text);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <TextInputV2 {...args} value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof TextInputV2> = {
|
||||
title: 'UI/Input/TextInputV2',
|
||||
component: TextInputV2,
|
||||
decorators: [ComponentDecorator],
|
||||
args: { placeholder: 'Tim' },
|
||||
render: Render,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TextInputV2>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Filled: Story = {
|
||||
args: { value: 'Tim' },
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true, value: 'Tim' },
|
||||
};
|
||||
@ -1,8 +1,9 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
|
||||
import styled from '@emotion/styled';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
@ -13,10 +14,12 @@ import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
@ -123,17 +126,22 @@ export const CreateProfile = () => {
|
||||
],
|
||||
);
|
||||
|
||||
const [isEditingMode, setIsEditingMode] = useState(false);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
if (isEditingMode) {
|
||||
onSubmit(getValues());
|
||||
}
|
||||
},
|
||||
PageHotkeyScope.CreateProfile,
|
||||
);
|
||||
|
||||
if (onboardingStatus !== OnboardingStatus.OngoingProfileCreation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onNameInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
onSubmit(getValues());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title withMarginTop={false}>Create profile</Title>
|
||||
@ -157,17 +165,19 @@ export const CreateProfile = () => {
|
||||
field: { onChange, onBlur, value },
|
||||
fieldState: { error },
|
||||
}) => (
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
label="First Name"
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onFocus={() => setIsEditingMode(true)}
|
||||
onBlur={() => {
|
||||
onBlur();
|
||||
setIsEditingMode(false);
|
||||
}}
|
||||
onChange={onChange}
|
||||
placeholder="Tim"
|
||||
error={error?.message}
|
||||
fullWidth
|
||||
onKeyDown={onNameInputKeyDown}
|
||||
disableHotkeys
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@ -178,16 +188,18 @@ export const CreateProfile = () => {
|
||||
field: { onChange, onBlur, value },
|
||||
fieldState: { error },
|
||||
}) => (
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
label="Last Name"
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onFocus={() => setIsEditingMode(true)}
|
||||
onBlur={() => {
|
||||
onBlur();
|
||||
setIsEditingMode(false);
|
||||
}}
|
||||
onChange={onChange}
|
||||
placeholder="Cook"
|
||||
error={error?.message}
|
||||
fullWidth
|
||||
onKeyDown={onNameInputKeyDown}
|
||||
disableHotkeys
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -20,7 +20,7 @@ import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Loader } from '@/ui/feedback/loader/components/Loader.tsx';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { useActivateWorkspaceMutation } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -141,7 +141,7 @@ export const CreateWorkspace = () => {
|
||||
field: { onChange, onBlur, value },
|
||||
fieldState: { error },
|
||||
}) => (
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={value}
|
||||
placeholder="Apple"
|
||||
@ -150,7 +150,6 @@ export const CreateWorkspace = () => {
|
||||
error={error?.message}
|
||||
onKeyDown={handleKeyDown}
|
||||
fullWidth
|
||||
disableHotkeys
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -18,7 +18,7 @@ import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
|
||||
import {
|
||||
useUpdatePasswordViaResetTokenMutation,
|
||||
@ -191,12 +191,11 @@ export const PasswordReset = () => {
|
||||
}}
|
||||
>
|
||||
<StyledInputContainer>
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={email}
|
||||
placeholder="Email"
|
||||
fullWidth
|
||||
disableHotkeys
|
||||
disabled
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
@ -218,7 +217,7 @@ export const PasswordReset = () => {
|
||||
fieldState: { error },
|
||||
}) => (
|
||||
<StyledInputContainer>
|
||||
<TextInput
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={value}
|
||||
type="password"
|
||||
@ -227,7 +226,6 @@ export const PasswordReset = () => {
|
||||
onChange={onChange}
|
||||
error={error?.message}
|
||||
fullWidth
|
||||
disableHotkeys
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user