Removed all v2 in naming of state management logic (#13675)
This PR removes any V2 naming in state management logic and some minor utils and hooks. It has a lot of changes but nearly all of them were made by the rename functionality of vscode which is deterministic, so it shouldn't introduce any regression. QA has been made on this PR on the main features of the app without any noticeable issue. Also renamed some other v2 naming related items : - TextInputV2 => TextInput - TextInput => SettingsTextInput - ObjectFilterDropdownFilterSelectMenuItemV2 => ObjectFilterDropdownFilterSelectMenuItem - useInitDraftValueV2 => useInitDraftValue - useOpenRecordTableCellV2 => useOpenRecordTableCell
This commit is contained in:
@@ -14,7 +14,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconApps, IconComponent, useIcons } from 'twenty-ui/display';
|
||||
import {
|
||||
@@ -87,7 +87,7 @@ const IconPickerIcon = ({
|
||||
Icon,
|
||||
focusedIconKey,
|
||||
}: IconPickerIconProps) => {
|
||||
const isSelectedItemId = useRecoilComponentValueV2(
|
||||
const isSelectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
iconKey,
|
||||
);
|
||||
@@ -200,7 +200,7 @@ export const IconPicker = ({
|
||||
const selectableListInstanceId = 'icon-list';
|
||||
|
||||
const focusedIconKey =
|
||||
useRecoilComponentValueV2(
|
||||
useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
selectableListInstanceId,
|
||||
) ?? undefined;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconComponent } from 'twenty-ui/display';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
@@ -124,7 +124,7 @@ export const Select = <Value extends SelectValue>({
|
||||
|
||||
const selectableItemIdArray = filteredOptions.map((option) => option.label);
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-l
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { TagColor } from 'twenty-ui/components';
|
||||
@@ -41,7 +41,7 @@ export const SelectInput = ({
|
||||
SelectableListComponentInstanceContext,
|
||||
);
|
||||
|
||||
const selectedItemId = useRecoilComponentValueV2(
|
||||
const selectedItemId = useRecoilComponentValue(
|
||||
selectedItemIdComponentState,
|
||||
selectableListInstanceId,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { FocusEventHandler, useEffect, useRef, useState } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import {
|
||||
TextInput,
|
||||
TextInputComponentProps,
|
||||
} from '@/ui/input/components/TextInput';
|
||||
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export type SettingsTextInputProps = TextInputComponentProps & {
|
||||
instanceId: string;
|
||||
disableHotkeys?: boolean;
|
||||
onInputEnter?: () => void;
|
||||
dataTestId?: string;
|
||||
autoFocusOnMount?: boolean;
|
||||
autoSelectOnMount?: boolean;
|
||||
};
|
||||
|
||||
export const SettingsTextInput = ({
|
||||
instanceId,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onInputEnter,
|
||||
disableHotkeys = false,
|
||||
autoFocusOnMount,
|
||||
autoSelectOnMount,
|
||||
dataTestId,
|
||||
...props
|
||||
}: SettingsTextInputProps) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocusOnMount === true) {
|
||||
inputRef.current?.focus();
|
||||
setIsFocused(true);
|
||||
}
|
||||
}, [autoFocusOnMount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoSelectOnMount === true) {
|
||||
inputRef.current?.select();
|
||||
}
|
||||
}, [autoSelectOnMount]);
|
||||
|
||||
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||
const { removeFocusItemFromFocusStackById } =
|
||||
useRemoveFocusItemFromFocusStackById();
|
||||
|
||||
const handleFocus: FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
onFocus?.(e);
|
||||
setIsFocused(true);
|
||||
|
||||
if (!disableHotkeys) {
|
||||
pushFocusItemToFocusStack({
|
||||
focusId: instanceId,
|
||||
component: {
|
||||
type: FocusComponentType.TEXT_INPUT,
|
||||
instanceId: instanceId,
|
||||
},
|
||||
globalHotkeysConfig: {
|
||||
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur: FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
onBlur?.(e);
|
||||
setIsFocused(false);
|
||||
|
||||
if (!disableHotkeys) {
|
||||
removeFocusItemFromFocusStackById({ focusId: instanceId });
|
||||
}
|
||||
};
|
||||
|
||||
const handleEscape = () => {
|
||||
if (!isFocused) {
|
||||
return;
|
||||
}
|
||||
if (isDefined(inputRef) && 'current' in inputRef) {
|
||||
inputRef.current?.blur();
|
||||
setIsFocused(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnter = () => {
|
||||
if (!isFocused) {
|
||||
return;
|
||||
}
|
||||
onInputEnter?.();
|
||||
if (isDefined(inputRef) && 'current' in inputRef) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
};
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Escape],
|
||||
callback: handleEscape,
|
||||
focusId: instanceId,
|
||||
dependencies: [handleEscape],
|
||||
options: {
|
||||
preventDefault: false,
|
||||
},
|
||||
});
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Enter],
|
||||
callback: handleEnter,
|
||||
focusId: instanceId,
|
||||
dependencies: [handleEnter],
|
||||
options: {
|
||||
preventDefault: false,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
dataTestId={dataTestId}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,132 +1,444 @@
|
||||
import { FocusEventHandler, useEffect, useRef, useState } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { InputErrorHelper } from '@/ui/input/components/InputErrorHelper';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
TextInputV2,
|
||||
TextInputV2ComponentProps,
|
||||
} from '@/ui/input/components/TextInputV2';
|
||||
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
||||
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
ChangeEvent,
|
||||
FocusEventHandler,
|
||||
InputHTMLAttributes,
|
||||
forwardRef,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { IconComponent, IconEye, IconEyeOff } from 'twenty-ui/display';
|
||||
import { AutogrowWrapper } from 'twenty-ui/utilities';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
|
||||
export type TextInputProps = TextInputV2ComponentProps & {
|
||||
instanceId: string;
|
||||
disableHotkeys?: boolean;
|
||||
onInputEnter?: () => void;
|
||||
const StyledContainer = styled.div<Pick<TextInputComponentProps, 'fullWidth'>>`
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: inherit;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
type StyledAdornmentContainerProps = {
|
||||
sizeVariant: TextInputSize;
|
||||
position: 'left' | 'right';
|
||||
};
|
||||
|
||||
const StyledAdornmentContainer = styled.div<StyledAdornmentContainerProps>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.light};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme, position }) =>
|
||||
position === 'left'
|
||||
? `${theme.border.radius.sm} 0 0 ${theme.border.radius.sm}`
|
||||
: `0 ${theme.border.radius.sm} ${theme.border.radius.sm} 0`};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
display: flex;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
justify-content: center;
|
||||
min-width: fit-content;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: auto;
|
||||
line-height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
|
||||
${({ position }) =>
|
||||
position === 'left' ? 'border-right: none;' : 'border-left: none;'}
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input<
|
||||
Pick<
|
||||
TextInputComponentProps,
|
||||
| 'LeftIcon'
|
||||
| 'RightIcon'
|
||||
| 'error'
|
||||
| 'sizeVariant'
|
||||
| 'width'
|
||||
| 'inheritFontStyles'
|
||||
| 'autoGrow'
|
||||
| 'rightAdornment'
|
||||
| 'leftAdornment'
|
||||
>
|
||||
>`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border-radius: ${({ theme, leftAdornment, rightAdornment }) =>
|
||||
leftAdornment
|
||||
? `0 ${theme.border.radius.sm} ${theme.border.radius.sm} 0`
|
||||
: rightAdornment
|
||||
? `${theme.border.radius.sm} 0 0 ${theme.border.radius.sm}`
|
||||
: theme.border.radius.sm};
|
||||
|
||||
border: 1px solid
|
||||
${({ theme, error }) =>
|
||||
error ? theme.border.color.danger : theme.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
font-family: ${({ theme, inheritFontStyles }) =>
|
||||
inheritFontStyles ? 'inherit' : theme.font.family};
|
||||
font-size: ${({ theme, inheritFontStyles }) =>
|
||||
inheritFontStyles ? 'inherit' : theme.font.size.md};
|
||||
font-weight: ${({ theme, inheritFontStyles }) =>
|
||||
inheritFontStyles ? 'inherit' : theme.font.weight.regular};
|
||||
height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
outline: none;
|
||||
padding: ${({ theme, sizeVariant, autoGrow }) =>
|
||||
autoGrow
|
||||
? 0
|
||||
: sizeVariant === 'xs'
|
||||
? `${theme.spacing(2)} 0`
|
||||
: theme.spacing(2)};
|
||||
padding-left: ${({ theme, LeftIcon, autoGrow }) =>
|
||||
autoGrow
|
||||
? theme.spacing(1)
|
||||
: LeftIcon
|
||||
? `calc(${theme.spacing(3)} + 16px)`
|
||||
: theme.spacing(2)};
|
||||
padding-right: ${({ theme, RightIcon, autoGrow }) =>
|
||||
autoGrow
|
||||
? theme.spacing(1)
|
||||
: RightIcon
|
||||
? `calc(${theme.spacing(3)} + 16px)`
|
||||
: theme.spacing(2)};
|
||||
width: ${({ theme, width }) =>
|
||||
width ? `calc(${width}px + ${theme.spacing(0.5)})` : '100%'};
|
||||
max-width: ${({ autoGrow }) => (autoGrow ? '100%' : 'none')};
|
||||
text-overflow: ellipsis;
|
||||
&::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};
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
${({ theme, error }) => {
|
||||
return `
|
||||
border-color: ${error ? theme.border.color.danger : theme.color.blue};
|
||||
`;
|
||||
}};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLeftIconContainer = styled.div<{ sizeVariant: TextInputSize }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-left: ${({ theme, sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? theme.spacing(0.5)
|
||||
: sizeVariant === 'md' || sizeVariant === 'sm'
|
||||
? theme.spacing(1)
|
||||
: theme.spacing(2)};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
`;
|
||||
|
||||
const StyledTrailingIconContainer = styled.div<
|
||||
Pick<TextInputComponentProps, 'error'>
|
||||
>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: auto 0;
|
||||
`;
|
||||
|
||||
const StyledTrailingIcon = styled.div<{
|
||||
isFocused?: boolean;
|
||||
onClick?: () => void;
|
||||
}>`
|
||||
align-items: center;
|
||||
color: ${({ theme, isFocused }) =>
|
||||
isFocused ? theme.font.color.secondary : theme.font.color.light};
|
||||
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const INPUT_TYPE_PASSWORD = 'password';
|
||||
|
||||
export type TextInputSize = 'xs' | 'sm' | 'md' | 'lg';
|
||||
|
||||
export type TextInputComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'onKeyDown'
|
||||
> & {
|
||||
className?: string;
|
||||
label?: string;
|
||||
onChange?: (text: string) => void;
|
||||
fullWidth?: boolean;
|
||||
error?: string;
|
||||
noErrorHelper?: boolean;
|
||||
RightIcon?: IconComponent;
|
||||
onRightIconClick?: () => void;
|
||||
LeftIcon?: IconComponent;
|
||||
autoGrow?: boolean;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
dataTestId?: string;
|
||||
autoFocusOnMount?: boolean;
|
||||
autoSelectOnMount?: boolean;
|
||||
sizeVariant?: TextInputSize;
|
||||
inheritFontStyles?: boolean;
|
||||
rightAdornment?: string;
|
||||
leftAdornment?: string;
|
||||
textClickOutsideId?: string;
|
||||
};
|
||||
|
||||
export const TextInput = ({
|
||||
instanceId,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onInputEnter,
|
||||
disableHotkeys = false,
|
||||
autoFocusOnMount,
|
||||
autoSelectOnMount,
|
||||
dataTestId,
|
||||
...props
|
||||
}: TextInputProps) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
type TextInputWithAutoGrowWrapperProps = TextInputComponentProps;
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const TextInputComponent = forwardRef<
|
||||
HTMLInputElement,
|
||||
TextInputComponentProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
fullWidth,
|
||||
width,
|
||||
error,
|
||||
noErrorHelper = false,
|
||||
required,
|
||||
type,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
disabled,
|
||||
readOnly,
|
||||
tabIndex,
|
||||
RightIcon,
|
||||
onRightIconClick,
|
||||
LeftIcon,
|
||||
autoComplete,
|
||||
maxLength,
|
||||
sizeVariant = 'lg',
|
||||
inheritFontStyles = false,
|
||||
dataTestId,
|
||||
autoGrow = false,
|
||||
rightAdornment,
|
||||
leftAdornment,
|
||||
textClickOutsideId,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const combinedRef = useCombinedRefs(ref, inputRef);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocusOnMount === true) {
|
||||
inputRef.current?.focus();
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const handleTogglePasswordVisibility = () => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
};
|
||||
|
||||
const handleFocus: FocusEventHandler<HTMLInputElement> = (event) => {
|
||||
setIsFocused(true);
|
||||
}
|
||||
}, [autoFocusOnMount]);
|
||||
onFocus?.(event);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (autoSelectOnMount === true) {
|
||||
inputRef.current?.select();
|
||||
}
|
||||
}, [autoSelectOnMount]);
|
||||
|
||||
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
||||
const { removeFocusItemFromFocusStackById } =
|
||||
useRemoveFocusItemFromFocusStackById();
|
||||
|
||||
const handleFocus: FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
onFocus?.(e);
|
||||
setIsFocused(true);
|
||||
|
||||
if (!disableHotkeys) {
|
||||
pushFocusItemToFocusStack({
|
||||
focusId: instanceId,
|
||||
component: {
|
||||
type: FocusComponentType.TEXT_INPUT,
|
||||
instanceId: instanceId,
|
||||
},
|
||||
globalHotkeysConfig: {
|
||||
enableGlobalHotkeysConflictingWithKeyboard: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur: FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
onBlur?.(e);
|
||||
setIsFocused(false);
|
||||
|
||||
if (!disableHotkeys) {
|
||||
removeFocusItemFromFocusStackById({ focusId: instanceId });
|
||||
}
|
||||
};
|
||||
|
||||
const handleEscape = () => {
|
||||
if (!isFocused) {
|
||||
return;
|
||||
}
|
||||
if (isDefined(inputRef) && 'current' in inputRef) {
|
||||
inputRef.current?.blur();
|
||||
const handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {
|
||||
setIsFocused(false);
|
||||
}
|
||||
};
|
||||
onBlur?.(event);
|
||||
};
|
||||
|
||||
const handleEnter = () => {
|
||||
if (!isFocused) {
|
||||
return;
|
||||
}
|
||||
onInputEnter?.();
|
||||
if (isDefined(inputRef) && 'current' in inputRef) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
};
|
||||
const instanceId = useId();
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Escape],
|
||||
callback: handleEscape,
|
||||
focusId: instanceId,
|
||||
dependencies: [handleEscape],
|
||||
options: {
|
||||
preventDefault: false,
|
||||
},
|
||||
});
|
||||
return (
|
||||
<StyledContainer
|
||||
className={className}
|
||||
fullWidth={fullWidth ?? false}
|
||||
data-click-outside-id={textClickOutsideId}
|
||||
>
|
||||
{label && (
|
||||
<InputLabel htmlFor={instanceId}>
|
||||
{label + (required ? '*' : '')}
|
||||
</InputLabel>
|
||||
)}
|
||||
<StyledInputContainer>
|
||||
{leftAdornment && (
|
||||
<StyledAdornmentContainer sizeVariant={sizeVariant} position="left">
|
||||
{leftAdornment}
|
||||
</StyledAdornmentContainer>
|
||||
)}
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Enter],
|
||||
callback: handleEnter,
|
||||
focusId: instanceId,
|
||||
dependencies: [handleEnter],
|
||||
options: {
|
||||
preventDefault: false,
|
||||
},
|
||||
});
|
||||
{!!LeftIcon && (
|
||||
<StyledLeftIconContainer sizeVariant={sizeVariant}>
|
||||
<StyledTrailingIcon isFocused={isFocused}>
|
||||
<LeftIcon size={theme.icon.size.md} />
|
||||
</StyledTrailingIcon>
|
||||
</StyledLeftIconContainer>
|
||||
)}
|
||||
|
||||
<StyledInput
|
||||
id={instanceId}
|
||||
width={width}
|
||||
data-testid={dataTestId}
|
||||
autoComplete={autoComplete || 'off'}
|
||||
ref={combinedRef}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
type={passwordVisible ? 'text' : type}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange?.(
|
||||
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
|
||||
);
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
{...{
|
||||
autoFocus,
|
||||
disabled,
|
||||
readOnly,
|
||||
placeholder,
|
||||
required,
|
||||
value,
|
||||
LeftIcon,
|
||||
RightIcon,
|
||||
maxLength,
|
||||
error,
|
||||
sizeVariant,
|
||||
inheritFontStyles,
|
||||
autoGrow,
|
||||
leftAdornment,
|
||||
rightAdornment,
|
||||
}}
|
||||
/>
|
||||
{rightAdornment && (
|
||||
<StyledAdornmentContainer
|
||||
sizeVariant={sizeVariant}
|
||||
position="right"
|
||||
>
|
||||
{rightAdornment}
|
||||
</StyledAdornmentContainer>
|
||||
)}
|
||||
<StyledTrailingIconContainer {...{ error }}>
|
||||
{!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
|
||||
onClick={onRightIconClick ? onRightIconClick : undefined}
|
||||
>
|
||||
<RightIcon size={theme.icon.size.md} />
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
</StyledTrailingIconContainer>
|
||||
</StyledInputContainer>
|
||||
{!noErrorHelper && error && (
|
||||
<InputErrorHelper>{error}</InputErrorHelper>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const StyledAutogrowWrapper = styled(AutogrowWrapper)<{
|
||||
sizeVariant?: TextInputSize;
|
||||
}>`
|
||||
box-sizing: border-box;
|
||||
height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
padding: 0 ${({ theme }) => theme.spacing(1.25)};
|
||||
`;
|
||||
|
||||
const TextInputWithAutoGrowWrapper = forwardRef<
|
||||
HTMLInputElement,
|
||||
TextInputWithAutoGrowWrapperProps
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<TextInputV2
|
||||
ref={inputRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
dataTestId={dataTestId}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
<>
|
||||
{props.autoGrow ? (
|
||||
<StyledAutogrowWrapper
|
||||
sizeVariant={props.sizeVariant}
|
||||
node={props.value || props.placeholder}
|
||||
>
|
||||
<TextInputComponent
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
fullWidth={true}
|
||||
/>
|
||||
</StyledAutogrowWrapper>
|
||||
) : (
|
||||
<TextInputComponent
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export const TextInput = TextInputWithAutoGrowWrapper;
|
||||
|
||||
@@ -1,446 +0,0 @@
|
||||
import { InputErrorHelper } from '@/ui/input/components/InputErrorHelper';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
ChangeEvent,
|
||||
FocusEventHandler,
|
||||
InputHTMLAttributes,
|
||||
forwardRef,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { IconComponent, IconEye, IconEyeOff } from 'twenty-ui/display';
|
||||
import { AutogrowWrapper } from 'twenty-ui/utilities';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
|
||||
const StyledContainer = styled.div<
|
||||
Pick<TextInputV2ComponentProps, 'fullWidth'>
|
||||
>`
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
align-items: center;
|
||||
background-color: inherit;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
type StyledAdornmentContainerProps = {
|
||||
sizeVariant: TextInputV2Size;
|
||||
position: 'left' | 'right';
|
||||
};
|
||||
|
||||
const StyledAdornmentContainer = styled.div<StyledAdornmentContainerProps>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.light};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme, position }) =>
|
||||
position === 'left'
|
||||
? `${theme.border.radius.sm} 0 0 ${theme.border.radius.sm}`
|
||||
: `0 ${theme.border.radius.sm} ${theme.border.radius.sm} 0`};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
display: flex;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
justify-content: center;
|
||||
min-width: fit-content;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: auto;
|
||||
line-height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
|
||||
${({ position }) =>
|
||||
position === 'left' ? 'border-right: none;' : 'border-left: none;'}
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input<
|
||||
Pick<
|
||||
TextInputV2ComponentProps,
|
||||
| 'LeftIcon'
|
||||
| 'RightIcon'
|
||||
| 'error'
|
||||
| 'sizeVariant'
|
||||
| 'width'
|
||||
| 'inheritFontStyles'
|
||||
| 'autoGrow'
|
||||
| 'rightAdornment'
|
||||
| 'leftAdornment'
|
||||
>
|
||||
>`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border-radius: ${({ theme, leftAdornment, rightAdornment }) =>
|
||||
leftAdornment
|
||||
? `0 ${theme.border.radius.sm} ${theme.border.radius.sm} 0`
|
||||
: rightAdornment
|
||||
? `${theme.border.radius.sm} 0 0 ${theme.border.radius.sm}`
|
||||
: theme.border.radius.sm};
|
||||
|
||||
border: 1px solid
|
||||
${({ theme, error }) =>
|
||||
error ? theme.border.color.danger : theme.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
font-family: ${({ theme, inheritFontStyles }) =>
|
||||
inheritFontStyles ? 'inherit' : theme.font.family};
|
||||
font-size: ${({ theme, inheritFontStyles }) =>
|
||||
inheritFontStyles ? 'inherit' : theme.font.size.md};
|
||||
font-weight: ${({ theme, inheritFontStyles }) =>
|
||||
inheritFontStyles ? 'inherit' : theme.font.weight.regular};
|
||||
height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
outline: none;
|
||||
padding: ${({ theme, sizeVariant, autoGrow }) =>
|
||||
autoGrow
|
||||
? 0
|
||||
: sizeVariant === 'xs'
|
||||
? `${theme.spacing(2)} 0`
|
||||
: theme.spacing(2)};
|
||||
padding-left: ${({ theme, LeftIcon, autoGrow }) =>
|
||||
autoGrow
|
||||
? theme.spacing(1)
|
||||
: LeftIcon
|
||||
? `calc(${theme.spacing(3)} + 16px)`
|
||||
: theme.spacing(2)};
|
||||
padding-right: ${({ theme, RightIcon, autoGrow }) =>
|
||||
autoGrow
|
||||
? theme.spacing(1)
|
||||
: RightIcon
|
||||
? `calc(${theme.spacing(3)} + 16px)`
|
||||
: theme.spacing(2)};
|
||||
width: ${({ theme, width }) =>
|
||||
width ? `calc(${width}px + ${theme.spacing(0.5)})` : '100%'};
|
||||
max-width: ${({ autoGrow }) => (autoGrow ? '100%' : 'none')};
|
||||
text-overflow: ellipsis;
|
||||
&::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};
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
${({ theme, error }) => {
|
||||
return `
|
||||
border-color: ${error ? theme.border.color.danger : theme.color.blue};
|
||||
`;
|
||||
}};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLeftIconContainer = styled.div<{ sizeVariant: TextInputV2Size }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-left: ${({ theme, sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? theme.spacing(0.5)
|
||||
: sizeVariant === 'md' || sizeVariant === 'sm'
|
||||
? theme.spacing(1)
|
||||
: theme.spacing(2)};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
`;
|
||||
|
||||
const StyledTrailingIconContainer = styled.div<
|
||||
Pick<TextInputV2ComponentProps, 'error'>
|
||||
>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: auto 0;
|
||||
`;
|
||||
|
||||
const StyledTrailingIcon = styled.div<{
|
||||
isFocused?: boolean;
|
||||
onClick?: () => void;
|
||||
}>`
|
||||
align-items: center;
|
||||
color: ${({ theme, isFocused }) =>
|
||||
isFocused ? theme.font.color.secondary : theme.font.color.light};
|
||||
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const INPUT_TYPE_PASSWORD = 'password';
|
||||
|
||||
export type TextInputV2Size = 'xs' | 'sm' | 'md' | 'lg';
|
||||
|
||||
export type TextInputV2ComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'onKeyDown'
|
||||
> & {
|
||||
className?: string;
|
||||
label?: string;
|
||||
onChange?: (text: string) => void;
|
||||
fullWidth?: boolean;
|
||||
error?: string;
|
||||
noErrorHelper?: boolean;
|
||||
RightIcon?: IconComponent;
|
||||
onRightIconClick?: () => void;
|
||||
LeftIcon?: IconComponent;
|
||||
autoGrow?: boolean;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
dataTestId?: string;
|
||||
sizeVariant?: TextInputV2Size;
|
||||
inheritFontStyles?: boolean;
|
||||
rightAdornment?: string;
|
||||
leftAdornment?: string;
|
||||
textClickOutsideId?: string;
|
||||
};
|
||||
|
||||
type TextInputV2WithAutoGrowWrapperProps = TextInputV2ComponentProps;
|
||||
|
||||
const TextInputV2Component = forwardRef<
|
||||
HTMLInputElement,
|
||||
TextInputV2ComponentProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
fullWidth,
|
||||
width,
|
||||
error,
|
||||
noErrorHelper = false,
|
||||
required,
|
||||
type,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
disabled,
|
||||
readOnly,
|
||||
tabIndex,
|
||||
RightIcon,
|
||||
onRightIconClick,
|
||||
LeftIcon,
|
||||
autoComplete,
|
||||
maxLength,
|
||||
sizeVariant = 'lg',
|
||||
inheritFontStyles = false,
|
||||
dataTestId,
|
||||
autoGrow = false,
|
||||
rightAdornment,
|
||||
leftAdornment,
|
||||
textClickOutsideId,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const combinedRef = useCombinedRefs(ref, inputRef);
|
||||
|
||||
const [passwordVisible, setPasswordVisible] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const handleTogglePasswordVisibility = () => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
};
|
||||
|
||||
const handleFocus: FocusEventHandler<HTMLInputElement> = (event) => {
|
||||
setIsFocused(true);
|
||||
onFocus?.(event);
|
||||
};
|
||||
|
||||
const handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {
|
||||
setIsFocused(false);
|
||||
onBlur?.(event);
|
||||
};
|
||||
|
||||
const instanceId = useId();
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
className={className}
|
||||
fullWidth={fullWidth ?? false}
|
||||
data-click-outside-id={textClickOutsideId}
|
||||
>
|
||||
{label && (
|
||||
<InputLabel htmlFor={instanceId}>
|
||||
{label + (required ? '*' : '')}
|
||||
</InputLabel>
|
||||
)}
|
||||
<StyledInputContainer>
|
||||
{leftAdornment && (
|
||||
<StyledAdornmentContainer sizeVariant={sizeVariant} position="left">
|
||||
{leftAdornment}
|
||||
</StyledAdornmentContainer>
|
||||
)}
|
||||
|
||||
{!!LeftIcon && (
|
||||
<StyledLeftIconContainer sizeVariant={sizeVariant}>
|
||||
<StyledTrailingIcon isFocused={isFocused}>
|
||||
<LeftIcon size={theme.icon.size.md} />
|
||||
</StyledTrailingIcon>
|
||||
</StyledLeftIconContainer>
|
||||
)}
|
||||
|
||||
<StyledInput
|
||||
id={instanceId}
|
||||
width={width}
|
||||
data-testid={dataTestId}
|
||||
autoComplete={autoComplete || 'off'}
|
||||
ref={combinedRef}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
type={passwordVisible ? 'text' : type}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange?.(
|
||||
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
|
||||
);
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
{...{
|
||||
autoFocus,
|
||||
disabled,
|
||||
readOnly,
|
||||
placeholder,
|
||||
required,
|
||||
value,
|
||||
LeftIcon,
|
||||
RightIcon,
|
||||
maxLength,
|
||||
error,
|
||||
sizeVariant,
|
||||
inheritFontStyles,
|
||||
autoGrow,
|
||||
leftAdornment,
|
||||
rightAdornment,
|
||||
}}
|
||||
/>
|
||||
{rightAdornment && (
|
||||
<StyledAdornmentContainer
|
||||
sizeVariant={sizeVariant}
|
||||
position="right"
|
||||
>
|
||||
{rightAdornment}
|
||||
</StyledAdornmentContainer>
|
||||
)}
|
||||
<StyledTrailingIconContainer {...{ error }}>
|
||||
{!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
|
||||
onClick={onRightIconClick ? onRightIconClick : undefined}
|
||||
>
|
||||
<RightIcon size={theme.icon.size.md} />
|
||||
</StyledTrailingIcon>
|
||||
)}
|
||||
</StyledTrailingIconContainer>
|
||||
</StyledInputContainer>
|
||||
{!noErrorHelper && error && (
|
||||
<InputErrorHelper>{error}</InputErrorHelper>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const StyledAutogrowWrapper = styled(AutogrowWrapper)<{
|
||||
sizeVariant?: TextInputV2Size;
|
||||
}>`
|
||||
box-sizing: border-box;
|
||||
height: ${({ sizeVariant }) =>
|
||||
sizeVariant === 'xs'
|
||||
? '20px'
|
||||
: sizeVariant === 'sm'
|
||||
? '24px'
|
||||
: sizeVariant === 'md'
|
||||
? '28px'
|
||||
: '32px'};
|
||||
padding: 0 ${({ theme }) => theme.spacing(1.25)};
|
||||
`;
|
||||
|
||||
const TextInputV2WithAutoGrowWrapper = forwardRef<
|
||||
HTMLInputElement,
|
||||
TextInputV2WithAutoGrowWrapperProps
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<>
|
||||
{props.autoGrow ? (
|
||||
<StyledAutogrowWrapper
|
||||
sizeVariant={props.sizeVariant}
|
||||
node={props.value || props.placeholder}
|
||||
>
|
||||
<TextInputV2Component
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
fullWidth={true}
|
||||
/>
|
||||
</StyledAutogrowWrapper>
|
||||
) : (
|
||||
<TextInputV2Component
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const TextInputV2 = TextInputV2WithAutoGrowWrapper;
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
TextInputV2,
|
||||
TextInputV2Size,
|
||||
} from '@/ui/input/components/TextInputV2';
|
||||
import { TextInput, TextInputSize } from '@/ui/input/components/TextInput';
|
||||
import { useRef, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -22,7 +19,7 @@ type InputProps = {
|
||||
onClickOutside?: () => void;
|
||||
onTab?: () => void;
|
||||
onShiftTab?: () => void;
|
||||
sizeVariant?: TextInputV2Size;
|
||||
sizeVariant?: TextInputSize;
|
||||
};
|
||||
|
||||
export type TitleInputProps = {
|
||||
@@ -30,7 +27,7 @@ export type TitleInputProps = {
|
||||
} & InputProps;
|
||||
|
||||
const StyledDiv = styled.div<{
|
||||
sizeVariant: TextInputV2Size;
|
||||
sizeVariant: TextInputSize;
|
||||
disabled?: boolean;
|
||||
}>`
|
||||
background: inherit;
|
||||
@@ -116,7 +113,7 @@ const Input = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<TextInputV2
|
||||
<TextInput
|
||||
ref={wrapperRef}
|
||||
autoGrow
|
||||
sizeVariant={sizeVariant}
|
||||
|
||||
+33
-2
@@ -1,10 +1,13 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { TextInput, TextInputProps } from '../TextInput';
|
||||
import {
|
||||
TextInput,
|
||||
TextInputComponentProps,
|
||||
} from '@/ui/input/components/TextInput';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
|
||||
type RenderProps = TextInputProps;
|
||||
type RenderProps = TextInputComponentProps;
|
||||
|
||||
const Render = (args: RenderProps) => {
|
||||
const [value, setValue] = useState(args.value);
|
||||
@@ -37,3 +40,31 @@ export const Filled: Story = {
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true, value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrow: Story = {
|
||||
args: { autoGrow: true, value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrowWithPlaceholder: Story = {
|
||||
args: { autoGrow: true, placeholder: 'Tim' },
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: { sizeVariant: 'sm', value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrowSmall: Story = {
|
||||
args: { autoGrow: true, sizeVariant: 'sm', value: 'Tim' },
|
||||
};
|
||||
|
||||
export const WithLeftAdornment: Story = {
|
||||
args: {
|
||||
leftAdornment: 'https://',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithRightAdornment: Story = {
|
||||
args: {
|
||||
rightAdornment: '@twenty.com',
|
||||
},
|
||||
};
|
||||
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
TextInputV2,
|
||||
TextInputV2ComponentProps,
|
||||
} from '@/ui/input/components/TextInputV2';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
|
||||
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' },
|
||||
};
|
||||
|
||||
export const AutoGrow: Story = {
|
||||
args: { autoGrow: true, value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrowWithPlaceholder: Story = {
|
||||
args: { autoGrow: true, placeholder: 'Tim' },
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: { sizeVariant: 'sm', value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrowSmall: Story = {
|
||||
args: { autoGrow: true, sizeVariant: 'sm', value: 'Tim' },
|
||||
};
|
||||
|
||||
export const WithLeftAdornment: Story = {
|
||||
args: {
|
||||
leftAdornment: 'https://',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithRightAdornment: Story = {
|
||||
args: {
|
||||
rightAdornment: '@twenty.com',
|
||||
},
|
||||
};
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { RELATIVE_DATE_DIRECTION_SELECT_OPTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateDirectionSelectOptions';
|
||||
import { RELATIVE_DATE_UNITS_SELECT_OPTIONS } from '@/ui/input/components/internal/date/constants/RelativeDateUnitSelectOptions';
|
||||
import {
|
||||
@@ -71,7 +71,7 @@ export const RelativeDatePickerHeader = (
|
||||
options={RELATIVE_DATE_DIRECTION_SELECT_OPTIONS}
|
||||
fullWidth
|
||||
/>
|
||||
<TextInput
|
||||
<SettingsTextInput
|
||||
instanceId="relative-date-picker-amount"
|
||||
width={50}
|
||||
value={textInputValue}
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ import { PhoneCountryPickerDropdownSelect } from './PhoneCountryPickerDropdownSe
|
||||
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import 'react-phone-number-input/style.css';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronDown, IconWorld } from 'twenty-ui/display';
|
||||
@@ -79,7 +79,7 @@ export const PhoneCountryPickerDropdownButton = ({
|
||||
|
||||
const dropdownId = 'country-picker-dropdown-id';
|
||||
|
||||
const isDropdownOpen = useRecoilComponentValueV2(
|
||||
const isDropdownOpen = useRecoilComponentValue(
|
||||
isDropdownOpenComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
|
||||
import { isSlashMenuOpenComponentState } from '@/ui/input/editor/states/isSlashMenuOpenComponentState';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export type BlockEditorDropdownFocusEffectProps = {
|
||||
@@ -12,7 +12,7 @@ export type BlockEditorDropdownFocusEffectProps = {
|
||||
export const BlockEditorDropdownFocusEffect = ({
|
||||
editor,
|
||||
}: BlockEditorDropdownFocusEffectProps) => {
|
||||
const isSlashMenuOpenState = useRecoilComponentCallbackStateV2(
|
||||
const isSlashMenuOpenState = useRecoilComponentCallbackState(
|
||||
isSlashMenuOpenComponentState,
|
||||
);
|
||||
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { SuggestionItem } from '@/ui/input/editor/components/types';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { MenuItemSuggestion } from 'twenty-ui/navigation';
|
||||
|
||||
export type CustomSlashMenuListItemProps = {
|
||||
@@ -15,7 +15,7 @@ export const CustomSlashMenuListItem = ({
|
||||
}: CustomSlashMenuListItemProps) => {
|
||||
const { resetSelectedItem } = useSelectableList(SLASH_MENU_LIST_ID);
|
||||
|
||||
const isSelectedItem = useRecoilComponentFamilyValueV2(
|
||||
const isSelectedItem = useRecoilComponentFamilyValue(
|
||||
isSelectedItemIdComponentFamilySelector,
|
||||
item.title,
|
||||
);
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { BlockEditorComponentInstanceContext } from '@/ui/input/editor/contexts/BlockEditorCompoponeInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const isSlashMenuOpenComponentState = createComponentStateV2<boolean>({
|
||||
export const isSlashMenuOpenComponentState = createComponentState<boolean>({
|
||||
key: 'isSlashMenuOpenComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: BlockEditorComponentInstanceContext,
|
||||
|
||||
Reference in New Issue
Block a user