1761 objects settings add a cover image (#2096)

* add image

* overflow hidden

* add close button

* add animation to cover image

* use cookie to store user preference

* refactor to have a reusable component called AnimatedFadeOut

* corrected close button position

* modified according to comments
This commit is contained in:
bosiraphael
2023-10-18 13:02:44 +02:00
committed by GitHub
parent a1a2309140
commit f95c9d3df8
6 changed files with 168 additions and 53 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@ -0,0 +1,58 @@
import { useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconX } from '@/ui/display/icon';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { AnimatedFadeOut } from '@/ui/utilities/animation/components/AnimatedFadeOut';
import { cookieStorage } from '~/utils/cookie-storage';
import CoverImage from '../assets/build-your-business-logic.jpg';
const StyledCoverImageContainer = styled.div`
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.md};
overflow: hidden;
position: relative;
width: 100%;
`;
const StyledCoverImage = styled.img`
height: 100%;
object-fit: cover;
width: 100%;
`;
const StyledLighIconButton = styled(LightIconButton)`
position: absolute;
right: ${({ theme }) => theme.spacing(1)};
top: ${({ theme }) => theme.spacing(1)};
`;
export const SettingsObjectCoverImage = () => {
const theme = useTheme();
const [cookieState, setCookieState] = useState(
cookieStorage.getItem('settings-object-cover-image'),
);
return (
<AnimatedFadeOut
isOpen={cookieState !== 'closed'}
marginBottom={theme.spacing(8)}
>
<StyledCoverImageContainer>
<StyledCoverImage src={CoverImage} alt="Build your business logic" />
<StyledLighIconButton
Icon={IconX}
accent="tertiary"
size="small"
onClick={() => {
cookieStorage.setItem('settings-object-cover-image', 'closed');
setCookieState('closed');
}}
/>
</StyledCoverImageContainer>
</AnimatedFadeOut>
);
};

View File

@ -5,3 +5,5 @@ export const animation = {
normal: 0.3, normal: 0.3,
}, },
}; };
export type AnimationDuration = 'instant' | 'fast' | 'normal';

View File

