Skip to content

Commit 895ad35

Browse files
committed
feat(flipcard): container query + accept breakpoint
1 parent f9c5d62 commit 895ad35

File tree

7 files changed

+163
-130
lines changed

7 files changed

+163
-130
lines changed

src/pages/Campaign/useWidgets/Experience/widgets/Overview/Sentiment.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const Sentiment = ({
2121
if (isLoading || isError || !sentiments) return null;
2222

2323
return (
24-
<FlipCard className="sentiment-widget">
24+
<FlipCard className="sentiment-widget" activateSwitchFromBreakpoint={500}>
2525
<FlipCard.Header>
2626
{t('__CAMPAIGN_EXP_WIDGET_SENTIMENT_HEADER')}
2727
</FlipCard.Header>
Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,103 @@
11
import styled from 'styled-components';
2-
import { CSSTransition, SwitchTransition } from 'react-transition-group';
3-
import { useFlipCardContext } from './context/FlipCardContext';
4-
import { FlipCardBodyProps } from './types';
2+
import { useFlipCardContext } from './context';
3+
4+
interface FlipCardBodyProps {
5+
front: React.ReactNode;
6+
back?: React.ReactNode;
7+
}
58

69
const durationMilliseconds = 125;
710

8-
const FaceContent = styled.div<{ isVisible?: boolean }>`
11+
const FaceContent = styled.div`
912
display: flex;
1013
flex-direction: column;
1114
align-items: center;
1215
justify-content: center;
1316
margin-bottom: ${({ theme }) => theme.space.xxs};
1417
margin-top: ${({ theme }) => theme.space.xxs};
15-
transition: opacity ${durationMilliseconds}ms;
1618
`;
1719

18-
const WidgetCard = styled.div`
19-
${FaceContent} {
20-
&.flip-card-enter {
21-
opacity: 0;
20+
const WidgetCard = styled.div<{ breakpoint: number }>`
21+
container-type: inline-size;
22+
23+
.flipcard-face {
24+
overflow: hidden;
25+
transform-style: preserve-3d;
26+
animation-duration: ${durationMilliseconds}ms;
27+
animation-timing-function: linear;
28+
animation-fill-mode: forwards;
29+
}
30+
.flipcard-face-back {
31+
animation-name: show;
32+
}
33+
.flipcard-face-front {
34+
animation-name: hide;
35+
}
36+
37+
@container (min-width: ${(p) => p.breakpoint}px) {
38+
.flipcard-face-front {
39+
z-index: 2;
2240
}
23-
&.flip-card-enter-active {
24-
opacity: 1;
41+
.flipcard-face-back {
42+
z-index: 1;
43+
}
44+
&.flipcard.front {
45+
.flipcard-face-front {
46+
animation-name: show;
47+
}
48+
.flipcard-face-back {
49+
animation-name: hide;
50+
}
51+
}
52+
&.flipcard.back {
53+
.flipcard-face-front {
54+
animation-name: hide;
55+
}
56+
.flipcard-face-back {
57+
animation-name: show;
58+
}
2559
}
26-
&.flip-card-exit {
60+
}
61+
62+
@keyframes hide {
63+
0% {
2764
opacity: 1;
65+
display: block;
66+
}
67+
99% {
68+
display: block;
2869
}
29-
&.flip-card-exit-active {
70+
100% {
71+
display: none;
3072
opacity: 0;
3173
}
3274
}
75+
@keyframes show {
76+
0% {
77+
opacity: 0;
78+
display: none;
79+
}
80+
1% {
81+
display: block;
82+
}
83+
100% {
84+
display: block;
85+
opacity: 1;
86+
}
87+
}
3388
`;
3489

3590
export const FlipCardBody = ({ front, back }: FlipCardBodyProps) => {
36-
const { visibleFace } = useFlipCardContext();
91+
const { visibleFace, breakpoint } = useFlipCardContext();
3792

3893
return (
39-
<WidgetCard className={`face ${visibleFace}`}>
40-
<SwitchTransition>
41-
<CSSTransition
42-
key={visibleFace}
43-
timeout={durationMilliseconds}
44-
classNames="flip-card"
45-
>
46-
<FaceContent>{visibleFace === 'front' ? front : back}</FaceContent>
47-
</CSSTransition>
48-
</SwitchTransition>
94+
<WidgetCard breakpoint={breakpoint} className={`flipcard ${visibleFace}`}>
95+
<FaceContent className="flipcard-face flipcard-face-front">
96+
{front}
97+
</FaceContent>
98+
<FaceContent className="flipcard-face flipcard-face-back">
99+
{back}
100+
</FaceContent>
49101
</WidgetCard>
50102
);
51103
};

src/pages/Campaign/widgetCards/FlipCard/FlipCardHeader.tsx

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,51 @@
11
import { IconButton } from '@appquality/unguess-design-system';
22
import styled from 'styled-components';
3-
import { appTheme } from 'src/app/theme';
43
import { ReactComponent as LineGraphIconFill } from 'src/assets/icons/line-graph-fill.svg';
54
import { ReactComponent as ListBulletIconFill } from 'src/assets/icons/list-bullet-fill.svg';
5+
import { ReactNode } from 'react';
66
import { WidgetCardHeader } from '../common/WidgetCardHeader';
7-
import { useFlipCardContext } from './context/FlipCardContext';
8-
import { FlipCardHeaderProps } from './types';
7+
import { useFlipCardContext } from './context';
98

109
const FlipButton = styled(IconButton)`
1110
margin-left: ${(p) => p.theme.space.xs};
1211
`;
1312

1413
export const FlipButtonContainer = styled.div`
15-
display: flex;
14+
display: none;
15+
@container (min-width: 500px) {
16+
display: flex;
17+
}
1618
`;
1719

18-
export const FlipCardHeader = ({ children, hasBack }: FlipCardHeaderProps) => {
19-
const { visibleFace, setVisibleFace, width } = useFlipCardContext();
20-
const breakpointMd = parseInt(appTheme.breakpoints.lg, 10);
20+
export const FlipCardHeader = ({ children }: { children: ReactNode }) => {
21+
const { visibleFace, setVisibleFace } = useFlipCardContext();
2122

2223
return (
2324
<WidgetCardHeader
2425
title={children}
2526
action={
26-
hasBack === false || width < breakpointMd ? null : (
27-
<FlipButtonContainer>
28-
<FlipButton
29-
className="flip-card-button-chart"
30-
isPrimary={visibleFace === 'front'}
31-
size="small"
32-
onClick={() => setVisibleFace('front')}
33-
>
34-
<LineGraphIconFill
35-
color={`${visibleFace === 'front' && 'white'}`}
36-
/>
37-
</FlipButton>
38-
<FlipButton
39-
className="flip-card-button-list"
40-
size="small"
41-
isPrimary={visibleFace === 'back'}
42-
onClick={() => setVisibleFace('back')}
43-
>
44-
<ListBulletIconFill
45-
color={`${visibleFace === 'back' && 'white'}`}
46-
/>
47-
</FlipButton>
48-
</FlipButtonContainer>
49-
)
27+
<FlipButtonContainer>
28+
<FlipButton
29+
className="flip-card-button-chart"
30+
isPrimary={visibleFace === 'front'}
31+
size="small"
32+
onClick={() => setVisibleFace('front')}
33+
>
34+
<LineGraphIconFill
35+
color={`${visibleFace === 'front' && 'white'}`}
36+
/>
37+
</FlipButton>
38+
<FlipButton
39+
className="flip-card-button-list"
40+
size="small"
41+
isPrimary={visibleFace === 'back'}
42+
onClick={() => setVisibleFace('back')}
43+
>
44+
<ListBulletIconFill
45+
color={`${visibleFace === 'back' && 'white'}`}
46+
/>
47+
</FlipButton>
48+
</FlipButtonContainer>
5049
}
5150
/>
5251
);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { createContext, useContext, useMemo, useState } from 'react';
2+
import { FaceType } from './types';
3+
4+
type FlipCardContextType = {
5+
visibleFace: FaceType;
6+
setVisibleFace: (face: FaceType) => void;
7+
breakpoint: number;
8+
};
9+
10+
export const Context = createContext<FlipCardContextType | null>(null);
11+
12+
export const ContextProvider = ({
13+
children,
14+
breakpoint,
15+
}: {
16+
children: React.ReactNode;
17+
breakpoint: number;
18+
}) => {
19+
const [visibleFace, setVisibleFace] = useState<FaceType>('front');
20+
21+
const flipCardContextValue = useMemo(
22+
() => ({
23+
visibleFace,
24+
setVisibleFace,
25+
breakpoint,
26+
}),
27+
[visibleFace, setVisibleFace]
28+
);
29+
30+
return (
31+
<Context.Provider value={flipCardContextValue}>{children}</Context.Provider>
32+
);
33+
};
34+
35+
export const useFlipCardContext = () => {
36+
const context = useContext(Context);
37+
38+
if (!context)
39+
throw new Error('Provider not found for FlipCardContextProvider');
40+
41+
return context; // Now we can use the context in the component, SAFELY.
42+
};

src/pages/Campaign/widgetCards/FlipCard/context/FlipCardContext.tsx

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/pages/Campaign/widgetCards/FlipCard/index.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,30 @@ import { WidgetSpecialCard } from '../common/StyledSpecialCard';
33
import { WidgetCardFooter } from '../common/WidgetCardFooter';
44
import { FlipCardBody } from './FlipCardBody';
55
import { FlipCardHeader } from './FlipCardHeader';
6-
import { FlipCardContextProvider } from './context/FlipCardContext';
6+
import { ContextProvider } from './context';
77

8-
const FlipCardContainer = styled(WidgetSpecialCard)<{ height?: string }>`
8+
const StyledSpecialCard = styled(WidgetSpecialCard)<{ height?: string }>`
99
height: ${({ height }) => height || 'auto'};
10+
container-type: inline-size;
1011
`;
1112

1213
interface FlipCardProps extends React.HTMLAttributes<HTMLDivElement> {
1314
children?: React.ReactNode;
1415
height?: string;
16+
activateSwitchFromBreakpoint?: number;
1517
}
1618

17-
const FlipCard = ({ children, height, ...props }: FlipCardProps) => (
18-
<FlipCardContextProvider>
19-
<FlipCardContainer {...props} height={height}>
19+
const FlipCard = ({
20+
children,
21+
height,
22+
activateSwitchFromBreakpoint,
23+
...props
24+
}: FlipCardProps) => (
25+
<ContextProvider breakpoint={activateSwitchFromBreakpoint || 500}>
26+
<StyledSpecialCard {...props} height={height}>
2027
{children}
21-
</FlipCardContainer>
22-
</FlipCardContextProvider>
28+
</StyledSpecialCard>
29+
</ContextProvider>
2330
);
2431

2532
FlipCard.Header = FlipCardHeader;
Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1 @@
1-
export type FlipCardContextType = {
2-
visibleFace: FaceType;
3-
setVisibleFace: (face: FaceType) => void;
4-
width: number;
5-
};
6-
7-
export interface FlipCardHeaderProps {
8-
children: React.ReactNode;
9-
setVisibleFace?: (face: FaceType) => void;
10-
visibleFace?: FaceType;
11-
hasBack?: boolean;
12-
}
13-
141
export type FaceType = 'front' | 'back';
15-
16-
export interface FlipCardBodyProps {
17-
front: React.ReactNode;
18-
back?: React.ReactNode;
19-
height?: string;
20-
visibleFace?: FaceType;
21-
}

0 commit comments

Comments
 (0)