Various styling improvements (#766)

* Various styling improvements

* Add card styling

* Fix select when editing fields

* Add colors

* Refactor prevent click
This commit is contained in:
Emilien Chauvet
2023-07-19 15:31:53 -07:00
committed by GitHub
parent d7efed9f89
commit 5fb7d753ef
10 changed files with 142 additions and 65 deletions

View File

@ -1,4 +1,4 @@
import { useCallback } from 'react'; import { ReactNode, useCallback, useState } 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';
@ -13,28 +13,32 @@ import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsSta
import { ChipVariant } from '@/ui/chip/components/EntityChip'; import { ChipVariant } from '@/ui/chip/components/EntityChip';
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField'; import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField'; import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField';
import { IconCheck, IconCurrencyDollar } 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 } 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';
import { CompanyAccountOwnerEditableField } from '../editable-field/components/CompanyAccountOwnerEditableField';
import { PipelineProgressForBoard } from '../types/CompanyProgress'; import { PipelineProgressForBoard } from '../types/CompanyProgress';
import { CompanyChip } from './CompanyChip'; import { CompanyChip } from './CompanyChip';
const StyledBoardCard = styled.div<{ selected: boolean }>` const StyledBoardCard = styled.div<{ selected: boolean }>`
background-color: ${({ theme, selected }) => background-color: ${({ theme, selected }) =>
selected ? theme.selectedCard : theme.background.secondary}; selected ? theme.accent.quaternary : theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.medium}; border: 1px solid
${({ theme, selected }) =>
selected ? theme.accent.secondary : theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
box-shadow: ${({ theme }) => theme.boxShadow.light}; box-shadow: ${({ theme }) => theme.boxShadow.light};
color: ${({ theme }) => theme.font.color.primary}; color: ${({ theme }) => theme.font.color.primary};
&:hover { &:hover {
background-color: ${({ theme, selected }) => background-color: ${({ theme, selected }) =>
selected ? theme.selectedCardHover : theme.background.tertiary}; selected && theme.accent.tertiary};
border: 1px solid
${({ theme, selected }) =>
selected ? theme.accent.primary : theme.border.color.medium};
} }
cursor: pointer; cursor: pointer;
`; `;
@ -49,6 +53,7 @@ const StyledBoardCardHeader = styled.div`
flex-direction: row; flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.semiBold}; font-weight: ${({ theme }) => theme.font.weight.semiBold};
height: 24px; height: 24px;
padding-bottom: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)}; padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)}; padding-right: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(2)}; padding-top: ${({ theme }) => theme.spacing(2)};
@ -62,7 +67,10 @@ const StyledBoardCardHeader = styled.div`
const StyledBoardCardBody = styled.div` const StyledBoardCardBody = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(0.5)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(2.5)};
padding-right: ${({ theme }) => theme.spacing(2)};
span { span {
align-items: center; align-items: center;
display: flex; display: flex;
@ -74,7 +82,12 @@ const StyledBoardCardBody = styled.div`
} }
`; `;
const StyledFieldContainer = styled.div`
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(
@ -119,17 +132,34 @@ export function CompanyBoardCard() {
[updatePipelineProgress], [updatePipelineProgress],
); );
const handleCheckboxChange = (checked: boolean) => {
setSelected(checked);
};
if (!company || !pipelineProgress) { if (!company || !pipelineProgress) {
return null; return null;
} }
function PreventSelectOnClickContainer({
children,
}: {
children: ReactNode;
}) {
return (
<StyledFieldContainer
onClick={(e) => {
e.stopPropagation();
}}
>
{children}
</StyledFieldContainer>
);
}
return ( return (
<StyledBoardCardWrapper> <StyledBoardCardWrapper>
<StyledBoardCard selected={selected}> <StyledBoardCard
selected={selected}
onClick={() => setSelected(!selected)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<StyledBoardCardHeader> <StyledBoardCardHeader>
<CompanyChip <CompanyChip
id={company.id} id={company.id}
@ -139,45 +169,56 @@ export function CompanyBoardCard() {
variant={ChipVariant.transparent} variant={ChipVariant.transparent}
/> />
<div style={{ display: 'flex', flex: 1 }} /> <div style={{ display: 'flex', flex: 1 }} />
<Checkbox checked={selected} onChange={handleCheckboxChange} /> {(isHovered || selected) && (
<Checkbox
checked={selected}
onChange={() => setSelected(!selected)}
/>
)}
</StyledBoardCardHeader> </StyledBoardCardHeader>
<StyledBoardCardBody> <StyledBoardCardBody>
<NumberEditableField <PreventSelectOnClickContainer>
icon={<IconCurrencyDollar />} <DateEditableField
placeholder="Opportunity amount" icon={<IconCalendarEvent />}
value={pipelineProgress.amount} value={pipelineProgress.closeDate || new Date().toISOString()}
onSubmit={(value) => onSubmit={(value) =>
handleCardUpdate({ handleCardUpdate({
...pipelineProgress, ...pipelineProgress,
amount: value, closeDate: value,
}) })
} }
/> />
<CompanyAccountOwnerEditableField company={company} /> </PreventSelectOnClickContainer>
<DateEditableField <PreventSelectOnClickContainer>
icon={<IconCalendarEvent />} <NumberEditableField
value={pipelineProgress.closeDate || new Date().toISOString()} icon={<IconCurrencyDollar />}
onSubmit={(value) => placeholder="Opportunity amount"
handleCardUpdate({ value={pipelineProgress.amount}
...pipelineProgress, onSubmit={(value) =>
closeDate: value, handleCardUpdate({
}) ...pipelineProgress,
} amount: value,
/> })
}
<ProbabilityEditableField />
icon={<IconCheck />} </PreventSelectOnClickContainer>
value={pipelineProgress.probability} <PreventSelectOnClickContainer>
onSubmit={(value) => { <ProbabilityEditableField
handleCardUpdate({ icon={<IconProgressCheck />}
...pipelineProgress, value={pipelineProgress.probability}
probability: value, onSubmit={(value) => {
}); handleCardUpdate({
}} ...pipelineProgress,
/> probability: value,
<PipelineProgressPointOfContactEditableField });
pipelineProgress={pipelineProgress} }}
/> />
</PreventSelectOnClickContainer>
<PreventSelectOnClickContainer>
<PipelineProgressPointOfContactEditableField
pipelineProgress={pipelineProgress}
/>
</PreventSelectOnClickContainer>
</StyledBoardCardBody> </StyledBoardCardBody>
</StyledBoardCard> </StyledBoardCard>
</StyledBoardCardWrapper> </StyledBoardCardWrapper>

View File

@ -24,7 +24,7 @@ const StyledPlaceholder = styled.div`
`; `;
const StyledNewCardButtonContainer = styled.div` const StyledNewCardButtonContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(40)}; padding-bottom: ${({ theme }) => theme.spacing(4)};
`; `;
const BoardColumnCardsContainer = ({ const BoardColumnCardsContainer = ({
@ -86,6 +86,7 @@ export function EntityBoardColumn({
colorCode={column.colorCode} colorCode={column.colorCode}
pipelineStageId={column.pipelineStageId} pipelineStageId={column.pipelineStageId}
totalAmount={boardColumnTotal} totalAmount={boardColumnTotal}
isFirstColumn={column.index === 0}
> >
<BoardColumnCardsContainer droppableProvided={droppableProvided}> <BoardColumnCardsContainer droppableProvided={droppableProvided}>
{column.pipelineProgressIds.map((pipelineProgressId, index) => ( {column.pipelineProgressIds.map((pipelineProgressId, index) => (

View File

@ -4,10 +4,10 @@ import { DropResult } from '@hello-pangea/dnd'; // Atlassian dnd does not suppor
export const StyledBoard = styled.div` export const StyledBoard = styled.div`
border-radius: ${({ theme }) => theme.spacing(2)}; border-radius: ${({ theme }) => theme.spacing(2)};
display: flex; display: flex;
flex: 1;
flex-direction: row; flex-direction: row;
overflow-x: auto; overflow-x: auto;
padding-left: ${({ theme }) => theme.spacing(2)}; padding-left: ${({ theme }) => theme.spacing(2)};
width: 100%;
`; `;
export type BoardPipelineStageColumn = { export type BoardPipelineStageColumn = {

View File

@ -5,8 +5,11 @@ import { debounce } from '~/utils/debounce';
import { EditColumnTitleInput } from './EditColumnTitleInput'; import { EditColumnTitleInput } from './EditColumnTitleInput';
export const StyledColumn = styled.div` export const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
background-color: ${({ theme }) => theme.background.primary}; background-color: ${({ theme }) => theme.background.primary};
border-left: 1px solid
${({ theme, isFirstColumn }) =>
isFirstColumn ? 'none' : theme.border.color.light};
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 200px; min-width: 200px;
@ -14,15 +17,19 @@ export const StyledColumn = styled.div`
`; `;
const StyledHeader = styled.div` const StyledHeader = styled.div`
align-items: center;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; height: 24px;
justify-content: left;
margin-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%; width: 100%;
`; `;
export const StyledColumnTitle = styled.h3` export const StyledColumnTitle = styled.h3`
align-items: center; align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ color }) => color}; color: ${({ color }) => color};
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -30,13 +37,16 @@ export const StyledColumnTitle = styled.h3`
font-style: normal; font-style: normal;
font-weight: ${({ theme }) => theme.font.weight.medium}; font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
height: 24px;
margin: 0; margin: 0;
margin-bottom: ${({ theme }) => theme.spacing(1)}; padding-bottom: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(1)};
`; `;
const StyledAmount = styled.div` const StyledAmount = styled.div`
color: ${({ theme }) => theme.font.color.tertiary}; color: ${({ theme }) => theme.font.color.tertiary};
margin-left: ${({ theme }) => theme.spacing(2)};
`; `;
type OwnProps = { type OwnProps = {
@ -46,6 +56,7 @@ type OwnProps = {
onTitleEdit: (title: string) => void; onTitleEdit: (title: string) => void;
totalAmount?: number; totalAmount?: number;
children: React.ReactNode; children: React.ReactNode;
isFirstColumn: boolean;
}; };
export function BoardColumn({ export function BoardColumn({
@ -54,6 +65,7 @@ export function BoardColumn({
onTitleEdit, onTitleEdit,
totalAmount, totalAmount,
children, children,
isFirstColumn,
}: OwnProps) { }: OwnProps) {
const [isEditing, setIsEditing] = React.useState(false); const [isEditing, setIsEditing] = React.useState(false);
const [internalValue, setInternalValue] = React.useState(title); const [internalValue, setInternalValue] = React.useState(title);
@ -65,10 +77,9 @@ export function BoardColumn({
}; };
return ( return (
<StyledColumn> <StyledColumn isFirstColumn={isFirstColumn}>
<StyledHeader onClick={() => setIsEditing(true)}> <StyledHeader onClick={() => setIsEditing(true)}>
<StyledColumnTitle color={colorCode}> <StyledColumnTitle color={colorCode}>
{isEditing ? ( {isEditing ? (
<EditColumnTitleInput <EditColumnTitleInput
color={colorCode} color={colorCode}

View File

@ -16,11 +16,12 @@ type OwnProps<SortField> = {
}; };
const StyledContainer = styled.div` const StyledContainer = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
display: flex; display: flex;
flex-direction: column; flex-direction: column;
`; `;
const StyledTableHeader = styled.div` const StyledBoardHeader = styled.div`
align-items: center; align-items: center;
color: ${({ theme }) => theme.font.color.secondary}; color: ${({ theme }) => theme.font.color.secondary};
display: flex; display: flex;
@ -83,7 +84,7 @@ export function BoardHeader<SortField>({
return ( return (
<StyledContainer> <StyledContainer>
<StyledTableHeader> <StyledBoardHeader>
<StyledViewSection> <StyledViewSection>
<StyledIcon>{viewIcon}</StyledIcon> <StyledIcon>{viewIcon}</StyledIcon>
{viewName} {viewName}
@ -100,7 +101,7 @@ export function BoardHeader<SortField>({
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/> />
</StyledFilters> </StyledFilters>
</StyledTableHeader> </StyledBoardHeader>
<SortAndFilterBar <SortAndFilterBar
context={context} context={context}
sorts={sorts} sorts={sorts}

View File

@ -16,7 +16,7 @@ const StyledButton = styled.button`
padding: ${({ theme }) => theme.spacing(1)}; padding: ${({ theme }) => theme.spacing(1)};
&:hover { &:hover {
background-color: ${({ theme }) => theme.background.secondary}; background-color: ${({ theme }) => theme.background.tertiary};
} }
`; `;

View File

@ -12,6 +12,7 @@ export { IconSearch } from '@tabler/icons-react';
export { IconSettings } from '@tabler/icons-react'; export { IconSettings } from '@tabler/icons-react';
export { IconLogout } from '@tabler/icons-react'; export { IconLogout } from '@tabler/icons-react';
export { IconColorSwatch } from '@tabler/icons-react'; export { IconColorSwatch } from '@tabler/icons-react';
export { IconProgressCheck } from '@tabler/icons-react';
export { IconX } from '@tabler/icons-react'; export { IconX } from '@tabler/icons-react';
export { IconChevronLeft } from '@tabler/icons-react'; export { IconChevronLeft } from '@tabler/icons-react';
export { IconPlus } from '@tabler/icons-react'; export { IconPlus } from '@tabler/icons-react';

View File

@ -0,0 +1,15 @@
import { color } from './colors';
export const accentLight = {
primary: color.blueAccent25,
secondary: color.blueAccent20,
tertiary: color.blueAccent15,
quaternary: color.blueAccent10,
};
export const accentDark = {
primary: color.blueAccent75,
secondary: color.blueAccent80,
tertiary: color.blueAccent85,
quaternary: color.blueAccent90,
};

View File

@ -116,6 +116,14 @@ export const color = {
gray20: grayScale.gray15, gray20: grayScale.gray15,
gray10: grayScale.gray10, gray10: grayScale.gray10,
gray0: grayScale.gray0, gray0: grayScale.gray0,
blueAccent90: '#141a25',
blueAccent85: '#151D2E',
blueAccent80: '#152037',
blueAccent75: '#16233F',
blueAccent25: '#dae6fc',
blueAccent20: '#e2ecfd',
blueAccent15: '#edf2fe',
blueAccent10: '#f5f9fd',
}; };
export function rgba(hex: string, alpha: number) { export function rgba(hex: string, alpha: number) {

View File

@ -1,3 +1,4 @@
import { accentDark, accentLight } from './accent';
import { animation } from './animation'; import { animation } from './animation';
import { backgroundDark, backgroundLight } from './background'; import { backgroundDark, backgroundLight } from './background';
import { blur } from './blur'; import { blur } from './blur';
@ -43,11 +44,10 @@ const common = {
export const lightTheme = { export const lightTheme = {
...common, ...common,
...{ ...{
accent: accentLight,
background: backgroundLight, background: backgroundLight,
border: borderLight, border: borderLight,
boxShadow: boxShadowLight, boxShadow: boxShadowLight,
selectedCardHover: color.blue20,
selectedCard: color.blue10,
font: fontLight, font: fontLight,
name: 'light', name: 'light',
}, },
@ -57,11 +57,10 @@ export type ThemeType = typeof lightTheme;
export const darkTheme: ThemeType = { export const darkTheme: ThemeType = {
...common, ...common,
...{ ...{
accent: accentDark,
background: backgroundDark, background: backgroundDark,
border: borderDark, border: borderDark,
boxShadow: boxShadowDark, boxShadow: boxShadowDark,
selectedCardHover: color.blue70,
selectedCard: color.blue80,
font: fontDark, font: fontDark,
name: 'dark', name: 'dark',
}, },