Create consistent ui/input and ui/display for Cell and Fields type : … (#1658)

* Create consistent ui/input and ui/display for Cell and Fields type : Probability, DoubleText, DoubleTextChip

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Move components to `ui/input`

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Update imports in ProbabilityEditableFieldEditMode

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Refactor according to review

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Create consistent ui/input and ui/display for Cell and Fields type : Probability, DoubleText, DoubleTextChip

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Merge main

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add more refactors

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Refactor according to review

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

---------

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
This commit is contained in:
gitstart-twenty
2023-09-25 10:10:14 +01:00
committed by GitHub
parent 1c3897848f
commit f777fa22e9
8 changed files with 242 additions and 159 deletions

View File

@ -0,0 +1,37 @@
import { CompanyChip } from '@/companies/components/CompanyChip';
import { PersonChip } from '@/people/components/PersonChip';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
type OwnProps = {
entityType: Entity;
displayName: string;
entityId: string | null;
avatarUrlValue?: string;
};
export const DoubleTextChipDisplay = ({
entityType,
displayName,
entityId,
avatarUrlValue,
}: OwnProps) => {
switch (entityType) {
case Entity.Company: {
return <CompanyChip id={entityId ?? ''} name={displayName} />;
}
case Entity.Person: {
return (
<PersonChip
id={entityId ?? ''}
name={displayName}
pictureUrl={avatarUrlValue}
/>
);
}
default:
console.warn(
`Unknown relation type: "${entityType}" in DoubleTextChipDisplay`,
);
return <> </>;
}
};

View File

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

View File

@ -1,8 +1,8 @@
import { useContext, useState } from 'react';
import styled from '@emotion/styled';
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { ProbabilityInput } from '@/ui/input/components/ProbabilityInput';
import { EditableFieldDefinitionContext } from '../contexts/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../contexts/EditableFieldEntityIdContext';
@ -11,66 +11,7 @@ import { genericEntityFieldFamilySelector } from '../states/selectors/genericEnt
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldProbabilityMetadata } from '../types/FieldMetadata';
const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 100%;
`;
const StyledProgressBarItemContainer = styled.div`
align-items: center;
display: flex;
height: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledProgressBarItem = styled.div<{
isFirst: boolean;
isLast: boolean;
isActive: boolean;
}>`
background-color: ${({ theme, isActive }) =>
isActive
? theme.font.color.secondary
: theme.background.transparent.medium};
border-bottom-left-radius: ${({ theme, isFirst }) =>
isFirst ? theme.border.radius.sm : theme.border.radius.xs};
border-bottom-right-radius: ${({ theme, isLast }) =>
isLast ? theme.border.radius.sm : theme.border.radius.xs};
border-top-left-radius: ${({ theme, isFirst }) =>
isFirst ? theme.border.radius.sm : theme.border.radius.xs};
border-top-right-radius: ${({ theme, isLast }) =>
isLast ? theme.border.radius.sm : theme.border.radius.xs};
height: ${({ theme }) => theme.spacing(2)};
width: ${({ theme }) => theme.spacing(3)};
`;
const StyledProgressBarContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 100%;
`;
const StyledLabel = styled.div`
width: ${({ theme }) => theme.spacing(12)};
`;
const PROBABILITY_VALUES = [
{ label: '0%', value: 0 },
{ label: '25%', value: 25 },
{ label: '50%', value: 50 },
{ label: '75%', value: 75 },
{ label: '100%', value: 100 },
];
export const ProbabilityEditableFieldEditMode = () => {
const [nextProbabilityIndex, setNextProbabilityIndex] = useState<
number | null
>(null);
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
@ -104,37 +45,9 @@ export const ProbabilityEditableFieldEditMode = () => {
};
return (
<StyledContainer>
<StyledLabel>
{
PROBABILITY_VALUES[
nextProbabilityIndex || nextProbabilityIndex === 0
? nextProbabilityIndex
: probabilityIndex
].label
}
</StyledLabel>
<StyledProgressBarContainer>
{PROBABILITY_VALUES.map((probability, i) => (
<StyledProgressBarItemContainer
key={i}
onClick={() => handleChange(probability.value)}
onMouseEnter={() => setNextProbabilityIndex(i)}
onMouseLeave={() => setNextProbabilityIndex(null)}
>
<StyledProgressBarItem
isActive={
nextProbabilityIndex || nextProbabilityIndex === 0
? i <= nextProbabilityIndex
: i <= probabilityIndex
}
key={probability.label}
isFirst={i === 0}
isLast={i === PROBABILITY_VALUES.length - 1}
/>
</StyledProgressBarItemContainer>
))}
</StyledProgressBarContainer>
</StyledContainer>
<ProbabilityInput
probabilityIndex={probabilityIndex}
onChange={handleChange}
/>
);
};

View File

@ -0,0 +1,65 @@
import { ChangeEvent, Ref } from 'react';
import styled from '@emotion/styled';
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;
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)};
}
`;
export const DoubleTextInput = ({
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
firstValueInputRef,
secondValueInputRef,
onChange,
containerRef,
}: OwnProps) => {
return (
<StyledContainer ref={containerRef}>
<StyledInput
autoComplete="off"
autoFocus
placeholder={firstValuePlaceholder}
ref={firstValueInputRef}
value={firstValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(event.target.value, secondValue);
}}
/>
<StyledInput
autoComplete="off"
placeholder={secondValuePlaceholder}
ref={secondValueInputRef}
value={secondValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange(firstValue, event.target.value);
}}
/>
</StyledContainer>
);
};

