Skip to content

Commit 9a4cb0a

Browse files
committed
feat(TopBar): animate the icon when reloading schema
1 parent ec6084b commit 9a4cb0a

File tree

3 files changed

+129
-36
lines changed

3 files changed

+129
-36
lines changed

packages/graphql-playground-react/src/components/Playground/GraphQLEditor.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export interface State {
123123
tracingSupported?: boolean
124124
queryVariablesActive: boolean
125125
endpointUnreachable: boolean
126+
isReloadingSchema: boolean
126127
}
127128

128129
export interface SimpleProps {
@@ -503,6 +504,7 @@ export class GraphQLEditor extends React.PureComponent<
503504
onClickShare={this.props.onClickShare}
504505
sharing={this.props.sharing}
505506
onReloadSchema={this.reloadSchema}
507+
isReloadingSchema={this.state.isReloadingSchema}
506508
fixedEndpoint={this.props.fixedEndpoint}
507509
endpointUnreachable={this.state.endpointUnreachable}
508510
/>
@@ -757,14 +759,19 @@ export class GraphQLEditor extends React.PureComponent<
757759
// Private methods
758760

759761
public reloadSchema = async () => {
760-
const result = await this.props.schemaFetcher.refetch(
761-
this.props.session.endpoint || this.props.endpoint,
762-
this.convertHeaders(this.props.session.headers),
763-
)
764-
if (result) {
765-
const { schema } = result
766-
this.setState({ schema })
767-
this.renewStacks(schema)
762+
try {
763+
this.setState({ isReloadingSchema: true })
764+
const result = await this.props.schemaFetcher.refetch(
765+
this.props.session.endpoint || this.props.endpoint,
766+
this.convertHeaders(this.props.session.headers),
767+
)
768+
if (result) {
769+
const { schema } = result
770+
this.setState({ schema, isReloadingSchema: false })
771+
this.renewStacks(schema)
772+
}
773+
} catch (e) {
774+
this.setState({ isReloadingSchema: false })
768775
}
769776
}
770777

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import * as React from 'react'
2+
import { styled, keyframes } from '../../../styled/index'
3+
import * as theme from 'styled-theming'
4+
import { StyledComponentClass } from 'styled-components'
5+
6+
export interface Props {
7+
isReloadingSchema: boolean
8+
onReloadSchema?: () => void
9+
}
10+
11+
export default class ReloadIcon extends React.Component<Props, {}> {
12+
render() {
13+
return (
14+
<Positioner onClick={this.props.onReloadSchema}>
15+
<Svg viewBox="0 0 20 20">
16+
<Circle
17+
cx="9.5"
18+
cy="10"
19+
r="6"
20+
strokeWidth="1.5"
21+
fill="none"
22+
strokeLinecap="round"
23+
isReloadingSchema={this.props.isReloadingSchema}
24+
/>
25+
<Icon
26+
d="M4.83 4.86a6.92 6.92 0 0 1 11.3 2.97l.41-1.23c.13-.4.56-.6.95-.47.4.13.6.56.47.95l-1.13 3.33a.76.76 0 0 1-.7.5.72.72 0 0 1-.43-.12l-2.88-1.92a.76.76 0 0 1-.2-1.04.75.75 0 0 1 1.03-.2l1.06.7A5.34 5.34 0 0 0 9.75 4.5a5.44 5.44 0 0 0-5.64 5.22 5.42 5.42 0 0 0 5.24 5.62c.41 0 .74.36.72.78a.75.75 0 0 1-.75.72H9.3a6.9 6.9 0 0 1-6.68-7.18 6.88 6.88 0 0 1 2.22-4.81z"
27+
isReloadingSchema={this.props.isReloadingSchema}
28+
/>
29+
</Svg>
30+
</Positioner>
31+
)
32+
}
33+
}
34+
35+
const iconColor = theme('mode', {
36+
light: p => p.theme.colours.darkBlue20,
37+
dark: p => p.theme.colours.white20,
38+
})
39+
40+
const iconColorHover = theme('mode', {
41+
light: p => p.theme.colours.darkBlue60,
42+
dark: p => p.theme.colours.white60,
43+
})
44+
45+
const refreshFrames = keyframes`
46+
0% {
47+
transform: rotate(0deg);
48+
stroke-dashoffset: 7.92;
49+
}
50+
51+
50% {
52+
transform: rotate(720deg);
53+
stroke-dashoffset: 37.68;
54+
}
55+
56+
100% {
57+
transform: rotate(1080deg);
58+
stroke-dashoffset: 7.92;
59+
}
60+
`
61+
62+
// same result for these 2 keyframes, however when the props change
63+
// it makes the element animated with these keyframes to trigger
64+
// again the animation
65+
const reloadAction = props => keyframes`
66+
0% {
67+
transform: rotate(${props.isReloadingSchema ? 0 : 360}deg);
68+
}
69+
70+
100% {
71+
transform: rotate(${props.isReloadingSchema ? 360 : 720}deg);
72+
}`
73+
74+
const Positioner = styled.div`
75+
position: absolute;
76+
right: 5px;
77+
width: 20px;
78+
height: 20px;
79+
cursor: pointer;
80+
transform: rotateY(180deg);
81+
`
82+
83+
const Svg = styled.svg`
84+
fill: ${iconColor};
85+
transition: 0.1s linear all;
86+
87+
&:hover {
88+
fill: ${iconColorHover};
89+
}
90+
`
91+
92+
const showWhenReloading = bool => props =>
93+
props.isReloadingSchema ? Number(bool) : Number(!bool)
94+
95+
const Circle: StyledComponentClass<any, any, any> = styled.circle`
96+
fill: none;
97+
stroke: ${iconColor};
98+
stroke-dasharray: 37.68;
99+
transition: opacity 0.3s ease-in-out;
100+
opacity: ${showWhenReloading(true)};
101+
transform-origin: 9.5px 10px;
102+
animation: ${refreshFrames} 2s linear infinite;
103+
`
104+
105+
const Icon: StyledComponentClass<any, any, any> = styled.path`
106+
transition: opacity 0.3s ease-in-out;
107+
opacity: ${showWhenReloading(false)};
108+
transform-origin: 9.5px 10px;
109+
animation: ${reloadAction} 0.5s linear;
110+
`

packages/graphql-playground-react/src/components/Playground/TopBar/TopBar.tsx

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import * as theme from 'styled-theming'
44
import { darken, lighten } from 'polished'
55
import * as CopyToClipboard from 'react-copy-to-clipboard'
66
import Share, { SharingProps } from '../../Share'
7+
import ReloadIcon from './ReloadIcon'
78
import { StyledComponentClass } from 'styled-components'
8-
import { Icon } from 'graphcool-styles'
99
import * as cx from 'classnames'
1010

1111
export interface Props {
@@ -17,6 +17,7 @@ export interface Props {
1717
curl: string
1818
onClickShare?: () => void
1919
onReloadSchema?: () => void
20+
isReloadingSchema: boolean
2021
sharing?: SharingProps
2122
fixedEndpoint?: boolean
2223
endpointUnreachable: boolean
@@ -45,10 +46,8 @@ export default class TopBar extends React.Component<Props, {}> {
4546
</ReachError>
4647
) : (
4748
<ReloadIcon
48-
src={require('graphcool-styles/icons/fill/reload.svg')}
49-
width={20}
50-
height={20}
51-
onClick={this.props.onReloadSchema}
49+
isReloadingSchema={this.props.isReloadingSchema}
50+
onReloadSchema={this.props.onReloadSchema}
5251
/>
5352
)}
5453
</UrlBarWrapper>
@@ -100,16 +99,6 @@ const fontColor = theme('mode', {
10099
dark: p => p.theme.colours.white60,
101100
})
102101

103-
const iconColor = theme('mode', {
104-
light: p => p.theme.colours.darkBlue20,
105-
dark: p => p.theme.colours.white20,
106-
})
107-
108-
const iconColorHover = theme('mode', {
109-
light: p => p.theme.colours.darkBlue60,
110-
dark: p => p.theme.colours.white60,
111-
})
112-
113102
const barBorder = theme('mode', {
114103
light: p => p.theme.colours.darkBlue20,
115104
dark: p => '#09141c',
@@ -172,19 +161,6 @@ const ReachError = styled.div`
172161
color: #f25c54;
173162
`
174163

175-
const ReloadIcon = styled(Icon)`
176-
position: absolute;
177-
right: 5px;
178-
cursor: pointer;
179-
svg {
180-
fill: ${iconColor};
181-
transition: 0.1s linear all;
182-
&:hover {
183-
fill: ${iconColorHover};
184-
}
185-
}
186-
` as any // TODO remove this once typings are fixed
187-
188164
const Spinner = styled.div`
189165
& {
190166
width: 40px;

0 commit comments

Comments
 (0)