Add minor UI updates (#772)

* Add minor UI updates

* Fix lint

* Fix company board card fields

* Fix company board card fields

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Emilien Chauvet
2023-07-19 22:40:52 -07:00
committed by GitHub
parent 7670ae5638
commit 8cd426fab8
8 changed files with 64 additions and 28 deletions

View File

@@ -1,4 +1,4 @@
import { ReactNode, useCallback, useState } from 'react'; import { ReactNode, useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities'; import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
@@ -15,7 +15,7 @@ import { DateEditableField } from '@/ui/editable-field/variants/components/DateE
import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField'; import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField';
import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon'; import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon';
import { IconCalendarEvent } from '@/ui/icon'; import { IconCalendarEvent } from '@/ui/icon';
import { Checkbox } from '@/ui/input/components/Checkbox'; import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql'; import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
@@ -41,6 +41,14 @@ const StyledBoardCard = styled.div<{ selected: boolean }>`
selected ? theme.accent.primary : theme.border.color.medium}; selected ? theme.accent.primary : theme.border.color.medium};
} }
cursor: pointer; cursor: pointer;
.checkbox-container {
opacity: 0;
}
&:hover .checkbox-container {
opacity: 1;
}
`; `;
const StyledBoardCardWrapper = styled.div` const StyledBoardCardWrapper = styled.div`
@@ -52,7 +60,7 @@ const StyledBoardCardHeader = styled.div`
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.semiBold}; font-weight: ${({ theme }) => theme.font.weight.medium};
height: 24px; height: 24px;
padding-bottom: ${({ theme }) => theme.spacing(1)}; padding-bottom: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)}; padding-left: ${({ theme }) => theme.spacing(2)};
@@ -84,12 +92,17 @@ const StyledBoardCardBody = styled.div`
} }
`; `;
const StyledCheckboxContainer = styled.div`
display: flex;
flex: 1;
justify-content: end;
`;
const StyledFieldContainer = styled.div` const StyledFieldContainer = styled.div`
width: max-content; width: max-content;
`; `;
export function CompanyBoardCard() { export function CompanyBoardCard() {
const [isHovered, setIsHovered] = useState(false);
const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation(); const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation();
const [pipelineProgressId] = useRecoilScopedState( const [pipelineProgressId] = useRecoilScopedState(
@@ -159,8 +172,6 @@ export function CompanyBoardCard() {
<StyledBoardCard <StyledBoardCard
selected={selected} selected={selected}
onClick={() => setSelected(!selected)} onClick={() => setSelected(!selected)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
> >
<StyledBoardCardHeader> <StyledBoardCardHeader>
<CompanyChip <CompanyChip
@@ -170,19 +181,19 @@ export function CompanyBoardCard() {
picture={getLogoUrlFromDomainName(company.domainName)} picture={getLogoUrlFromDomainName(company.domainName)}
variant={ChipVariant.transparent} variant={ChipVariant.transparent}
/> />
<div style={{ display: 'flex', flex: 1 }} /> <StyledCheckboxContainer className="checkbox-container">
{(isHovered || selected) && (
<Checkbox <Checkbox
checked={selected} checked={selected}
onChange={() => setSelected(!selected)} onChange={() => setSelected(!selected)}
variant={CheckboxVariant.Secondary}
/> />
)} </StyledCheckboxContainer>
</StyledBoardCardHeader> </StyledBoardCardHeader>
<StyledBoardCardBody> <StyledBoardCardBody>
<PreventSelectOnClickContainer> <PreventSelectOnClickContainer>
<DateEditableField <DateEditableField
icon={<IconCalendarEvent />} icon={<IconCalendarEvent />}
value={pipelineProgress.closeDate || new Date().toISOString()} value={pipelineProgress.closeDate}
onSubmit={(value) => onSubmit={(value) =>
handleCardUpdate({ handleCardUpdate({
...pipelineProgress, ...pipelineProgress,

View File

@@ -25,6 +25,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
return ( return (
<EditableCell <EditableCell
transparent transparent
maxContentWidth={160}
editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }} editHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker }}
editModeContent={ editModeContent={
isCreating ? ( isCreating ? (

View File

@@ -24,7 +24,7 @@ const DropdownMenuSelectableItemContainer = styled(DropdownMenuItem)<Props>`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
max-width: 150px; width: calc(100% - ${({ theme }) => theme.spacing(2)});
`; `;
const StyledLeftContainer = styled.div` const StyledLeftContainer = styled.div`