@ -1,19 +1,26 @@
import { useTheme } from '@emotion/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { AnimationDuration } from '@/ui/theme/constants/animation';
type AnimatedEaseInProps = Omit< type AnimatedEaseInProps = Omit<
React.ComponentProps<typeof motion.div>, React.ComponentProps<typeof motion.div>,
'initial' | 'animated' | 'transition' 'initial' | 'animated' | 'transition'
> & { > & {
duration?: number; duration?: AnimationDuration;
}; };
export const AnimatedEaseIn = ({ export const AnimatedEaseIn = ({
children, children,
duration = 0.3, duration = 'normal',
}: AnimatedEaseInProps) => { }: AnimatedEaseInProps) => {
const theme = useTheme();
const initial = { opacity: 0 }; const initial = { opacity: 0 };
const animate = { opacity: 1 }; const animate = { opacity: 1 };
const transition = { ease: 'linear', duration }; const transition = {
ease: 'linear',
duration: theme.animation.duration[duration],
};
return ( return (
<motion.div initial={initial} animate={animate} transition={transition}> <motion.div initial={initial} animate={animate} transition={transition}>

View File

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

View File

@ -8,6 +8,7 @@ import {
activeObjectItems, activeObjectItems,
disabledObjectItems, disabledObjectItems,
} from '@/settings/data-model/constants/mockObjects'; } from '@/settings/data-model/constants/mockObjects';
import { SettingsObjectCoverImage } from '@/settings/data-model/objects/SettingsObjectCoverImage';
import { import {
IconChevronRight, IconChevronRight,
IconDotsVertical, IconDotsVertical,
@ -76,52 +77,28 @@ export const SettingsObjects = () => {
}} }}
/> />
</SettingsHeaderContainer> </SettingsHeaderContainer>
<Section> <div>
<H2Title title="Existing objects" /> <SettingsObjectCoverImage />
<Table> <Section>
<StyledTableRow> <H2Title title="Existing objects" />
<TableHeader>Name</TableHeader> <Table>
<TableHeader>Type</TableHeader> <StyledTableRow>
<TableHeader align="right">Fields</TableHeader> <TableHeader>Name</TableHeader>
<TableHeader align="right">Instances</TableHeader> <TableHeader>Type</TableHeader>
<TableHeader></TableHeader> <TableHeader align="right">Fields</TableHeader>
</StyledTableRow> <TableHeader align="right">Instances</TableHeader>
<TableSection title="Active"> <TableHeader></TableHeader>
{activeObjectItems.map((objectItem) => ( </StyledTableRow>
<StyledTableRow <TableSection title="Active">
key={objectItem.name} {activeObjectItems.map((objectItem) => (
onClick={() => <StyledTableRow
navigate( key={objectItem.name}
`/settings/objects/${objectItem.name.toLowerCase()}`, onClick={() =>
) navigate(
} `/settings/objects/${objectItem.name.toLowerCase()}`,
> )
<StyledNameTableCell> }
<objectItem.Icon size={theme.icon.size.md} /> >
{objectItem.name}
</StyledNameTableCell>
<TableCell>
{objectItem.type === 'standard' ? (
<StyledTag color="blue" text="Standard" />
) : (
<StyledTag color="orange" text="Custom" />
)}
</TableCell>
<TableCell align="right">{objectItem.fields}</TableCell>
<TableCell align="right">{objectItem.instances}</TableCell>
<StyledIconTableCell>
<StyledIconChevronRight
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconTableCell>
</StyledTableRow>
))}
</TableSection>
{!!disabledObjectItems.length && (
<TableSection title="Disabled">
{disabledObjectItems.map((objectItem) => (
<StyledTableRow key={objectItem.name}>
<StyledNameTableCell> <StyledNameTableCell>
<objectItem.Icon size={theme.icon.size.md} /> <objectItem.Icon size={theme.icon.size.md} />
{objectItem.name} {objectItem.name}
@ -136,7 +113,7 @@ export const SettingsObjects = () => {
<TableCell align="right">{objectItem.fields}</TableCell> <TableCell align="right">{objectItem.fields}</TableCell>
<TableCell align="right">{objectItem.instances}</TableCell> <TableCell align="right">{objectItem.instances}</TableCell>
<StyledIconTableCell> <StyledIconTableCell>
<StyledIconDotsVertical <StyledIconChevronRight
size={theme.icon.size.md} size={theme.icon.size.md}
stroke={theme.icon.stroke.sm} stroke={theme.icon.stroke.sm}
/> />
@ -144,9 +121,38 @@ export const SettingsObjects = () => {
</StyledTableRow> </StyledTableRow>
))} ))}
</TableSection> </TableSection>
)} {!!disabledObjectItems.length && (
</Table> <TableSection title="Disabled">
</Section> {disabledObjectItems.map((objectItem) => (
<StyledTableRow key={objectItem.name}>
<StyledNameTableCell>
<objectItem.Icon size={theme.icon.size.md} />
{objectItem.name}
</StyledNameTableCell>
<TableCell>
{objectItem.type === 'standard' ? (
<StyledTag color="blue" text="Standard" />
) : (
<StyledTag color="orange" text="Custom" />
)}
</TableCell>
<TableCell align="right">{objectItem.fields}</TableCell>
<TableCell align="right">
{objectItem.instances}
</TableCell>
<StyledIconTableCell>
<StyledIconDotsVertical
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
</StyledIconTableCell>
</StyledTableRow>
))}
</TableSection>
)}
</Table>
</Section>
</div>
</SettingsPageContainer> </SettingsPageContainer>
</SubMenuTopBarContainer> </SubMenuTopBarContainer>
); );