View File

@ -0,0 +1,104 @@
import { useState } from 'react';
import styled from '@emotion/styled';
const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 100%;
`;
const StyledProgressBarItemContainer = styled.div`
align-items: center;
display: flex;
height: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledProgressBarItem = styled.div<{
isFirst: boolean;
isLast: boolean;
isActive: boolean;
}>`
background-color: ${({ theme, isActive }) =>
isActive
? theme.font.color.secondary
: theme.background.transparent.medium};
border-bottom-left-radius: ${({ theme, isFirst }) =>
isFirst ? theme.border.radius.sm : theme.border.radius.xs};
border-bottom-right-radius: ${({ theme, isLast }) =>
isLast ? theme.border.radius.sm : theme.border.radius.xs};
border-top-left-radius: ${({ theme, isFirst }) =>
isFirst ? theme.border.radius.sm : theme.border.radius.xs};
border-top-right-radius: ${({ theme, isLast }) =>
isLast ? theme.border.radius.sm : theme.border.radius.xs};
height: ${({ theme }) => theme.spacing(2)};
width: ${({ theme }) => theme.spacing(3)};
`;
const StyledProgressBarContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 100%;
`;
const StyledLabel = styled.div`
width: ${({ theme }) => theme.spacing(12)};
`;
const PROBABILITY_VALUES = [
{ label: '0%', value: 0 },
{ label: '25%', value: 25 },
{ label: '50%', value: 50 },
{ label: '75%', value: 75 },
{ label: '100%', value: 100 },
];
type OwnProps = {
probabilityIndex: number;
onChange: (newValue: number) => void;
};
export const ProbabilityInput = ({ onChange, probabilityIndex }: OwnProps) => {
const [nextProbabilityIndex, setNextProbabilityIndex] = useState<
number | null
>(null);
return (
<StyledContainer>
<StyledLabel>
{
PROBABILITY_VALUES[
nextProbabilityIndex || nextProbabilityIndex === 0
? nextProbabilityIndex
: probabilityIndex
].label
}
</StyledLabel>
<StyledProgressBarContainer>
{PROBABILITY_VALUES.map((probability, i) => (
<StyledProgressBarItemContainer
key={i}
onClick={() => onChange(probability.value)}
onMouseEnter={() => setNextProbabilityIndex(i)}
onMouseLeave={() => setNextProbabilityIndex(null)}
>
<StyledProgressBarItem
isActive={
nextProbabilityIndex || nextProbabilityIndex === 0
? i <= nextProbabilityIndex
: i <= probabilityIndex
}
key={probability.label}
isFirst={i === 0}
isLast={i === PROBABILITY_VALUES.length - 1}
/>
</StyledProgressBarItemContainer>
))}
</StyledProgressBarContainer>
</StyledContainer>
);
};

View File

@ -1,15 +1,13 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { useEffect, useRef, useState } from 'react';
import { Key } from 'ts-key-enum';
import { DoubleTextInput } from '@/ui/input/components/DoubleTextInput';
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
import { useRegisterCloseCellHandlers } from '@/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers';
import { useMoveSoftFocus } from '@/ui/table/hooks/useMoveSoftFocus';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { StyledInput } from '../../../../input/components/TextInput';
import { useEditableCell } from '../../hooks/useEditableCell';
import { useRegisterCloseCellHandlers } from '../../hooks/useRegisterCloseCellHandlers';
type OwnProps = {
firstValue: string;
secondValue: string;
@ -20,21 +18,6 @@ type OwnProps = {
onCancel?: () => void;
};
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)};
}
`;
export const DoubleTextCellEdit = ({
firstValue,
secondValue,
@ -142,26 +125,17 @@ export const DoubleTextCellEdit = ({
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
return (
<StyledContainer ref={wrapperRef}>
<StyledInput
autoComplete="off"
autoFocus
placeholder={firstValuePlaceholder}
ref={firstValueInputRef}
value={firstInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
handleOnChange(event.target.value, secondInternalValue);
}}
/>
<StyledInput
autoComplete="off"
placeholder={secondValuePlaceholder}
ref={secondValueInputRef}
value={secondInternalValue}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
handleOnChange(firstInternalValue, event.target.value);
}}
/>
</StyledContainer>
<DoubleTextInput
{...{
firstValue,
secondValue,
firstValuePlaceholder,
secondValuePlaceholder,
firstValueInputRef,
secondValueInputRef,
}}
onChange={handleOnChange}
containerRef={wrapperRef}
/>
);
};