View File

@@ -49,7 +49,7 @@ export function DateEditableField({ icon, value, onSubmit }: OwnProps) {
iconLabel={icon} iconLabel={icon}
editModeContent={ editModeContent={
<EditableFieldEditModeDate <EditableFieldEditModeDate
value={internalValue ?? ''} value={internalValue || new Date().toISOString()}
onChange={(newValue: string) => { onChange={(newValue: string) => {
handleChange(newValue); handleChange(newValue);
}} }}
@@ -58,7 +58,7 @@ export function DateEditableField({ icon, value, onSubmit }: OwnProps) {
displayModeContent={ displayModeContent={
<InplaceInputDateDisplayMode value={internalDateValue} /> <InplaceInputDateDisplayMode value={internalDateValue} />
} }
isDisplayModeContentEmpty={!(internalValue !== '')} isDisplayModeContentEmpty={!internalValue}
/> />
</RecoilScope> </RecoilScope>
); );

View File

@@ -3,10 +3,16 @@ import styled from '@emotion/styled';
import { IconCheck, IconMinus } from '@/ui/icon'; import { IconCheck, IconMinus } from '@/ui/icon';
export enum CheckboxVariant {
Primary = 'primary',
Secondary = 'secondary',
}
type OwnProps = { type OwnProps = {
checked: boolean; checked: boolean;
indeterminate?: boolean; indeterminate?: boolean;
onChange?: (value: boolean) => void; onChange?: (value: boolean) => void;
variant?: CheckboxVariant;
}; };
const StyledInputContainer = styled.div` const StyledInputContainer = styled.div`
@@ -15,7 +21,10 @@ const StyledInputContainer = styled.div`
position: relative; position: relative;
`; `;
const StyledInput = styled.input<{ indeterminate?: boolean }>` const StyledInput = styled.input<{
indeterminate?: boolean;
variant?: CheckboxVariant;
}>`
cursor: pointer; cursor: pointer;
margin: 0; margin: 0;
opacity: 0; opacity: 0;
@@ -31,9 +40,17 @@ const StyledInput = styled.input<{ indeterminate?: boolean }>`
} }
& + label:before { & + label:before {
background: ${({ theme }) => theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.strong};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
background: ${({ theme, indeterminate }) =>
indeterminate ? theme.color.blue : 'transparent'};
border-style: solid;
border-width: 1px;
border-color: ${({ theme, indeterminate, variant }) =>
indeterminate
? theme.color.blue
: variant === CheckboxVariant.Primary
? theme.border.color.inverted
: theme.border.color.secondaryInverted};
content: ''; content: '';
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
@@ -46,13 +63,6 @@ const StyledInput = styled.input<{ indeterminate?: boolean }>`
border-color: ${({ theme }) => theme.color.blue}; border-color: ${({ theme }) => theme.color.blue};
} }
& + label:before {
background: ${({ theme, indeterminate }) =>
indeterminate ? theme.color.blue : theme.background.primary};
border-color: ${({ theme, indeterminate }) =>
indeterminate ? theme.color.blue : theme.border.color.inverted};
}
& + label > svg { & + label > svg {
height: 12px; height: 12px;
left: 1px; left: 1px;
@@ -63,7 +73,12 @@ const StyledInput = styled.input<{ indeterminate?: boolean }>`
} }
`; `;
export function Checkbox({ checked, onChange, indeterminate }: OwnProps) { export function Checkbox({
checked,
onChange,
indeterminate,
variant = CheckboxVariant.Primary,
}: OwnProps) {
const [isInternalChecked, setIsInternalChecked] = React.useState(false); const [isInternalChecked, setIsInternalChecked] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
@@ -83,6 +98,7 @@ export function Checkbox({ checked, onChange, indeterminate }: OwnProps) {
data-testid="input-checkbox" data-testid="input-checkbox"
checked={isInternalChecked} checked={isInternalChecked}
indeterminate={indeterminate} indeterminate={indeterminate}
variant={variant}
onChange={(event) => handleChange(event.target.checked)} onChange={(event) => handleChange(event.target.checked)}
/> />
<label htmlFor="checkbox"> <label htmlFor="checkbox">

View File

@@ -28,6 +28,7 @@ type OwnProps = {
editModeVerticalPosition?: 'over' | 'below'; editModeVerticalPosition?: 'over' | 'below';
editHotkeyScope?: HotkeyScope; editHotkeyScope?: HotkeyScope;
transparent?: boolean; transparent?: boolean;
maxContentWidth?: number;
onSubmit?: () => void; onSubmit?: () => void;
onCancel?: () => void; onCancel?: () => void;
}; };
@@ -39,6 +40,7 @@ export function EditableCell({
nonEditModeContent, nonEditModeContent,
editHotkeyScope, editHotkeyScope,
transparent = false, transparent = false,
maxContentWidth,
onSubmit, onSubmit,
onCancel, onCancel,
}: OwnProps) { }: OwnProps) {
@@ -50,6 +52,7 @@ export function EditableCell({
<CellBaseContainer> <CellBaseContainer>
{isCurrentCellInEditMode ? ( {isCurrentCellInEditMode ? (
<EditableCellEditMode <EditableCellEditMode
maxContentWidth={maxContentWidth}
transparent={transparent} transparent={transparent}
editModeHorizontalAlign={editModeHorizontalAlign} editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition} editModeVerticalPosition={editModeVerticalPosition}

View File

@@ -17,20 +17,23 @@ export const EditableCellEditModeContainer = styled.div<OwnProps>`
margin-left: -1px; margin-left: -1px;
margin-top: -1px; margin-top: -1px;
max-width: ${({ maxContentWidth }) =>
maxContentWidth ? `${maxContentWidth}px` : 'auto'};
min-height: 100%; min-height: 100%;
min-width: 100%; min-width: 100%;
position: absolute;
position: absolute;
right: ${(props) => right: ${(props) =>
props.editModeHorizontalAlign === 'right' ? '0' : 'auto'}; props.editModeHorizontalAlign === 'right' ? '0' : 'auto'};
top: ${(props) => (props.editModeVerticalPosition === 'over' ? '0' : '100%')}; top: ${(props) => (props.editModeVerticalPosition === 'over' ? '0' : '100%')};
z-index: 1;
${({ transparent }) => (transparent ? '' : overlayBackground)}; ${({ transparent }) => (transparent ? '' : overlayBackground)};
z-index: 1;
`; `;
type OwnProps = { type OwnProps = {
children: ReactElement; children: ReactElement;
transparent?: boolean; transparent?: boolean;
maxContentWidth?: number;
editModeHorizontalAlign?: 'left' | 'right'; editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below'; editModeVerticalPosition?: 'over' | 'below';
onOutsideClick?: () => void; onOutsideClick?: () => void;
@@ -45,6 +48,7 @@ export function EditableCellEditMode({
onCancel, onCancel,
onSubmit, onSubmit,
transparent = false, transparent = false,
maxContentWidth,
}: OwnProps) { }: OwnProps) {
const wrapperRef = useRef(null); const wrapperRef = useRef(null);
@@ -52,6 +56,7 @@ export function EditableCellEditMode({
return ( return (
<EditableCellEditModeContainer <EditableCellEditModeContainer
maxContentWidth={maxContentWidth}
transparent={transparent} transparent={transparent}
data-testid="editable-cell-edit-mode-container" data-testid="editable-cell-edit-mode-container"
ref={wrapperRef} ref={wrapperRef}

View File

@@ -14,7 +14,7 @@ export const borderLight = {
strong: grayScale.gray25, strong: grayScale.gray25,
medium: grayScale.gray20, medium: grayScale.gray20,
light: grayScale.gray15, light: grayScale.gray15,
invertedSecondary: grayScale.gray50, secondaryInverted: grayScale.gray50,
inverted: grayScale.gray60, inverted: grayScale.gray60,
}, },
...common, ...common,
@@ -25,7 +25,7 @@ export const borderDark = {
strong: grayScale.gray65, strong: grayScale.gray65,
medium: grayScale.gray70, medium: grayScale.gray70,
light: grayScale.gray75, light: grayScale.gray75,
invertedSecondary: grayScale.gray40, secondaryInverted: grayScale.gray40,
inverted: grayScale.gray30, inverted: grayScale.gray30,
}, },
...common, ...common,