Moved pure UI object fields to ui folder (#2861)

* Moved pure UI object fields to ui folder

* Moved pure UI object fields to ui folder 2
This commit is contained in:
Lucas Bordeau
2023-12-07 16:27:39 +01:00
committed by GitHub
parent 4fecf6d8b9
commit 145b432dc6
40 changed files with 35 additions and 34 deletions

View File

@ -1,5 +1,6 @@
import { CurrencyDisplay } from '@/ui/field/display/components/CurrencyDisplay';
import { useCurrencyField } from '../../hooks/useCurrencyField';
import { CurrencyDisplay } from '../content-display/components/CurrencyDisplay';
export const CurrencyFieldDisplay = () => {
const { initialAmount } = useCurrencyField();

View File

@ -1,4 +1,4 @@
import { DateDisplay } from '@/object-record/field/meta-types/display/content-display/components/DateDisplay';
import { DateDisplay } from '@/ui/field/display/components/DateDisplay';
import { useDateTimeField } from '../../hooks/useDateTimeField';

View File

@ -1,5 +1,6 @@
import { EmailDisplay } from '@/ui/field/display/components/EmailDisplay';
import { useEmailField } from '../../hooks/useEmailField';
import { EmailDisplay } from '../content-display/components/EmailDisplay';
export const EmailFieldDisplay = () => {
const { fieldValue } = useEmailField();

View File

@ -1,6 +1,5 @@
import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField';
import { TextDisplay } from '../content-display/components/TextDisplay';
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
export const FullNameFieldDisplay = () => {
const { fieldValue } = useFullNameField();

View File

@ -1,5 +1,6 @@
import { LinkDisplay } from '@/ui/field/display/components/LinkDisplay';
import { useLinkField } from '../../hooks/useLinkField';
import { LinkDisplay } from '../content-display/components/LinkDisplay';
export const LinkFieldDisplay = () => {
const { fieldValue } = useLinkField();

View File

@ -1,4 +1,4 @@
import { NumberDisplay } from '@/object-record/field/meta-types/display/content-display/components/NumberDisplay';
import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay';
import { useNumberField } from '../../hooks/useNumberField';

View File

@ -1,4 +1,4 @@
import { PhoneDisplay } from '@/object-record/field/meta-types/display/content-display/components/PhoneDisplay';
import { PhoneDisplay } from '@/ui/field/display/components/PhoneDisplay';
import { usePhoneField } from '../../hooks/usePhoneField';

View File

@ -1,4 +1,4 @@
import { TextDisplay } from '@/object-record/field/meta-types/display/content-display/components/TextDisplay';
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
import { useTextField } from '../../hooks/useTextField';

View File

@ -1,5 +1,5 @@
import { TextDisplay } from '@/object-record/field/meta-types/display/content-display/components/TextDisplay';
import { useUuidField } from '@/object-record/field/meta-types/hooks/useUuidField';
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
export const UuidFieldDisplay = () => {
const { fieldValue } = useUuidField();

View File

@ -1,10 +0,0 @@
import { EllipsisDisplay } from './EllipsisDisplay';
type CurrencyDisplayProps = {
amount?: number | null;
};
// TODO: convert currencyCode to currency symbol
export const CurrencyDisplay = ({ amount }: CurrencyDisplayProps) => {
return <EllipsisDisplay>{amount}</EllipsisDisplay>;
};

View File

@ -1,11 +0,0 @@
import { formatToHumanReadableDate } from '~/utils';
import { EllipsisDisplay } from './EllipsisDisplay';
type DateDisplayProps = {
value: Date | string | null | undefined;
};
export const DateDisplay = ({ value }: DateDisplayProps) => (
<EllipsisDisplay>{value && formatToHumanReadableDate(value)}</EllipsisDisplay>
);

View File

@ -1,3 +0,0 @@
import { TextDisplay } from './TextDisplay';
export const DoubleTextDisplay = TextDisplay;

View File

@ -1,10 +0,0 @@
import styled from '@emotion/styled';
const StyledEllipsisDisplay = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
`;
export { StyledEllipsisDisplay as EllipsisDisplay };

View File

@ -1,31 +0,0 @@
import { MouseEvent } from 'react';
import { ContactLink } from '@/ui/navigation/link/components/ContactLink';
import { EllipsisDisplay } from './EllipsisDisplay';
const validateEmail = (email: string) => {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailPattern.test(email.trim());
};
type EmailDisplayProps = {
value: string | null;
};
export const EmailDisplay = ({ value }: EmailDisplayProps) => (
<EllipsisDisplay>
{value && validateEmail(value) ? (
<ContactLink
href={`mailto:${value}`}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{value}
</ContactLink>
) : (
<ContactLink href="#">{value}</ContactLink>
)}
</EllipsisDisplay>
);

View File

@ -1,73 +0,0 @@
import { MouseEvent } from 'react';
import styled from '@emotion/styled';
import { FieldLinkValue } from '@/object-record/field/types/FieldMetadata';
import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink';
import {
LinkType,
SocialLink,
} from '@/ui/navigation/link/components/SocialLink';
import { EllipsisDisplay } from './EllipsisDisplay';
const StyledRawLink = styled(RoundedLink)`
overflow: hidden;
a {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`;
type LinkDisplayProps = {
value?: FieldLinkValue;
};
const checkUrlType = (url: string) => {
if (
/^(http|https):\/\/(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(
url,
)
) {
return LinkType.LinkedIn;
}
if (url.match(/^((http|https):\/\/)?(?:www\.)?twitter\.com\/(\w+)?/i)) {
return LinkType.Twitter;
}
return LinkType.Url;
};
export const LinkDisplay = ({ value }: LinkDisplayProps) => {
const handleClick = (event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
};
const absoluteUrl = value?.url
? value.url.startsWith('http')
? value.url
: 'https://' + value.url
: '';
const displayedValue = value?.label || value?.url || '';
const type = checkUrlType(absoluteUrl);
if (type === LinkType.LinkedIn || type === LinkType.Twitter) {
return (
<EllipsisDisplay>
<SocialLink href={absoluteUrl} onClick={handleClick} type={type}>
{displayedValue}
</SocialLink>
</EllipsisDisplay>
);
}
return (
<EllipsisDisplay>
<StyledRawLink href={absoluteUrl} onClick={handleClick}>
{displayedValue}
</StyledRawLink>
</EllipsisDisplay>
);
};

View File

@ -1,11 +0,0 @@
import { formatNumber } from '~/utils/format/number';
import { EllipsisDisplay } from './EllipsisDisplay';
type MoneyDisplayProps = {
value: number | null;
};
export const MoneyDisplay = ({ value }: MoneyDisplayProps) => (
<EllipsisDisplay>{value ? `$${formatNumber(value)}` : ''}</EllipsisDisplay>
);

View File

@ -1,11 +0,0 @@
import { formatNumber } from '~/utils/format/number';
import { EllipsisDisplay } from './EllipsisDisplay';
type NumberDisplayProps = {
value: string | number | null;
};
export const NumberDisplay = ({ value }: NumberDisplayProps) => (
<EllipsisDisplay>{value && formatNumber(Number(value))}</EllipsisDisplay>
);

View File

@ -1,27 +0,0 @@
import { MouseEvent } from 'react';
import { isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js';
import { ContactLink } from '@/ui/navigation/link/components/ContactLink';
import { EllipsisDisplay } from './EllipsisDisplay';
type PhoneDisplayProps = {
value: string | null;
};
export const PhoneDisplay = ({ value }: PhoneDisplayProps) => (
<EllipsisDisplay>
{value && isValidPhoneNumber(value) ? (
<ContactLink
href={parsePhoneNumber(value, 'FR')?.getURI()}
onClick={(event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
}}
>
{parsePhoneNumber(value, 'FR')?.formatInternational() || value}
</ContactLink>
) : (
<ContactLink href="#">{value}</ContactLink>
)}
</EllipsisDisplay>
);

View File

@ -1,9 +0,0 @@
import { EllipsisDisplay } from './EllipsisDisplay';
type TextDisplayProps = {
text: string;
};
export const TextDisplay = ({ text }: TextDisplayProps) => (
<EllipsisDisplay>{text}</EllipsisDisplay>
);

View File

@ -1,72 +0,0 @@
import { MouseEvent } from 'react';
import styled from '@emotion/styled';
import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink';
import {
LinkType,
SocialLink,
} from '@/ui/navigation/link/components/SocialLink';
import { EllipsisDisplay } from './EllipsisDisplay';
const StyledRawLink = styled(RoundedLink)`
overflow: hidden;
a {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`;
type URLDisplayProps = {
value: string | null;
};
const checkUrlType = (url: string) => {
if (
/^(http|https):\/\/(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(
url,
)
) {
return LinkType.LinkedIn;
}
if (url.match(/^((http|https):\/\/)?(?:www\.)?twitter\.com\/(\w+)?/i)) {
return LinkType.Twitter;
}
return LinkType.Url;
};
export const URLDisplay = ({ value }: URLDisplayProps) => {
const handleClick = (event: MouseEvent<HTMLElement>) => {
event.stopPropagation();
};
const absoluteUrl = value
? value.startsWith('http')
? value
: 'https://' + value
: '';
const displayedValue = value ?? '';
const type = checkUrlType(absoluteUrl);
if (type === LinkType.LinkedIn || type === LinkType.Twitter) {
return (
<EllipsisDisplay>
<SocialLink href={absoluteUrl} onClick={handleClick} type={type}>
{displayedValue}
</SocialLink>
</EllipsisDisplay>
);
}
return (
<EllipsisDisplay>
<StyledRawLink href={absoluteUrl} onClick={handleClick}>
{displayedValue}
</StyledRawLink>
</EllipsisDisplay>
);
};

View File

@ -1,20 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { PhoneDisplay } from '../PhoneDisplay'; // Adjust the import path as needed
const meta: Meta = {
title: 'UI/Input/PhoneInputDisplay/PhoneInputDisplay',
component: PhoneDisplay,
decorators: [ComponentWithRouterDecorator],
args: {
value: '+33788901234',
},
};
export default meta;
type Story = StoryObj<typeof PhoneDisplay>;
export const Default: Story = {};

View File

@ -1,4 +1,4 @@
import { BooleanInput } from '@/object-record/field/meta-types/input/components/internal/BooleanInput';
import { BooleanInput } from '@/ui/field/input/components/BooleanInput';
import { usePersistField } from '../../../hooks/usePersistField';
import { useBooleanField } from '../../hooks/useBooleanField';

View File

@ -1,8 +1,8 @@
import { TextInput } from '@/object-record/field/meta-types/input/components/internal/TextInput';
import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { useCurrencyField } from '../../hooks/useCurrencyField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
import { FieldInputEvent } from './DateFieldInput';
export type CurrencyFieldInputProps = {

View File

@ -1,4 +1,4 @@
import { DateInput } from '@/object-record/field/meta-types/input/components/internal/DateInput';
import { DateInput } from '@/ui/field/input/components/DateInput';
import { Nullable } from '~/types/Nullable';
import { usePersistField } from '../../../hooks/usePersistField';

View File

@ -1,9 +1,9 @@
import { TextInput } from '@/object-record/field/meta-types/input/components/internal/TextInput';
import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { usePersistField } from '../../../hooks/usePersistField';
import { useEmailField } from '../../hooks/useEmailField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
import { FieldInputEvent } from './DateFieldInput';
export type EmailFieldInputProps = {

View File

@ -1,10 +1,10 @@
import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField';
import { DoubleTextInput } from '@/object-record/field/meta-types/input/components/internal/DoubleTextInput';
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { usePersistField } from '../../../hooks/usePersistField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
import { FieldInputEvent } from './DateFieldInput';
export type FullNameFieldInputProps = {

View File

@ -1,8 +1,8 @@
import { TextInput } from '@/object-record/field/meta-types/input/components/internal/TextInput';
import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { useLinkField } from '../../hooks/useLinkField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
import { FieldInputEvent } from './DateFieldInput';
export type LinkFieldInputProps = {

View File

@ -1,9 +1,8 @@
import { TextInput } from '@/object-record/field/meta-types/input/components/internal/TextInput';
import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { useNumberField } from '../../hooks/useNumberField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
export type FieldInputEvent = (persist: () => void) => void;
export type NumberFieldInputProps = {

View File

@ -1,8 +1,8 @@
import { PhoneInput } from '@/object-record/field/meta-types/input/components/internal/PhoneInput';
import { PhoneInput } from '@/ui/field/input/components/PhoneInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { usePhoneField } from '../../hooks/usePhoneField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
import { FieldInputEvent } from './DateFieldInput';
export type PhoneFieldInputProps = {

View File

@ -1,4 +1,4 @@
import { RatingInput } from '@/object-record/field/meta-types/input/components/internal/RatingInput';
import { RatingInput } from '@/ui/field/input/components/RatingInput';
import { usePersistField } from '../../../hooks/usePersistField';
import { useRatingField } from '../../hooks/useRatingField';

View File

@ -1,9 +1,9 @@
import { TextInput } from '@/object-record/field/meta-types/input/components/internal/TextInput';
import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
import { usePersistField } from '../../../hooks/usePersistField';
import { useTextField } from '../../hooks/useTextField';
import { FieldInputOverlay } from './internal/FieldInputOverlay';
import { FieldInputEvent } from './DateFieldInput';
export type TextFieldInputProps = {

View File

@ -1,61 +0,0 @@
import { useEffect, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconCheck, IconX } from '@/ui/display/icon';
const StyledEditableBooleanFieldContainer = styled.div`
align-items: center;
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')};
display: flex;
height: 100%;
width: 100%;
`;
const StyledEditableBooleanFieldValue = styled.div`
margin-left: ${({ theme }) => theme.spacing(1)};
`;
type BooleanInputProps = {
value: boolean;
onToggle?: (newValue: boolean) => void;
readonly?: boolean;
testId?: string;
};
export const BooleanInput = ({
value,
onToggle,
readonly,
testId,
}: BooleanInputProps) => {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
const handleClick = () => {
setInternalValue(!internalValue);
onToggle?.(!internalValue);
};
const theme = useTheme();
return (
<StyledEditableBooleanFieldContainer
onClick={readonly ? undefined : handleClick}
data-testid={testId}
>
{internalValue ? (
<IconCheck size={theme.icon.size.sm} />
) : (
<IconX size={theme.icon.size.sm} />
)}
<StyledEditableBooleanFieldValue>
{internalValue ? 'True' : 'False'}
</StyledEditableBooleanFieldValue>
</StyledEditableBooleanFieldContainer>
);
};

View File

@ -1,102 +0,0 @@
import { useEffect, useRef, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { flip, offset, useFloating } from '@floating-ui/react';
import { DateDisplay } from '@/object-record/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';
const StyledCalendarContainer = styled.div`
background: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.md};
box-shadow: ${({ theme }) => theme.boxShadow.strong};
margin-top: 1px;
position: absolute;
z-index: 1;
`;
const StyledInputContainer = styled.div`
padding: ${({ theme }) => theme.spacing(0)} ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
export type DateInputProps = {
value: Nullable<Date>;
onEnter: (newDate: Nullable<Date>) => void;
onEscape: (newDate: Nullable<Date>) => void;
onClickOutside: (
event: MouseEvent | TouchEvent,
newDate: Nullable<Date>,
) => void;
hotkeyScope: string;
};
export const DateInput = ({
value,
hotkeyScope,
onEnter,
onEscape,
onClickOutside,
}: DateInputProps) => {
const theme = useTheme();
const [internalValue, setInternalValue] = useState(value);
const wrapperRef = useRef<HTMLDivElement>(null);
const { refs, floatingStyles } = useFloating({
placement: 'bottom-start',
middleware: [
flip(),
offset({
mainAxis: theme.spacingMultiplicator * 2,
}),
],
});
const handleChange = (newDate: Date) => {
setInternalValue(newDate);
};
useEffect(() => {
setInternalValue(value);
}, [value]);
useRegisterInputEvents({
inputRef: wrapperRef,
inputValue: internalValue,
onEnter,
onEscape,
onClickOutside,
hotkeyScope,
});
return (
<div ref={wrapperRef}>
<div ref={refs.setReference}>
<StyledInputContainer>
<DateDisplay value={internalValue ?? new Date()} />
</StyledInputContainer>
</div>
<div ref={refs.setFloating} style={floatingStyles}>
<StyledCalendarContainer>
<InternalDatePicker
date={internalValue ?? new Date()}
onChange={handleChange}
onMouseSelect={(newDate: Date) => {
onEnter(newDate);
}}
/>
</StyledCalendarContainer>
</div>
</div>
);
};

View File

@ -1,171 +0,0 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined';
import { StyledInput } from './TextInput';
const StyledContainer = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
input {
width: ${({ theme }) => theme.spacing(24)};
}
& > input:last-child {
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
padding-left: ${({ theme }) => theme.spacing(2)};
}
`;
type DoubleTextInputProps = {
firstValue: string;
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
hotkeyScope: string;
onEnter: (newDoubleTextValue: FieldDoubleText) => void;
onEscape: (newDoubleTextValue: FieldDoubleText) => void;
onTab?: (newDoubleTextValue: FieldDoubleText) => void;
onShiftTab?: (newDoubleTextValue: FieldDoubleText) => void;
onClickOutside: (
event: MouseEvent | TouchEvent,
newDoubleTextValue: FieldDoubleText,
) => void;
};
export const DoubleTextInput = ({
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
hotkeyScope,
onClickOutside,
onEnter,
onEscape,
onShiftTab,
onTab,
}: DoubleTextInputProps) => {
const [firstInternalValue, setFirstInternalValue] = useState(firstValue);
const [secondInternalValue, setSecondInternalValue] = useState(secondValue);
const firstValueInputRef = useRef<HTMLInputElement>(null);
const secondValueInputRef = useRef<HTMLInputElement>(null);
const containerRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setFirstInternalValue(firstValue);
setSecondInternalValue(secondValue);
}, [firstValue, secondValue]);
const handleChange = (
newFirstValue: string,
newSecondValue: string,
): void => {
setFirstInternalValue(newFirstValue);
setSecondInternalValue(newSecondValue);
};
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
useScopedHotkeys(
Key.Enter,
() => {
onEnter({
firstValue: firstInternalValue,
secondValue: secondInternalValue,
});
},
hotkeyScope,
[onEnter, firstInternalValue, secondInternalValue],
);
useScopedHotkeys(
Key.Escape,
() => {
onEscape({
firstValue: firstInternalValue,
secondValue: secondInternalValue,
});
},
hotkeyScope,
[onEscape, firstInternalValue, secondInternalValue],
);
useScopedHotkeys(
'tab',
() => {
if (focusPosition === 'left') {
setFocusPosition('right');
secondValueInputRef.current?.focus();
} else {
onTab?.({
firstValue: firstInternalValue,
secondValue: secondInternalValue,
});
}
},
hotkeyScope,
[onTab, firstInternalValue, secondInternalValue, focusPosition],
);
useScopedHotkeys(
'shift+tab',
() => {
if (focusPosition === 'right') {
setFocusPosition('left');
firstValueInputRef.current?.focus();
} else {
onShiftTab?.({
firstValue: firstInternalValue,
secondValue: secondInternalValue,
});
}
},
hotkeyScope,
[onShiftTab, firstInternalValue, secondInternalValue, focusPosition],
);
useListenClickOutside({
refs: [containerRef],
callback: (event) => {
onClickOutside?.(event, {
firstValue: firstInternalValue,
secondValue: secondInternalValue,
});
},
enabled: isDefined(onClickOutside),
});
return (
<StyledContainer ref={containerRef}>
<StyledInput
autoComplete="off"
autoFocus
onFocus={() => setFocusPosition('left')}
ref={firstValueInputRef}
placeholder={firstValuePlaceholder}
value={firstInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
handleChange(event.target.value, secondInternalValue);
}}
/>
<StyledInput
autoComplete="off"
onFocus={() => setFocusPosition('right')}
ref={secondValueInputRef}
placeholder={secondValuePlaceholder}
value={secondInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
handleChange(firstInternalValue, event.target.value);
}}
/>
</StyledContainer>
);
};

View File

@ -1,15 +0,0 @@
import styled from '@emotion/styled';
import { overlayBackground } from '@/ui/theme/constants/effects';
const StyledFieldInputOverlay = styled.div`
border: ${({ theme }) => `1px solid ${theme.border.color.light}`};
border-radius: ${({ theme }) => theme.border.radius.sm};
${overlayBackground}
display: flex;
height: 32px;
margin: -1px;
width: 100%;
`;
export const FieldInputOverlay = StyledFieldInputOverlay;

View File

@ -1,102 +0,0 @@
import { useEffect, useRef, useState } from 'react';
import ReactPhoneNumberInput from 'react-phone-number-input';
import styled from '@emotion/styled';
import { CountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/CountryPickerDropdownButton';
import { useRegisterInputEvents } from '../../hooks/useRegisterInputEvents';
import 'react-phone-number-input/style.css';
const StyledContainer = styled.div`
align-items: center;
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
justify-content: center;
`;
const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
font-family: ${({ theme }) => theme.font.family};
height: 32px;
.PhoneInputInput {
background: ${({ theme }) => theme.background.transparent.secondary};
border: none;
color: ${({ theme }) => theme.font.color.primary};
&::placeholder,
&::-webkit-input-placeholder {
color: ${({ theme }) => theme.font.color.light};
font-family: ${({ theme }) => theme.font.family};
font-weight: ${({ theme }) => theme.font.weight.medium};
}
:focus {
outline: none;
}
}
& svg {
border-radius: ${({ theme }) => theme.border.radius.xs};
height: 12px;
}
`;
export type PhoneInputProps = {
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 PhoneInput = ({
autoFocus,
value,
onEnter,
onEscape,
onTab,
onShiftTab,
onClickOutside,
hotkeyScope,
}: PhoneInputProps) => {
const [internalValue, setInternalValue] = useState<string | undefined>(value);
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setInternalValue(value);
}, [value]);
useRegisterInputEvents({
inputRef: wrapperRef,
inputValue: internalValue ?? '',
onEnter,
onEscape,
onClickOutside,
onTab,
onShiftTab,
hotkeyScope,
});
return (
<StyledContainer ref={wrapperRef}>
<StyledCustomPhoneInput
autoFocus={autoFocus}
placeholder="Phone number"
value={value}
onChange={setInternalValue}
international={true}
withCountryCallingCode={true}
countrySelectComponent={CountryPickerDropdownButton}
/>
</StyledContainer>
);
};

View File

@ -1,61 +0,0 @@
import { useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconTwentyStarFilled } from '@/ui/display/icon/components/IconTwentyStarFilled';
const StyledContainer = styled.div`
align-items: center;
display: flex;
`;
const StyledRatingIconContainer = styled.div<{ isActive?: boolean }>`
color: ${({ isActive, theme }) =>
isActive ? theme.font.color.secondary : theme.background.quaternary};
display: inline-flex;
`;
type RatingInputProps = {
onChange: (newValue: number) => void;
value: number;
readonly?: boolean;
};
const RATING_LEVELS_NB = 5;
export const RatingInput = ({
onChange,
value,
readonly,
}: RatingInputProps) => {
const theme = useTheme();
const [hoveredValue, setHoveredValue] = useState<number | null>(null);
const currentValue = hoveredValue ?? value;
return (
<StyledContainer
role="slider"
aria-label="Rating"
aria-valuemax={RATING_LEVELS_NB}
aria-valuemin={1}
aria-valuenow={value}
tabIndex={0}
>
{Array.from({ length: RATING_LEVELS_NB }, (_, index) => {
const rating = index + 1;
return (
<StyledRatingIconContainer
key={index}
isActive={rating <= currentValue}
onClick={readonly ? undefined : () => onChange(rating)}
onMouseEnter={readonly ? undefined : () => setHoveredValue(rating)}
onMouseLeave={readonly ? undefined : () => setHoveredValue(null)}
>
<IconTwentyStarFilled size={theme.icon.size.md} />
</StyledRatingIconContainer>
);
})}
</StyledContainer>
);
};

View File

@ -1,70 +0,0 @@
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;
${textInputStyle}
width: 100%;
`;
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<HTMLInputElement>(null);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setInternalText(event.target.value);
};
useEffect(() => {
setInternalText(value);
}, [value]);
useRegisterInputEvents({
inputRef: wrapperRef,
inputValue: internalText,
onEnter,
onEscape,
onClickOutside,
onTab,
onShiftTab,
hotkeyScope,
});
return (
<StyledInput
autoComplete="off"
ref={wrapperRef}
placeholder={placeholder}
onChange={handleChange}
autoFocus={autoFocus}
value={internalText}
/>
);
};