FieldDisplay & FieldInput (#1708)

* Removed view field duplicate types

* wip

* wip 2

* wip 3

* Unified state for fields

* Renaming

* Wip

* Post merge

* Post post merge

* wip

* Delete unused file

* Boolean and Probability

* Finished InlineCell

* Renamed EditableCell to TableCell

* Finished double texts

* Finished MoneyField

* Fixed bug inline cell click outside

* Fixed hotkey scope

* Final fixes

* Phone

* Fix url and number input validation

* Fix

* Fix position

* wip refactor activity editor

* Fixed activity editor

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-09-27 18:18:02 +02:00
committed by GitHub
parent d9feabbc63
commit cbadcba188
290 changed files with 3152 additions and 4481 deletions

View File

@ -8,6 +8,9 @@ const StyledEditableBooleanFieldContainer = styled.div`
align-items: center;
cursor: pointer;
display: flex;
height: 100%;
width: 100%;
`;
const StyledEditableBooleanFieldValue = styled.div`

View File

@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { flip, offset, useFloating } from '@floating-ui/react';
import { DateDisplay } from '@/ui/content-display/components/DateDisplay';
import { DateDisplay } from '@/ui/field/meta-types/display/content-display/components/DateDisplay';
import { Nullable } from '~/types/Nullable';
import { useRegisterInputEvents } from '../hooks/useRegisterInputEvents';

View File

@ -1,19 +1,14 @@
import { ChangeEvent, Ref } from 'react';
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { FieldDoubleText } from '@/ui/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';
type OwnProps = {
firstValue: string;
secondValue: string;
firstValuePlaceholder: string;
secondValuePlaceholder: string;
onChange: (firstValue: string, secondValue: string) => void;
firstValueInputRef?: Ref<HTMLInputElement>;
secondValueInputRef?: Ref<HTMLInputElement>;
containerRef?: Ref<HTMLDivElement>;
};
const StyledContainer = styled.div`
align-items: center;
display: flex;
@ -29,35 +24,146 @@ const StyledContainer = styled.div`
}
`;
type OwnProps = {
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,
firstValueInputRef,
secondValueInputRef,
onChange,
containerRef,
hotkeyScope,
onClickOutside,
onEnter,
onEscape,
onShiftTab,
onTab,
}: OwnProps) => {
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
placeholder={firstValuePlaceholder}
onFocus={() => setFocusPosition('left')}
ref={firstValueInputRef}
value={firstValue}
placeholder={firstValuePlaceholder}
value={firstInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value, secondValue);
handleChange(event.target.value, secondInternalValue);
}}
/>
<StyledInput
autoComplete="off"
placeholder={secondValuePlaceholder}
onFocus={() => setFocusPosition('right')}
ref={secondValueInputRef}
value={secondValue}
placeholder={secondValuePlaceholder}
value={secondInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(firstValue, event.target.value);
handleChange(firstInternalValue, event.target.value);
}}
/>
</StyledContainer>

View File

@ -32,7 +32,7 @@ const StyledTextInput = styled(StyledInput)`
}
`;
export const DoubleTextInputEdit = ({
export const EntityTitleDoubleTextInput = ({
firstValue,
secondValue,
firstValuePlaceholder,

View File

@ -58,43 +58,44 @@ const PROBABILITY_VALUES = [
];
type OwnProps = {
probabilityIndex: number;
probabilityIndex: number | null;
onChange: (newValue: number) => void;
};
export const ProbabilityInput = ({ onChange, probabilityIndex }: OwnProps) => {
const [nextProbabilityIndex, setNextProbabilityIndex] = useState<
const [hoveredProbabilityIndex, setHoveredProbabilityIndex] = useState<
number | null
>(null);
const probabilityIndexToShow =
hoveredProbabilityIndex ?? probabilityIndex ?? 0;
return (
<StyledContainer>
<StyledLabel>
{
PROBABILITY_VALUES[
nextProbabilityIndex || nextProbabilityIndex === 0
? nextProbabilityIndex
: probabilityIndex
].label
}
{PROBABILITY_VALUES[probabilityIndexToShow].label}
</StyledLabel>
<StyledProgressBarContainer>
{PROBABILITY_VALUES.map((probability, i) => (
{PROBABILITY_VALUES.map((probability, probabilityIndexToSelect) => (
<StyledProgressBarItemContainer
key={i}
key={probabilityIndexToSelect}
onClick={() => onChange(probability.value)}
onMouseEnter={() => setNextProbabilityIndex(i)}
onMouseLeave={() => setNextProbabilityIndex(null)}
onMouseEnter={() =>
setHoveredProbabilityIndex(probabilityIndexToSelect)
}
onMouseLeave={() => setHoveredProbabilityIndex(null)}
>
<StyledProgressBarItem
isActive={
nextProbabilityIndex || nextProbabilityIndex === 0
? i <= nextProbabilityIndex
: i <= probabilityIndex
hoveredProbabilityIndex || hoveredProbabilityIndex === 0
? probabilityIndexToSelect <= hoveredProbabilityIndex
: probabilityIndexToSelect <= probabilityIndexToShow
}
key={probability.label}
isFirst={i === 0}
isLast={i === PROBABILITY_VALUES.length - 1}
isFirst={probabilityIndexToSelect === 0}
isLast={
probabilityIndexToSelect === PROBABILITY_VALUES.length - 1
}
/>
</StyledProgressBarItemContainer>
))}

View File

@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { EmailDisplay } from '../../../content-display/components/EmailDisplay';
import { EmailDisplay } from '../../../field/meta-types/display/content-display/components/EmailDisplay';
const meta: Meta = {
title: 'UI/Input/EmailInputDisplay',