View File

@ -1,6 +1,6 @@
import { useRecoilValue } from 'recoil';
import { TextDisplay } from '@/ui/content-display/components/TextDisplay';
import { DoubleTextDisplay } from '@/ui/content-display/components/DoubleTextDisplay';
import type { ViewFieldDoubleTextMetadata } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
@ -42,7 +42,7 @@ export const GenericEditableDoubleTextCell = ({
columnDefinition={columnDefinition}
/>
}
nonEditModeContent={<TextDisplay text={displayName} />}
nonEditModeContent={<DoubleTextDisplay text={displayName} />}
></EditableCell>
);
};

View File

@ -1,9 +1,7 @@
import { useRecoilState } from 'recoil';
import { CompanyChip } from '@/companies/components/CompanyChip';
import { PersonChip } from '@/people/components/PersonChip';
import { DoubleTextChipDisplay } from '@/ui/content-display/components/DoubleTextChipDisplay';
import type { ViewFieldDoubleTextChipMetadata } from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector';
@ -41,23 +39,12 @@ export const GenericEditableDoubleTextChipCellDisplayMode = ({
const displayName = [firstValue, secondValue].filter(Boolean).join(' ');
switch (columnDefinition.metadata.entityType) {
case Entity.Company: {
return <CompanyChip id={currentRowEntityId ?? ''} name={displayName} />;
}
case Entity.Person: {
return (
<PersonChip
id={currentRowEntityId ?? ''}
name={displayName}
pictureUrl={avatarUrlValue}
/>
);
}
default:
console.warn(
`Unknown relation type: "${columnDefinition.metadata.entityType}" in GenericEditableDoubleTextChipCellDisplayMode`,
);
return <> </>;
}
return (
<DoubleTextChipDisplay
entityType={columnDefinition.metadata.entityType}
displayName={displayName}
entityId={currentRowEntityId}
avatarUrlValue={avatarUrlValue}
/>
);
};