1259/add compact view in opportunities (#2182)

* icons added

* recoil family state added for checking compact view in each card

* recoil state added for toggle button. Wether compact view show or not

* menu item modifed for right side content

* compact view toggle added in dropdown options

* dropdown width increased because compact view text was  overflowing

* compact view added in boardcard

* new animation added for in and out

* compact view enabled state added

* old state deleted

* sizes added in toggle component

* removed extra added code form navigation

* toggle size added in menuitem toggle

* MenuItemToggle added instead of MenuItemNavigate

* Compact view improved
This commit is contained in:
Abhishek Thory 2023-10-24 19:54:25 +05:30 committed by GitHub
parent 350410b0fe
commit bd0b886081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 216 additions and 43 deletions

View File

@ -6,11 +6,15 @@ import { FieldContext } from '@/ui/data/field/contexts/FieldContext';
import { InlineCell } from '@/ui/data/inline-cell/components/InlineCell'; import { InlineCell } from '@/ui/data/inline-cell/components/InlineCell';
import { InlineCellHotkeyScope } from '@/ui/data/inline-cell/types/InlineCellHotkeyScope'; import { InlineCellHotkeyScope } from '@/ui/data/inline-cell/types/InlineCellHotkeyScope';
import { EntityChipVariant } from '@/ui/display/chip/components/EntityChip'; import { EntityChipVariant } from '@/ui/display/chip/components/EntityChip';
import { IconEye } from '@/ui/display/icon/index';
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox'; import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
import { BoardCardIdContext } from '@/ui/layout/board/contexts/BoardCardIdContext'; import { BoardCardIdContext } from '@/ui/layout/board/contexts/BoardCardIdContext';
import { useBoardContext } from '@/ui/layout/board/hooks/useBoardContext'; import { useBoardContext } from '@/ui/layout/board/hooks/useBoardContext';
import { useCurrentCardSelected } from '@/ui/layout/board/hooks/useCurrentCardSelected'; import { useCurrentCardSelected } from '@/ui/layout/board/hooks/useCurrentCardSelected';
import { isCardInCompactViewState } from '@/ui/layout/board/states/isCardInCompactViewState';
import { isCompactViewEnabledState } from '@/ui/layout/board/states/isCompactViewEnabledState';
import { visibleBoardCardFieldsScopedSelector } from '@/ui/layout/board/states/selectors/visibleBoardCardFieldsScopedSelector'; import { visibleBoardCardFieldsScopedSelector } from '@/ui/layout/board/states/selectors/visibleBoardCardFieldsScopedSelector';
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql'; import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
@ -38,12 +42,21 @@ const StyledBoardCard = styled.div<{ selected: boolean }>`
cursor: pointer; cursor: pointer;
.checkbox-container { .checkbox-container {
transition: all ease-in-out 160ms;
opacity: ${({ selected }) => (selected ? 1 : 0)}; opacity: ${({ selected }) => (selected ? 1 : 0)};
} }
&:hover .checkbox-container { &:hover .checkbox-container {
opacity: 1; opacity: 1;
} }
.compact-icon-container {
transition: all ease-in-out 160ms;
opacity: 0;
}
&:hover .compact-icon-container {
opacity: 1;
}
`; `;
const StyledBoardCardWrapper = styled.div` const StyledBoardCardWrapper = styled.div`
@ -51,16 +64,21 @@ const StyledBoardCardWrapper = styled.div`
width: 100%; width: 100%;
`; `;
const StyledBoardCardHeader = styled.div` const StyledBoardCardHeader = styled.div<{
showCompactView: boolean;
}>`
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium}; font-weight: ${({ theme }) => theme.font.weight.medium};
height: 24px; height: 24px;
padding-bottom: ${({ theme }) => theme.spacing(1)}; padding-bottom: ${({ theme, showCompactView }) =>
theme.spacing(showCompactView ? 0 : 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)};
transition: padding ease-in-out 160ms;
img { img {
height: ${({ theme }) => theme.icon.size.md}px; height: ${({ theme }) => theme.icon.size.md}px;
margin-right: ${({ theme }) => theme.spacing(2)}; margin-right: ${({ theme }) => theme.spacing(2)};
@ -99,6 +117,27 @@ const StyledFieldContainer = styled.div`
width: 100%; width: 100%;
`; `;
const StyledCompactIconContainer = styled.div`
align-items: center;
display: flex;
justify-content: center;
`;
const StyledIconEye = styled(IconEye)`
color: ${({ theme }) => theme.font.color.tertiary};
height: 24px;
padding-bottom: ${({ theme }) => theme.spacing(0.2)};
padding-left: ${({ theme }) => theme.spacing(0.5)};
padding-right: ${({ theme }) => theme.spacing(0.5)};
padding-top: ${({ theme }) => theme.spacing(0.2)};
&:hover {
background-color: ${({ theme }) => theme.background.transparent.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
}
`;
export const CompanyBoardCard = () => { export const CompanyBoardCard = () => {
const { BoardRecoilScopeContext } = useBoardContext(); const { BoardRecoilScopeContext } = useBoardContext();
@ -109,6 +148,15 @@ export const CompanyBoardCard = () => {
const [companyProgress] = useRecoilState( const [companyProgress] = useRecoilState(
companyProgressesFamilyState(boardCardId ?? ''), companyProgressesFamilyState(boardCardId ?? ''),
); );
const [isCompactViewEnabled] = useRecoilState(isCompactViewEnabledState);
const [isCardInCompactView, setIsCardInCompactView] = useRecoilState(
isCardInCompactViewState(boardCardId ?? ''),
);
const showCompactView = isCompactViewEnabled && isCardInCompactView;
const { pipelineProgress, company } = companyProgress ?? {}; const { pipelineProgress, company } = companyProgress ?? {};
const visibleBoardCardFields = useRecoilScopedValue( const visibleBoardCardFields = useRecoilScopedValue(
@ -135,19 +183,34 @@ export const CompanyBoardCard = () => {
</StyledFieldContainer> </StyledFieldContainer>
); );
const OnMouseLeaveBoard = () => {
setIsCardInCompactView(true);
};
return ( return (
<StyledBoardCardWrapper> <StyledBoardCardWrapper>
<StyledBoardCard <StyledBoardCard
selected={currentCardSelected} selected={currentCardSelected}
onMouseLeave={OnMouseLeaveBoard}
onClick={() => setCurrentCardSelected(!currentCardSelected)} onClick={() => setCurrentCardSelected(!currentCardSelected)}
> >
<StyledBoardCardHeader> <StyledBoardCardHeader showCompactView={showCompactView}>
<CompanyChip <CompanyChip
id={company.id} id={company.id}
name={company.name} name={company.name}
pictureUrl={getLogoUrlFromDomainName(company.domainName)} pictureUrl={getLogoUrlFromDomainName(company.domainName)}
variant={EntityChipVariant.Transparent} variant={EntityChipVariant.Transparent}
/> />
{showCompactView && (
<StyledCompactIconContainer className="compact-icon-container">
<StyledIconEye
onClick={(e) => {
e.stopPropagation();
setIsCardInCompactView(false);
}}
/>
</StyledCompactIconContainer>
)}
<StyledCheckboxContainer className="checkbox-container"> <StyledCheckboxContainer className="checkbox-container">
<Checkbox <Checkbox
checked={currentCardSelected} checked={currentCardSelected}
@ -157,28 +220,32 @@ export const CompanyBoardCard = () => {
</StyledCheckboxContainer> </StyledCheckboxContainer>
</StyledBoardCardHeader> </StyledBoardCardHeader>
<StyledBoardCardBody> <StyledBoardCardBody>
{visibleBoardCardFields.map((viewField) => ( <AnimatedEaseInOut isOpen={!showCompactView}>
<PreventSelectOnClickContainer key={viewField.key}> {visibleBoardCardFields.map((viewField) => (
<FieldContext.Provider <PreventSelectOnClickContainer key={viewField.key}>
value={{ <FieldContext.Provider
entityId: boardCardId, value={{
recoilScopeId: boardCardId + viewField.key, entityId: boardCardId,
fieldDefinition: { recoilScopeId: boardCardId + viewField.key,
key: viewField.key, fieldDefinition: {
name: viewField.name, key: viewField.key,
Icon: viewField.Icon, name: viewField.name,
type: viewField.type, Icon: viewField.Icon,
metadata: viewField.metadata, type: viewField.type,
entityChipDisplayMapper: viewField.entityChipDisplayMapper, metadata: viewField.metadata,
}, entityChipDisplayMapper:
useUpdateEntityMutation: useUpdateOnePipelineProgressMutation, viewField.entityChipDisplayMapper,
hotkeyScope: InlineCellHotkeyScope.InlineCell, },
}} useUpdateEntityMutation:
> useUpdateOnePipelineProgressMutation,
<InlineCell /> hotkeyScope: InlineCellHotkeyScope.InlineCell,
</FieldContext.Provider> }}
</PreventSelectOnClickContainer> >
))} <InlineCell />
</FieldContext.Provider>
</PreventSelectOnClickContainer>
))}
</AnimatedEaseInOut>
</StyledBoardCardBody> </StyledBoardCardBody>
</StyledBoardCard> </StyledBoardCard>
</StyledBoardCardWrapper> </StyledBoardCardWrapper>

View File

@ -11,6 +11,7 @@ export {
IconArrowRight, IconArrowRight,
IconArrowUp, IconArrowUp,
IconArrowUpRight, IconArrowUpRight,
IconBaselineDensitySmall,
IconBell, IconBell,
IconBox, IconBox,
IconBrandGithub, IconBrandGithub,

View File

@ -1,10 +1,13 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
export type ToggleSize = 'small' | 'medium';
type ContainerProps = { type ContainerProps = {
isOn: boolean; isOn: boolean;
color?: string; color?: string;
toggleSize: ToggleSize;
}; };
const StyledContainer = styled.div<ContainerProps>` const StyledContainer = styled.div<ContainerProps>`
@ -14,32 +17,40 @@ const StyledContainer = styled.div<ContainerProps>`
border-radius: 10px; border-radius: 10px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
height: 20px; height: ${({ toggleSize }) => (toggleSize === 'small' ? 16 : 20)}px;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
width: 32px; width: ${({ toggleSize }) => (toggleSize === 'small' ? 24 : 32)}px;
`; `;
const StyledCircle = styled(motion.div)` const StyledCircle = styled(motion.div)<{
toggleSize: ToggleSize;
}>`
background-color: ${({ theme }) => theme.background.primary}; background-color: ${({ theme }) => theme.background.primary};
border-radius: 50%; border-radius: 50%;
height: 16px; height: ${({ toggleSize }) => (toggleSize === 'small' ? 12 : 16)}px;
width: 16px; width: ${({ toggleSize }) => (toggleSize === 'small' ? 12 : 16)}px;
`; `;
const circleVariants = {
on: { x: 14 },
off: { x: 2 },
};
export type ToggleProps = { export type ToggleProps = {
value?: boolean; value?: boolean;
onChange?: (value: boolean) => void; onChange?: (value: boolean) => void;
color?: string; color?: string;
toggleSize?: ToggleSize;
}; };
export const Toggle = ({ value, onChange, color }: ToggleProps) => { export const Toggle = ({
value,
onChange,
color,
toggleSize = 'medium',
}: ToggleProps) => {
const [isOn, setIsOn] = useState(value ?? false); const [isOn, setIsOn] = useState(value ?? false);
const circleVariants = {
on: { x: toggleSize === 'small' ? 10 : 14 },
off: { x: 2 },
};
const handleChange = () => { const handleChange = () => {
setIsOn(!isOn); setIsOn(!isOn);
@ -56,8 +67,17 @@ export const Toggle = ({ value, onChange, color }: ToggleProps) => {
}, [value]); }, [value]);
return ( return (
<StyledContainer onClick={handleChange} isOn={isOn} color={color}> <StyledContainer
<StyledCircle animate={isOn ? 'on' : 'off'} variants={circleVariants} /> onClick={handleChange}
isOn={isOn}
color={color}
toggleSize={toggleSize}
>
<StyledCircle
animate={isOn ? 'on' : 'off'}
variants={circleVariants}
toggleSize={toggleSize}
/>
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -35,6 +35,7 @@ export const BoardOptionsDropdown = ({
} }
dropdownHotkeyScope={customHotkeyScope} dropdownHotkeyScope={customHotkeyScope}
onClickOutside={resetViewEditMode} onClickOutside={resetViewEditMode}
dropdownMenuWidth={170}
/> />
</DropdownScope> </DropdownScope>
); );

View File

@ -15,6 +15,7 @@ import { currentViewScopedSelector } from '@/ui/data/view-bar/states/selectors/c
import { viewsByIdScopedSelector } from '@/ui/data/view-bar/states/selectors/viewsByIdScopedSelector'; import { viewsByIdScopedSelector } from '@/ui/data/view-bar/states/selectors/viewsByIdScopedSelector';
import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState'; import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState';
import { import {
IconBaselineDensitySmall,
IconChevronLeft, IconChevronLeft,
IconLayoutKanban, IconLayoutKanban,
IconPlus, IconPlus,
@ -28,6 +29,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemNavigate'; import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemNavigate';
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
import { ThemeColor } from '@/ui/theme/constants/colors'; import { ThemeColor } from '@/ui/theme/constants/colors';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@ -37,6 +39,7 @@ import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilSco
import { useBoardCardFields } from '../hooks/useBoardCardFields'; import { useBoardCardFields } from '../hooks/useBoardCardFields';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState'; import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { boardColumnsState } from '../states/boardColumnsState'; import { boardColumnsState } from '../states/boardColumnsState';
import { isCompactViewEnabledState } from '../states/isCompactViewEnabledState';
import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState'; import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
import { hiddenBoardCardFieldsScopedSelector } from '../states/selectors/hiddenBoardCardFieldsScopedSelector'; import { hiddenBoardCardFieldsScopedSelector } from '../states/selectors/hiddenBoardCardFieldsScopedSelector';
import { visibleBoardCardFieldsScopedSelector } from '../states/selectors/visibleBoardCardFieldsScopedSelector'; import { visibleBoardCardFieldsScopedSelector } from '../states/selectors/visibleBoardCardFieldsScopedSelector';
@ -72,6 +75,9 @@ export const BoardOptionsDropdownContent = ({
>(); >();
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState); const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
const [isCompactViewEnabled, setIsCompactViewEnabled] = useRecoilState(
isCompactViewEnabledState,
);
const hiddenBoardCardFields = useRecoilScopedValue( const hiddenBoardCardFields = useRecoilScopedValue(
hiddenBoardCardFieldsScopedSelector, hiddenBoardCardFieldsScopedSelector,
@ -194,6 +200,16 @@ export const BoardOptionsDropdownContent = ({
text="Stages" text="Stages"
/> />
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconBaselineDensitySmall}
onToggleChange={setIsCompactViewEnabled}
toggled={isCompactViewEnabled}
text="Compact view"
toggleSize="small"
/>
</DropdownMenuItemsContainer>
</> </>
)} )}
{currentMenu === 'stages' && ( {currentMenu === 'stages' && (

View File

@ -0,0 +1,6 @@
import { atomFamily } from 'recoil';
export const isCardInCompactViewState = atomFamily<boolean, string>({
key: 'isCardInCompactViewState',
default: true,
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const isCompactViewEnabledState = atom<boolean>({
key: 'isCompactViewEnabledState',
default: false,
});

View File

@ -1,15 +1,19 @@
import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { Toggle } from '@/ui/input/components/Toggle'; import { Toggle, ToggleSize } from '@/ui/input/components/Toggle';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent'; import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase'; import {
StyledMenuItemBase,
StyledMenuItemRightContent,
} from '../internals/components/StyledMenuItemBase';
type MenuItemToggleProps = { type MenuItemToggleProps = {
LeftIcon?: IconComponent; LeftIcon?: IconComponent;
toggled: boolean; toggled: boolean;
text: string; text: string;
className: string; className?: string;
onToggleChange?: (toggled: boolean) => void; onToggleChange?: (toggled: boolean) => void;
toggleSize?: ToggleSize;
}; };
export const MenuItemToggle = ({ export const MenuItemToggle = ({
@ -18,6 +22,7 @@ export const MenuItemToggle = ({
toggled, toggled,
className, className,
onToggleChange, onToggleChange,
toggleSize,
}: MenuItemToggleProps) => { }: MenuItemToggleProps) => {
const handleOnClick = () => { const handleOnClick = () => {
onToggleChange?.(!toggled); onToggleChange?.(!toggled);
@ -26,7 +31,13 @@ export const MenuItemToggle = ({
return ( return (
<StyledMenuItemBase className={className} onClick={handleOnClick}> <StyledMenuItemBase className={className} onClick={handleOnClick}>
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} /> <MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
<Toggle value={toggled} onChange={onToggleChange} /> <StyledMenuItemRightContent>
<Toggle
value={toggled}
onChange={onToggleChange}
toggleSize={toggleSize}
/>
</StyledMenuItemRightContent>
</StyledMenuItemBase> </StyledMenuItemBase>
); );
}; };

View File

@ -0,0 +1,45 @@
import { useTheme } from '@emotion/react';
import { AnimatePresence, motion } from 'framer-motion';
import { AnimationDuration } from '@/ui/theme/constants/animation';
type AnimatedEaseInOutProps = {
isOpen: boolean;
children: React.ReactNode;
duration?: AnimationDuration;
marginBottom?: string;
marginTop?: string;
};
export const AnimatedEaseInOut = ({
children,
isOpen,
marginBottom,
marginTop,
duration = 'normal',
}: AnimatedEaseInOutProps) => {
const theme = useTheme();
return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{
marginBottom: marginBottom ?? 0,
marginTop: marginTop ?? 0,
height: 0,
opacity: 0,
}}
animate={{ height: 100, opacity: 1 }}
exit={{ height: 0, opacity: 0, marginBottom: 0, marginTop: 0 }}
transition={{
duration: theme.animation.duration[duration],
ease: 'easeInOut',
}}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
};