mirror of
https://github.com/lingble/twenty.git
synced 2025-11-02 21:57:56 +00:00
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:
@@ -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,
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user