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:
@ -8,6 +8,9 @@ const StyledEditableBooleanFieldContainer = styled.div`
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledEditableBooleanFieldValue = styled.div`
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -32,7 +32,7 @@ const StyledTextInput = styled(StyledInput)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const DoubleTextInputEdit = ({
|
||||
export const EntityTitleDoubleTextInput = ({
|
||||
firstValue,
|
||||
secondValue,
|
||||
firstValuePlaceholder,
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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',
|
||||
|
||||
Reference in New Issue
Block a user