diff --git a/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx b/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx index c69b615f6..827aba0dc 100644 --- a/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx +++ b/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx @@ -6,7 +6,7 @@ import { motion } from 'framer-motion'; import { MainButton } from '@/ui/button/components/MainButton'; import { IconBrandGoogle } from '@/ui/icon'; -import { TextInputSettings } from '@/ui/input/text/components/TextInputSettings'; +import { TextInput } from '@/ui/input/components/TextInput'; import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn'; import { Logo } from '../../components/Logo'; @@ -132,7 +132,7 @@ export const SignInUpForm = () => { fieldState: { error }, }) => ( - { fieldState: { error }, }) => ( - { const currentUser = useRecoilValue(currentUserState); return ( - - - - ( } default: component = ( - { onRowChange({ ...row, [columnKey]: value }); diff --git a/front/src/modules/ui/field/meta-types/input/components/BooleanFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/BooleanFieldInput.tsx index bc2e87233..d6f65de38 100644 --- a/front/src/modules/ui/field/meta-types/input/components/BooleanFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/BooleanFieldInput.tsx @@ -1,4 +1,4 @@ -import { BooleanInput } from '@/ui/input/components/BooleanInput'; +import { BooleanInput } from '@/ui/field/meta-types/input/components/internal/BooleanInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useBooleanField } from '../../hooks/useBooleanField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx index d0960f6ad..c5383c6a3 100644 --- a/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/ChipFieldInput.tsx @@ -1,4 +1,4 @@ -import { TextInput } from '@/ui/input/components/TextInput'; +import { TextInput } from '@/ui/field/meta-types/input/components/internal/TextInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useChipField } from '../../hooks/useChipField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/DateFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DateFieldInput.tsx index fecf97c7e..f10d557f7 100644 --- a/front/src/modules/ui/field/meta-types/input/components/DateFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/DateFieldInput.tsx @@ -1,4 +1,4 @@ -import { DateInput } from '@/ui/input/components/DateInput'; +import { DateInput } from '@/ui/field/meta-types/input/components/internal/DateInput'; import { Nullable } from '~/types/Nullable'; import { usePersistField } from '../../../hooks/usePersistField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx index c739f0f9d..5b0e2689a 100644 --- a/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/DoubleTextChipFieldInput.tsx @@ -1,5 +1,5 @@ +import { DoubleTextInput } from '@/ui/field/meta-types/input/components/internal/DoubleTextInput'; import { FieldDoubleText } from '@/ui/field/types/FieldDoubleText'; -import { DoubleTextInput } from '@/ui/input/components/DoubleTextInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useDoubleTextChipField } from '../../hooks/useDoubleTextChipField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx index a3931e32b..0adce6ee0 100644 --- a/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/DoubleTextFieldInput.tsx @@ -1,5 +1,5 @@ +import { DoubleTextInput } from '@/ui/field/meta-types/input/components/internal/DoubleTextInput'; import { FieldDoubleText } from '@/ui/field/types/FieldDoubleText'; -import { DoubleTextInput } from '@/ui/input/components/DoubleTextInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useDoubleTextField } from '../../hooks/useDoubleTextField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx index a1bca9c16..e52ede464 100644 --- a/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/EmailFieldInput.tsx @@ -1,4 +1,4 @@ -import { TextInput } from '@/ui/input/components/TextInput'; +import { TextInput } from '@/ui/field/meta-types/input/components/internal/TextInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useEmailField } from '../../hooks/useEmailField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx index aa77fda91..40cc6c337 100644 --- a/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/MoneyFieldInput.tsx @@ -1,4 +1,4 @@ -import { TextInput } from '@/ui/input/components/TextInput'; +import { TextInput } from '@/ui/field/meta-types/input/components/internal/TextInput'; import { useMoneyField } from '../../hooks/useMoneyField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx index 840b58c4a..a40af62a2 100644 --- a/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/NumberFieldInput.tsx @@ -1,4 +1,4 @@ -import { TextInput } from '@/ui/input/components/TextInput'; +import { TextInput } from '@/ui/field/meta-types/input/components/internal/TextInput'; import { useNumberField } from '../../hooks/useNumberField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx index ecc157c46..dfd1e42cc 100644 --- a/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/PhoneFieldInput.tsx @@ -1,4 +1,4 @@ -import { PhoneInput } from '@/ui/input/components/PhoneInput'; +import { PhoneInput } from '@/ui/field/meta-types/input/components/internal/PhoneInput'; import { usePhoneField } from '../../hooks/usePhoneField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx index b8601b276..7884152bb 100644 --- a/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/ProbabilityFieldInput.tsx @@ -1,4 +1,4 @@ -import { ProbabilityInput } from '@/ui/input/components/ProbabilityInput'; +import { ProbabilityInput } from '@/ui/field/meta-types/input/components/internal/ProbabilityInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useProbabilityField } from '../../hooks/useProbabilityField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx index 3edd43660..7473314cc 100644 --- a/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/TextFieldInput.tsx @@ -1,4 +1,4 @@ -import { TextInput } from '@/ui/input/components/TextInput'; +import { TextInput } from '@/ui/field/meta-types/input/components/internal/TextInput'; import { usePersistField } from '../../../hooks/usePersistField'; import { useTextField } from '../../hooks/useTextField'; diff --git a/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx b/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx index 172b1eedd..633cd3622 100644 --- a/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/URLFieldInput.tsx @@ -1,4 +1,4 @@ -import { TextInput } from '@/ui/input/components/TextInput'; +import { TextInput } from '@/ui/field/meta-types/input/components/internal/TextInput'; import { useURLField } from '../../hooks/useURLField'; diff --git a/front/src/modules/ui/input/components/BooleanInput.tsx b/front/src/modules/ui/field/meta-types/input/components/internal/BooleanInput.tsx similarity index 100% rename from front/src/modules/ui/input/components/BooleanInput.tsx rename to front/src/modules/ui/field/meta-types/input/components/internal/BooleanInput.tsx diff --git a/front/src/modules/ui/input/components/DateInput.tsx b/front/src/modules/ui/field/meta-types/input/components/internal/DateInput.tsx similarity index 91% rename from front/src/modules/ui/input/components/DateInput.tsx rename to front/src/modules/ui/field/meta-types/input/components/internal/DateInput.tsx index 4952b1010..bd2b731e0 100644 --- a/front/src/modules/ui/input/components/DateInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/internal/DateInput.tsx @@ -4,11 +4,10 @@ import styled from '@emotion/styled'; import { flip, offset, useFloating } from '@floating-ui/react'; import { DateDisplay } from '@/ui/field/meta-types/display/content-display/components/DateDisplay'; +import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; import { Nullable } from '~/types/Nullable'; -import { useRegisterInputEvents } from '../hooks/useRegisterInputEvents'; - -import { DatePicker } from './DatePicker'; +import { useRegisterInputEvents } from '../../hooks/useRegisterInputEvents'; const StyledCalendarContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; @@ -89,7 +88,7 @@ export const DateInput = ({
- { diff --git a/front/src/modules/ui/input/components/DoubleTextInput.tsx b/front/src/modules/ui/field/meta-types/input/components/internal/DoubleTextInput.tsx similarity index 100% rename from front/src/modules/ui/input/components/DoubleTextInput.tsx rename to front/src/modules/ui/field/meta-types/input/components/internal/DoubleTextInput.tsx diff --git a/front/src/modules/ui/input/components/PhoneInput.tsx b/front/src/modules/ui/field/meta-types/input/components/internal/PhoneInput.tsx similarity index 91% rename from front/src/modules/ui/input/components/PhoneInput.tsx rename to front/src/modules/ui/field/meta-types/input/components/internal/PhoneInput.tsx index bd7930508..c18b4b7f6 100644 --- a/front/src/modules/ui/input/components/PhoneInput.tsx +++ b/front/src/modules/ui/field/meta-types/input/components/internal/PhoneInput.tsx @@ -2,9 +2,9 @@ import { useEffect, useRef, useState } from 'react'; import ReactPhoneNumberInput from 'react-phone-number-input'; import styled from '@emotion/styled'; -import { useRegisterInputEvents } from '../hooks/useRegisterInputEvents'; +import { CountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/CountryPickerDropdownButton'; -import { CountryPickerDropdownButton } from './CountryPickerDropdownButton'; +import { useRegisterInputEvents } from '../../hooks/useRegisterInputEvents'; import 'react-phone-number-input/style.css'; diff --git a/front/src/modules/ui/input/components/ProbabilityInput.tsx b/front/src/modules/ui/field/meta-types/input/components/internal/ProbabilityInput.tsx similarity index 100% rename from front/src/modules/ui/input/components/ProbabilityInput.tsx rename to front/src/modules/ui/field/meta-types/input/components/internal/ProbabilityInput.tsx diff --git a/front/src/modules/ui/field/meta-types/input/components/internal/TextInput.tsx b/front/src/modules/ui/field/meta-types/input/components/internal/TextInput.tsx new file mode 100644 index 000000000..fdfedd63a --- /dev/null +++ b/front/src/modules/ui/field/meta-types/input/components/internal/TextInput.tsx @@ -0,0 +1,70 @@ +import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import styled from '@emotion/styled'; + +import { textInputStyle } from '@/ui/theme/constants/effects'; + +import { useRegisterInputEvents } from '../../hooks/useRegisterInputEvents'; + +export const StyledInput = styled.input` + margin: 0; + width: 100%; + ${textInputStyle} +`; + +type TextInputProps = { + placeholder?: string; + autoFocus?: boolean; + value: string; + onEnter: (newText: string) => void; + onEscape: (newText: string) => void; + onTab?: (newText: string) => void; + onShiftTab?: (newText: string) => void; + onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void; + hotkeyScope: string; +}; + +export const TextInput = ({ + placeholder, + autoFocus, + value, + hotkeyScope, + onEnter, + onEscape, + onTab, + onShiftTab, + onClickOutside, +}: TextInputProps) => { + const [internalText, setInternalText] = useState(value); + + const wrapperRef = useRef(null); + + const handleChange = (event: ChangeEvent) => { + setInternalText(event.target.value); + }; + + useEffect(() => { + setInternalText(value); + }, [value]); + + useRegisterInputEvents({ + inputRef: wrapperRef, + inputValue: internalText, + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, + hotkeyScope, + }); + + return ( + + ); +}; diff --git a/front/src/modules/ui/input/hooks/useRegisterInputEvents.ts b/front/src/modules/ui/field/meta-types/input/hooks/useRegisterInputEvents.ts similarity index 100% rename from front/src/modules/ui/input/hooks/useRegisterInputEvents.ts rename to front/src/modules/ui/field/meta-types/input/hooks/useRegisterInputEvents.ts diff --git a/front/src/modules/ui/input/components/AutosizeTextInput.tsx b/front/src/modules/ui/input/components/AutosizeTextInput.tsx index 64638902e..35092cfb2 100644 --- a/front/src/modules/ui/input/components/AutosizeTextInput.tsx +++ b/front/src/modules/ui/input/components/AutosizeTextInput.tsx @@ -8,7 +8,7 @@ import { RoundedIconButton } from '@/ui/button/components/RoundedIconButton'; import { IconArrowRight } from '@/ui/icon/index'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { InputHotkeyScope } from '../text/types/InputHotkeyScope'; +import { InputHotkeyScope } from '../types/InputHotkeyScope'; const MAX_ROWS = 5; diff --git a/front/src/modules/ui/input/components/EntityTitleDoubleTextInput.tsx b/front/src/modules/ui/input/components/EntityTitleDoubleTextInput.tsx index dcaca0b71..23ca05cad 100644 --- a/front/src/modules/ui/input/components/EntityTitleDoubleTextInput.tsx +++ b/front/src/modules/ui/input/components/EntityTitleDoubleTextInput.tsx @@ -1,7 +1,7 @@ import { ChangeEvent } from 'react'; import styled from '@emotion/styled'; -import { StyledInput } from '@/ui/input/components/TextInput'; +import { StyledInput } from '@/ui/field/meta-types/input/components/internal/TextInput'; import { ComputeNodeDimensions } from '@/ui/utilities/dimensions/components/ComputeNodeDimensions'; export type EntityTitleDoubleTextInputProps = { diff --git a/front/src/modules/ui/input/image/components/ImageInput.tsx b/front/src/modules/ui/input/components/ImageInput.tsx similarity index 100% rename from front/src/modules/ui/input/image/components/ImageInput.tsx rename to front/src/modules/ui/input/components/ImageInput.tsx diff --git a/front/src/modules/ui/input/components/TextInput.tsx b/front/src/modules/ui/input/components/TextInput.tsx index 43e3ce48b..e678bcd22 100644 --- a/front/src/modules/ui/input/components/TextInput.tsx +++ b/front/src/modules/ui/input/components/TextInput.tsx @@ -1,70 +1,203 @@ -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { + ChangeEvent, + FocusEventHandler, + ForwardedRef, + forwardRef, + InputHTMLAttributes, + useRef, + useState, +} from 'react'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { Key } from 'ts-key-enum'; -import { textInputStyle } from '@/ui/theme/constants/effects'; +import { IconAlertCircle } from '@/ui/icon'; +import { IconEye, IconEyeOff } from '@/ui/icon/index'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useCombinedRefs } from '~/hooks/useCombinedRefs'; -import { useRegisterInputEvents } from '../hooks/useRegisterInputEvents'; +import { InputHotkeyScope } from '../types/InputHotkeyScope'; -export const StyledInput = styled.input` - margin: 0; - width: 100%; - ${textInputStyle} +type TextInputComponentProps = Omit< + InputHTMLAttributes, + 'onChange' +> & { + label?: string; + onChange?: (text: string) => void; + fullWidth?: boolean; + disableHotkeys?: boolean; + error?: string; +}; + +const StyledContainer = styled.div>` + display: flex; + flex-direction: column; + width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')}; `; -type TextInputProps = { - placeholder?: string; - autoFocus?: boolean; - value: string; - onEnter: (newText: string) => void; - onEscape: (newText: string) => void; - onTab?: (newText: string) => void; - onShiftTab?: (newText: string) => void; - onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void; - hotkeyScope: string; -}; +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)}; + text-transform: uppercase; +`; -export const TextInput = ({ - placeholder, - autoFocus, - value, - hotkeyScope, - onEnter, - onEscape, - onTab, - onShiftTab, - onClickOutside, -}: TextInputProps) => { - const [internalText, setInternalText] = useState(value); +const StyledInputContainer = styled.div` + display: flex; + flex-direction: row; - const wrapperRef = useRef(null); + width: 100%; +`; - const handleChange = (event: ChangeEvent) => { - setInternalText(event.target.value); +const StyledInput = styled.input>` + background-color: ${({ theme }) => theme.background.tertiary}; + border: none; + border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm}; + border-top-left-radius: ${({ theme }) => theme.border.radius.sm}; + color: ${({ theme }) => theme.font.color.primary}; + display: flex; + flex-grow: 1; + font-family: ${({ theme }) => theme.font.family}; + + font-weight: ${({ theme }) => theme.font.weight.regular}; + 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}; + } +`; + +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.tertiary}; + border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm}; + 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: pointer; + display: flex; + justify-content: center; +`; + +const INPUT_TYPE_PASSWORD = 'password'; + +const TextInputComponent = ( + { + label, + value, + onChange, + onFocus, + onBlur, + fullWidth, + error, + required, + type, + disableHotkeys = false, + autoFocus, + placeholder, + disabled, + tabIndex, + }: TextInputComponentProps, + // eslint-disable-next-line twenty/component-props-naming + ref: ForwardedRef, +): JSX.Element => { + const theme = useTheme(); + + const inputRef = useRef(null); + const combinedRef = useCombinedRefs(ref, inputRef); + + const { + goBackToPreviousHotkeyScope, + setHotkeyScopeAndMemorizePreviousScope, + } = usePreviousHotkeyScope(); + + const handleFocus: FocusEventHandler = (e) => { + onFocus?.(e); + if (!disableHotkeys) { + setHotkeyScopeAndMemorizePreviousScope(InputHotkeyScope.TextInput); + } }; - useEffect(() => { - setInternalText(value); - }, [value]); + const handleBlur: FocusEventHandler = (e) => { + onBlur?.(e); + if (!disableHotkeys) { + goBackToPreviousHotkeyScope(); + } + }; - useRegisterInputEvents({ - inputRef: wrapperRef, - inputValue: internalText, - onEnter, - onEscape, - onClickOutside, - onTab, - onShiftTab, - hotkeyScope, - }); + useScopedHotkeys( + [Key.Escape, Key.Enter], + () => { + inputRef.current?.blur(); + }, + InputHotkeyScope.TextInput, + ); + + const [passwordVisible, setPasswordVisible] = useState(false); + + const handleTogglePasswordVisibility = () => { + setPasswordVisible(!passwordVisible); + }; return ( - + + {label && {label + (required ? '*' : '')}} + + ) => { + onChange?.(event.target.value); + }} + {...{ autoFocus, disabled, placeholder, required, value }} + /> + + {error && ( + + + + )} + {!error && type === INPUT_TYPE_PASSWORD && ( + + {passwordVisible ? ( + + ) : ( + + )} + + )} + + + {error && {error}} + ); }; + +export const TextInput = forwardRef(TextInputComponent); diff --git a/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx b/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx deleted file mode 100644 index afe556a1b..000000000 --- a/front/src/modules/ui/input/components/__stories__/EmailInputDisplay.stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; - -import { EmailDisplay } from '../../../field/meta-types/display/content-display/components/EmailDisplay'; - -const meta: Meta = { - title: 'UI/Input/EmailInputDisplay', - component: EmailDisplay, - decorators: [ComponentWithRouterDecorator], - args: { - value: 'mustajab.ikram@google.com', - }, -}; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/front/src/modules/ui/input/image/components/__stories__/ImageInput.stories.tsx b/front/src/modules/ui/input/components/__stories__/ImageInput.stories.tsx similarity index 100% rename from front/src/modules/ui/input/image/components/__stories__/ImageInput.stories.tsx rename to front/src/modules/ui/input/components/__stories__/ImageInput.stories.tsx diff --git a/front/src/modules/ui/input/components/DatePicker.tsx b/front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx similarity index 98% rename from front/src/modules/ui/input/components/DatePicker.tsx rename to front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx index c3dfdf59d..3d76158eb 100644 --- a/front/src/modules/ui/input/components/DatePicker.tsx +++ b/front/src/modules/ui/input/components/internal/date/components/InternalDatePicker.tsx @@ -220,17 +220,17 @@ const StyledContainer = styled.div` } `; -export type DatePickerProps = { +export type InternalDatePickerProps = { date: Date; onMouseSelect?: (date: Date) => void; onChange?: (date: Date) => void; }; -export const DatePicker = ({ +export const InternalDatePicker = ({ date, onChange, onMouseSelect, -}: DatePickerProps) => ( +}: InternalDatePickerProps) => ( = { - title: 'UI/Input/DatePicker', - component: DatePicker, +const meta: Meta = { + title: 'UI/Input/InternalDatePicker', + component: InternalDatePicker, decorators: [ComponentDecorator], argTypes: { date: { control: 'date' }, @@ -17,7 +17,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; diff --git a/front/src/modules/ui/input/components/CountryPickerDropdownButton.tsx b/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx similarity index 97% rename from front/src/modules/ui/input/components/CountryPickerDropdownButton.tsx rename to front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx index 5f80c8d86..a5f7c3c53 100644 --- a/front/src/modules/ui/input/components/CountryPickerDropdownButton.tsx +++ b/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownButton.tsx @@ -10,9 +10,9 @@ import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { useDropdown } from '@/ui/dropdown/hooks/useDropdown'; import { DropdownScope } from '@/ui/dropdown/scopes/DropdownScope'; import { IconChevronDown } from '@/ui/icon'; +import { IconWorld } from '@/ui/input/constants/icons'; -import { IconWorld } from '../constants/icons'; -import { CountryPickerHotkeyScope } from '../Types/CountryPickerHotkeyScope'; +import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope'; import { CountryPickerDropdownSelect } from './CountryPickerDropdownSelect'; diff --git a/front/src/modules/ui/input/components/CountryPickerDropdownSelect.tsx b/front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownSelect.tsx similarity index 100% rename from front/src/modules/ui/input/components/CountryPickerDropdownSelect.tsx rename to front/src/modules/ui/input/components/internal/phone/components/CountryPickerDropdownSelect.tsx diff --git a/front/src/modules/ui/input/Types/CountryPickerHotkeyScope.ts b/front/src/modules/ui/input/components/internal/phone/types/CountryPickerHotkeyScope.ts similarity index 100% rename from front/src/modules/ui/input/Types/CountryPickerHotkeyScope.ts rename to front/src/modules/ui/input/components/internal/phone/types/CountryPickerHotkeyScope.ts diff --git a/front/src/modules/ui/input/text/components/TextInputEdit.tsx b/front/src/modules/ui/input/text/components/TextInputEdit.tsx deleted file mode 100644 index e0cf341b1..000000000 --- a/front/src/modules/ui/input/text/components/TextInputEdit.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import styled from '@emotion/styled'; - -import { textInputStyle } from '@/ui/theme/constants/effects'; -import { overlayBackground } from '@/ui/theme/constants/effects'; - -const StyledInplaceInputTextInput = styled.input` - margin: 0; - width: 100%; - ${textInputStyle} -`; - -const StyledTextInputContainer = styled.div` - align-items: center; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.sm}; - display: flex; - margin-left: -1px; - min-height: 32px; - width: inherit; - - ${overlayBackground} - - z-index: 10; -`; - -export type TextInputEditProps = { - placeholder?: string; - value?: string; - onChange?: (newValue: string) => void; - autoFocus?: boolean; -}; - -export const TextInputEdit = ({ - placeholder, - value, - onChange, - autoFocus, -}: TextInputEditProps) => ( - - onChange?.(e.target.value)} - /> - -); diff --git a/front/src/modules/ui/input/text/components/TextInputSettings.tsx b/front/src/modules/ui/input/text/components/TextInputSettings.tsx deleted file mode 100644 index fde416e28..000000000 --- a/front/src/modules/ui/input/text/components/TextInputSettings.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { - ChangeEvent, - FocusEventHandler, - ForwardedRef, - forwardRef, - InputHTMLAttributes, - useRef, - useState, -} from 'react'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { Key } from 'ts-key-enum'; - -import { IconAlertCircle } from '@/ui/icon'; -import { IconEye, IconEyeOff } from '@/ui/icon/index'; -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'; - -type TextInputComponentProps = Omit< - InputHTMLAttributes, - 'onChange' -> & { - label?: string; - onChange?: (text: string) => void; - fullWidth?: boolean; - disableHotkeys?: boolean; - error?: string; -}; - -const StyledContainer = styled.div>` - display: 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)}; - text-transform: uppercase; -`; - -const StyledInputContainer = styled.div` - display: flex; - flex-direction: row; - - width: 100%; -`; - -const StyledInput = styled.input>` - background-color: ${({ theme }) => theme.background.tertiary}; - border: none; - border-bottom-left-radius: ${({ theme }) => theme.border.radius.sm}; - border-top-left-radius: ${({ theme }) => theme.border.radius.sm}; - color: ${({ theme }) => theme.font.color.primary}; - display: flex; - flex-grow: 1; - font-family: ${({ theme }) => theme.font.family}; - - font-weight: ${({ theme }) => theme.font.weight.regular}; - 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}; - } -`; - -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.tertiary}; - border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm}; - 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: pointer; - display: flex; - justify-content: center; -`; - -const INPUT_TYPE_PASSWORD = 'password'; - -const TextInputComponent = ( - { - label, - value, - onChange, - onFocus, - onBlur, - fullWidth, - error, - required, - type, - disableHotkeys = false, - autoFocus, - placeholder, - disabled, - tabIndex, - }: TextInputComponentProps, - // eslint-disable-next-line twenty/component-props-naming - ref: ForwardedRef, -): JSX.Element => { - const theme = useTheme(); - - const inputRef = useRef(null); - const combinedRef = useCombinedRefs(ref, inputRef); - - const { - goBackToPreviousHotkeyScope, - setHotkeyScopeAndMemorizePreviousScope, - } = usePreviousHotkeyScope(); - - const handleFocus: FocusEventHandler = (e) => { - onFocus?.(e); - if (!disableHotkeys) { - setHotkeyScopeAndMemorizePreviousScope(InputHotkeyScope.TextInput); - } - }; - - const handleBlur: FocusEventHandler = (e) => { - onBlur?.(e); - if (!disableHotkeys) { - goBackToPreviousHotkeyScope(); - } - }; - - useScopedHotkeys( - [Key.Escape, Key.Enter], - () => { - inputRef.current?.blur(); - }, - InputHotkeyScope.TextInput, - ); - - const [passwordVisible, setPasswordVisible] = useState(false); - - const handleTogglePasswordVisibility = () => { - setPasswordVisible(!passwordVisible); - }; - - return ( - - {label && {label + (required ? '*' : '')}} - - ) => { - onChange?.(event.target.value); - }} - {...{ autoFocus, disabled, placeholder, required, value }} - /> - - {error && ( - - - - )} - {!error && type === INPUT_TYPE_PASSWORD && ( - - {passwordVisible ? ( - - ) : ( - - )} - - )} - - - {error && {error}} - - ); -}; - -export const TextInputSettings = forwardRef(TextInputComponent); diff --git a/front/src/modules/ui/input/text/components/__stories__/TextInput.stories.tsx b/front/src/modules/ui/input/text/components/__stories__/TextInput.stories.tsx deleted file mode 100644 index 2d57367ff..000000000 --- a/front/src/modules/ui/input/text/components/__stories__/TextInput.stories.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { useState } from 'react'; -import { expect } from '@storybook/jest'; -import { jest } from '@storybook/jest'; -import { Meta, StoryObj } from '@storybook/react'; -import { userEvent, within } from '@storybook/testing-library'; - -import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; - -import { TextInputSettings } from '../TextInputSettings'; - -const changeJestFn = jest.fn(); - -const meta: Meta = { - title: 'UI/Input/TextInput', - component: TextInputSettings, - decorators: [ComponentDecorator], - args: { value: '', onChange: changeJestFn, placeholder: 'Placeholder' }, -}; - -export default meta; -type Story = StoryObj; - -const FakeTextInput = ({ - autoFocus, - disableHotkeys = false, - disabled, - error, - fullWidth, - label, - onBlur, - onChange, - onFocus, - placeholder, - required, - tabIndex, - type, - value: initialValue, -}: React.ComponentProps) => { - const [value, setValue] = useState(initialValue); - return ( - { - setValue(text); - onChange?.(text); - }} - /> - ); -}; - -export const Default: Story = { - argTypes: { value: { control: false } }, - args: { value: 'A good value ' }, - render: ({ - autoFocus, - disableHotkeys, - disabled, - error, - fullWidth, - label, - onBlur, - onChange, - onFocus, - placeholder, - required, - tabIndex, - type, - value, - }) => ( - - ), - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const input = canvas.getByRole('textbox'); - await userEvent.type(input, 'cou', { delay: 100 }); - - expect(changeJestFn).toHaveBeenNthCalledWith(1, 'A good value c'); - expect(changeJestFn).toHaveBeenNthCalledWith(2, 'A good value co'); - expect(changeJestFn).toHaveBeenNthCalledWith(3, 'A good value cou'); - }, -}; - -export const Placeholder: Story = {}; - -export const FullWidth: Story = { - args: { value: 'A good value', fullWidth: true }, -}; - -export const WithLabel: Story = { - args: { label: 'Lorem ipsum' }, -}; - -export const WithError: Story = { - args: { error: 'Lorem ipsum' }, -}; - -export const PasswordInput: Story = { - args: { type: 'password', placeholder: 'Password' }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - const input = canvas.getByPlaceholderText('Password'); - await userEvent.type(input, 'pa$$w0rd'); - - const revealButton = canvas.getByTestId('reveal-password-button'); - await userEvent.click(revealButton); - - expect(input).toHaveAttribute('type', 'text'); - }, -}; diff --git a/front/src/modules/ui/input/text/types/InputHotkeyScope.ts b/front/src/modules/ui/input/types/InputHotkeyScope.ts similarity index 100% rename from front/src/modules/ui/input/text/types/InputHotkeyScope.ts rename to front/src/modules/ui/input/types/InputHotkeyScope.ts diff --git a/front/src/modules/ui/modal/components/ConfirmationModal.tsx b/front/src/modules/ui/modal/components/ConfirmationModal.tsx index 1037f6861..fd2afb29d 100644 --- a/front/src/modules/ui/modal/components/ConfirmationModal.tsx +++ b/front/src/modules/ui/modal/components/ConfirmationModal.tsx @@ -4,7 +4,7 @@ import { AnimatePresence, LayoutGroup } from 'framer-motion'; import debounce from 'lodash.debounce'; import { Button } from '@/ui/button/components/Button'; -import { TextInputSettings } from '@/ui/input/text/components/TextInputSettings'; +import { TextInput } from '@/ui/input/components/TextInput'; import { Modal } from '@/ui/modal/components/Modal'; import { Section, @@ -99,7 +99,7 @@ export const ConfirmationModal = ({ {confirmationValue && (
- { }; return ( - - +