1
1
import * as React from 'react' ;
2
- import {
3
- makeStyles ,
4
- tokens ,
5
- Button ,
6
- Card ,
7
- CardHeader ,
8
- CardPreview ,
9
- Body1 ,
10
- Title3 ,
11
- Caption1 ,
12
- } from '@fluentui/react-components' ;
2
+ import { makeStyles , tokens , Button , Card , Title3 , Body2 , Caption1 , motionTokens } from '@fluentui/react-components' ;
13
3
import { Rotate } from '@fluentui/react-motion-components-preview' ;
14
- import { Star20Filled , ThumbLike20Filled , Eye20Filled } from '@fluentui/react-icons ' ;
4
+ import { RotateParams } from '../../../library/src/components/Rotate/rotate-types ' ;
15
5
16
6
const useClasses = makeStyles ( {
17
7
container : {
18
8
display : 'flex' ,
19
9
flexDirection : 'column' ,
20
10
gap : '20px' ,
21
11
padding : '20px' ,
22
- maxWidth : '900px' ,
12
+ maxWidth : '1000px' ,
13
+ perspective : '500px' ,
23
14
} ,
24
15
controls : {
25
16
display : 'flex' ,
26
17
gap : '10px' ,
27
18
alignItems : 'center' ,
19
+ flexWrap : 'wrap' ,
28
20
marginBottom : '20px' ,
29
21
} ,
30
- cardGrid : {
22
+ patternsGrid : {
31
23
display : 'grid' ,
32
- gridTemplateColumns : 'repeat(auto-fit, minmax(280px , 1fr))' ,
24
+ gridTemplateColumns : 'repeat(auto-fit, minmax(200px , 1fr))' ,
33
25
gap : '20px' ,
34
- perspective : '1000px ' ,
26
+ perspective : '800px ' ,
35
27
} ,
36
- cardContainer : {
37
- position : 'relative' ,
38
- height : '200px' ,
39
- } ,
40
- card : {
41
- height : '100%' ,
28
+ patternCard : {
29
+ height : '140px' ,
30
+ display : 'flex' ,
31
+ flexDirection : 'column' ,
32
+ alignItems : 'center' ,
33
+ justifyContent : 'center' ,
34
+ gap : tokens . spacingVerticalS ,
42
35
cursor : 'pointer' ,
43
- transition : 'transform 0.2s' ,
36
+ transition : 'transform 0.2s ease ' ,
44
37
'&:hover' : {
45
38
transform : 'translateY(-2px)' ,
46
- boxShadow : tokens . shadow28 ,
39
+ boxShadow : tokens . shadow16 ,
47
40
} ,
48
41
} ,
49
- cardPreview : {
50
- height : '120px' ,
51
- display : 'flex' ,
52
- alignItems : 'center' ,
53
- justifyContent : 'center' ,
54
- fontSize : tokens . fontSizeBase600 ,
55
- position : 'relative' ,
56
- } ,
57
- cardBack : {
58
- backgroundColor : tokens . colorNeutralBackground1 ,
59
- border : `1px solid ${ tokens . colorNeutralStroke2 } ` ,
60
- borderRadius : tokens . borderRadiusMedium ,
61
- padding : tokens . spacingVerticalM ,
62
- height : '100%' ,
63
- display : 'flex' ,
64
- flexDirection : 'column' ,
65
- justifyContent : 'center' ,
66
- alignItems : 'center' ,
67
- gap : tokens . spacingVerticalS ,
42
+ patternTitle : {
43
+ color : tokens . colorNeutralForeground1 ,
68
44
} ,
69
- stats : {
70
- display : 'flex' ,
71
- gap : tokens . spacingHorizontalS ,
72
- alignItems : 'center' ,
45
+ patternDescription : {
46
+ color : tokens . colorNeutralForeground2 ,
47
+ textAlign : 'center' ,
73
48
} ,
74
- statItem : {
49
+ demoIcon : {
50
+ width : '48px' ,
51
+ height : '48px' ,
52
+ borderRadius : tokens . borderRadiusMedium ,
75
53
display : 'flex' ,
76
54
alignItems : 'center' ,
77
- gap : tokens . spacingHorizontalXS ,
55
+ justifyContent : 'center' ,
56
+ fontSize : '24px' ,
57
+ fontWeight : 'bold' ,
58
+ color : tokens . colorNeutralForegroundOnBrand ,
78
59
} ,
79
60
} ) ;
80
61
81
- const cardData = [
62
+ const curveSpringRelaxed = `linear(0.000 0.000%, 0.06073 1.000%, 0.1215 2.000%, 0.1822 3.000%, 0.2429 4.000%, 0.3036 5.000%, 0.3644 6.000%, 0.4251 7.000%, 0.4858 8.000%, 0.5465 9.000%, 0.6073 10.00%, 0.6680 11.00%, 0.7287 12.00%, 0.7895 13.00%, 0.8502 14.00%, 0.9109 15.00%, 0.9716 16.00%, 1.031 17.00%, 1.085 18.00%, 1.131 19.00%, 1.168 20.00%, 1.198 21.00%, 1.220 22.00%, 1.234 23.00%, 1.241 24.00%, 1.242 25.00%, 1.236 26.00%, 1.226 27.00%, 1.211 28.00%, 1.192 29.00%, 1.171 30.00%, 1.148 31.00%, 1.124 32.00%, 1.099 33.00%, 1.074 34.00%, 1.050 35.00%, 1.028 36.00%, 1.007 37.00%, 0.9880 38.00%, 0.9714 39.00%, 0.9572 40.00%, 0.9455 41.00%, 0.9364 42.00%, 0.9298 43.00%, 0.9255 44.00%, 0.9235 45.00%, 0.9236 46.00%, 0.9255 47.00%, 0.9291 48.00%, 0.9339 49.00%, 0.9399 50.00%, 0.9467 51.00%, 0.9541 52.00%, 0.9618 53.00%, 0.9697 54.00%, 0.9774 55.00%, 0.9849 56.00%, 0.9920 57.00%, 0.9986 58.00%, 1.004 59.00%, 1.010 60.00%, 1.014 61.00%, 1.018 62.00%, 1.020 63.00%, 1.022 64.00%, 1.024 65.00%, 1.024 66.00%, 1.024 67.00%, 1.023 68.00%, 1.022 69.00%, 1.021 70.00%, 1.019 71.00%, 1.017 72.00%, 1.014 73.00%, 1.012 74.00%, 1.009 75.00%, 1.007 76.00%, 1.004 77.00%, 1.002 78.00%, 1.000 79.00%, 0.9984 80.00%, 0.9968 81.00%, 0.9954 82.00%, 0.9943 83.00%, 0.9935 84.00%, 0.9929 85.00%, 0.9925 86.00%, 0.9923 87.00%, 0.9924 88.00%, 0.9926 89.00%, 0.9930 90.00%, 0.9935 91.00%, 0.9941 92.00%, 0.9948 93.00%, 0.9956 94.00%, 0.9964 95.00%, 0.9972 96.00%, 0.9979 97.00%, 0.9987 98.00%, 0.9994 99.00%, 1.000 100.0%)` ;
63
+
64
+ type RotatePattern = {
65
+ id : string ;
66
+ name : string ;
67
+ description : string ;
68
+ icon : string ;
69
+ color : string ;
70
+ axis : Required < RotateParams [ 'axis' ] > ;
71
+ angle : Required < RotateParams [ 'angle' ] > ;
72
+ duration : Required < RotateParams [ 'duration' ] > ;
73
+ easing : Required < RotateParams [ 'easing' ] > ;
74
+ exitEasing : Required < RotateParams [ 'easing' ] > ;
75
+ exitDuration : Required < RotateParams [ 'duration' ] > ;
76
+ } ;
77
+
78
+ const patterns : RotatePattern [ ] = [
82
79
{
83
- id : 1 ,
84
- title : 'Design System' ,
85
- subtitle : 'Fluent UI Components' ,
86
- color : tokens . colorPaletteBlueBackground2 ,
87
- stats : { likes : 324 , views : 1205 , rating : 4.8 } ,
88
- description : 'A comprehensive design system for modern applications' ,
80
+ id : 'flip-horizontal' ,
81
+ name : 'Horizontal Flip' ,
82
+ description : 'Y-axis rotation' ,
83
+ icon : '↔️' ,
84
+ color : tokens . colorPaletteBlueForeground2 ,
85
+ axis : 'y' ,
86
+ angle : 180 ,
87
+ easing : curveSpringRelaxed ,
88
+ exitEasing : motionTokens . curveDecelerateMid ,
89
+ duration : 2000 ,
90
+ exitDuration : motionTokens . durationUltraSlow ,
89
91
} ,
90
92
{
91
- id : 2 ,
92
- title : 'React Motion' ,
93
- subtitle : 'Animation Library' ,
94
- color : tokens . colorPaletteGreenBackground2 ,
95
- stats : { likes : 156 , views : 892 , rating : 4.6 } ,
96
- description : 'Smooth and performant animations for React components' ,
93
+ id : 'flip-vertical' ,
94
+ name : 'Vertical Flip' ,
95
+ description : 'X-axis rotation' ,
96
+ icon : '↕️' ,
97
+ color : tokens . colorPaletteGreenForeground2 ,
98
+ axis : 'x' ,
99
+ angle : 180 ,
100
+ easing : curveSpringRelaxed ,
101
+ exitEasing : motionTokens . curveDecelerateMid ,
102
+ duration : 2000 ,
103
+ exitDuration : motionTokens . durationUltraSlow ,
97
104
} ,
98
105
{
99
- id : 3 ,
100
- title : 'TypeScript' ,
101
- subtitle : 'Type Safety' ,
102
- color : tokens . colorPalettePurpleBackground2 ,
103
- stats : { likes : 445 , views : 2103 , rating : 4.9 } ,
104
- description : 'Strongly typed JavaScript for better development experience' ,
106
+ id : 'spin' ,
107
+ name : 'Spin' ,
108
+ description : 'Z-axis rotation' ,
109
+ icon : '🔄' ,
110
+ color : tokens . colorPaletteRedForeground2 ,
111
+ axis : 'z' ,
112
+ angle : 180 ,
113
+ easing : curveSpringRelaxed ,
114
+ exitEasing : motionTokens . curveDecelerateMid ,
115
+ duration : 2000 ,
116
+ exitDuration : motionTokens . durationUltraSlow ,
105
117
} ,
106
118
] ;
107
119
108
120
export const CardFlip = ( ) => {
109
121
const classes = useClasses ( ) ;
110
- const [ flippedCards , setFlippedCards ] = React . useState < Set < number > > ( new Set ( ) ) ;
111
- const [ autoFlip , setAutoFlip ] = React . useState ( false ) ;
122
+ const [ activePatterns , setActivePatterns ] = React . useState < Set < string > > ( new Set ( patterns . map ( p => p . id ) ) ) ;
112
123
113
- const toggleCard = ( cardId : number ) => {
114
- setFlippedCards ( prev => {
124
+ const togglePattern = ( patternId : string ) => {
125
+ setActivePatterns ( prev => {
115
126
const newSet = new Set ( prev ) ;
116
- if ( newSet . has ( cardId ) ) {
117
- newSet . delete ( cardId ) ;
127
+ if ( newSet . has ( patternId ) ) {
128
+ newSet . delete ( patternId ) ;
118
129
} else {
119
- newSet . add ( cardId ) ;
130
+ newSet . add ( patternId ) ;
120
131
}
121
132
return newSet ;
122
133
} ) ;
123
134
} ;
124
135
125
- const flipAllCards = ( ) => {
126
- if ( flippedCards . size === cardData . length ) {
127
- setFlippedCards ( new Set ( ) ) ;
136
+ const toggleAllPatterns = ( ) => {
137
+ if ( activePatterns . size === patterns . length ) {
138
+ // All are showing, so hide all
139
+ setActivePatterns ( new Set ( ) ) ;
128
140
} else {
129
- setFlippedCards ( new Set ( cardData . map ( card => card . id ) ) ) ;
141
+ // Some or none are showing, so show all
142
+ setActivePatterns ( new Set ( patterns . map ( p => p . id ) ) ) ;
130
143
}
131
144
} ;
132
145
133
- React . useEffect ( ( ) => {
134
- if ( ! autoFlip ) return ;
135
-
136
- const interval = setInterval ( ( ) => {
137
- const randomCard = cardData [ Math . floor ( Math . random ( ) * cardData . length ) ] ;
138
- toggleCard ( randomCard . id ) ;
139
- } , 1500 ) ;
140
-
141
- return ( ) => clearInterval ( interval ) ;
142
- } , [ autoFlip ] ) ;
146
+ const allPatternsVisible = activePatterns . size === patterns . length ;
143
147
144
148
return (
145
149
< div className = { classes . container } >
146
150
< div className = { classes . controls } >
147
- < Button appearance = "primary" onClick = { flipAllCards } >
148
- { flippedCards . size === cardData . length ? 'Flip All Back' : 'Flip All Cards' }
149
- </ Button >
150
- < Button appearance = { autoFlip ? 'primary' : 'secondary' } onClick = { ( ) => setAutoFlip ( ! autoFlip ) } >
151
- { autoFlip ? 'Stop Auto Flip' : 'Auto Flip' }
152
- </ Button >
151
+ < Button onClick = { toggleAllPatterns } > { allPatternsVisible ? 'Hide All' : 'Show All' } </ Button >
153
152
</ div >
154
153
155
- < div className = { classes . cardGrid } >
156
- { cardData . map ( card => (
157
- < div key = { card . id } className = { classes . cardContainer } >
158
- < Rotate
159
- visible = { ! flippedCards . has ( card . id ) }
160
- axis = "Y"
161
- angle = { 0 }
162
- exitAngle = { 180 }
163
- duration = { 600 }
164
- easing = "cubic-bezier(0.4, 0, 0.2, 1)"
165
- >
166
- < Card className = { classes . card } onClick = { ( ) => toggleCard ( card . id ) } >
167
- < CardPreview className = { classes . cardPreview } style = { { backgroundColor : card . color } } >
168
- < Title3 > { card . title } </ Title3 >
169
- </ CardPreview >
170
- < CardHeader header = { < Body1 > { card . title } </ Body1 > } description = { < Caption1 > { card . subtitle } </ Caption1 > } />
171
- </ Card >
172
- </ Rotate >
173
-
154
+ < div className = { classes . patternsGrid } >
155
+ { patterns . map ( pattern => (
156
+ < div key = { pattern . id } >
174
157
< Rotate
175
- visible = { flippedCards . has ( card . id ) }
176
- axis = "Y"
177
- angle = { - 180 }
178
- exitAngle = { 0 }
179
- duration = { 600 }
180
- easing = "cubic-bezier(0.4, 0, 0.2, 1)"
158
+ visible = { activePatterns . has ( pattern . id ) }
159
+ axis = { pattern . axis }
160
+ angle = { pattern . angle }
161
+ duration = { pattern . duration }
162
+ easing = { pattern . easing }
163
+ exitEasing = { pattern . exitEasing }
164
+ exitDuration = { pattern . exitDuration }
165
+ animateOpacity = { false }
181
166
>
182
- < div className = { classes . cardBack } onClick = { ( ) => toggleCard ( card . id ) } >
183
- < Title3 > { card . title } </ Title3 >
184
- < Body1 style = { { textAlign : 'center' , margin : tokens . spacingVerticalM } } > { card . description } </ Body1 >
185
- < div className = { classes . stats } >
186
- < div className = { classes . statItem } >
187
- < ThumbLike20Filled color = { tokens . colorPaletteRedForeground2 } />
188
- < Caption1 > { card . stats . likes } </ Caption1 >
189
- </ div >
190
- < div className = { classes . statItem } >
191
- < Eye20Filled color = { tokens . colorPaletteBlueForeground2 } />
192
- < Caption1 > { card . stats . views } </ Caption1 >
193
- </ div >
194
- < div className = { classes . statItem } >
195
- < Star20Filled color = { tokens . colorPaletteYellowForeground2 } />
196
- < Caption1 > { card . stats . rating } </ Caption1 >
197
- </ div >
167
+ < Card className = { classes . patternCard } onClick = { ( ) => togglePattern ( pattern . id ) } >
168
+ < div className = { classes . demoIcon } style = { { backgroundColor : pattern . color } } >
169
+ { pattern . icon }
198
170
</ div >
199
- </ div >
171
+ < Title3 className = { classes . patternTitle } > { pattern . name } </ Title3 >
172
+ < Caption1 className = { classes . patternDescription } > { pattern . description } </ Caption1 >
173
+ </ Card >
200
174
</ Rotate >
201
175
</ div >
202
176
) ) }
203
177
</ div >
178
+
179
+ < Body2 style = { { textAlign : 'center' , color : tokens . colorNeutralForeground2 , marginTop : '20px' } } >
180
+ Click any pattern to see its rotation effect, or use the controls above to see them all in sequence
181
+ </ Body2 >
204
182
</ div >
205
183
) ;
206
184
} ;
@@ -209,7 +187,7 @@ CardFlip.parameters = {
209
187
docs : {
210
188
description : {
211
189
story :
212
- 'Demonstrates card flip animations using Y -axis rotation. Click cards to flip them and reveal additional information on the back .' ,
190
+ 'A collection of common single -axis rotation patterns that you can use as starting points for your own animations. Each pattern demonstrates rotation around a specific axis (X, Y, or Z) with spring-relaxed easing .' ,
213
191
} ,
214
192
} ,
215
193
} ;
0 commit comments