Skip to content

Commit 8009e25

Browse files
committed
feat(ui-spinner,ui-avatar): refactor spinner
1 parent 75c8743 commit 8009e25

File tree

7 files changed

+214
-337
lines changed

7 files changed

+214
-337
lines changed

packages/__docs__/src/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<meta charset="utf-8" />
55
<meta http-equiv="x-ua-compatible" content="ie=edge" />
66
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
78
<title>Instructure UI</title>
89

910
<link

packages/__docs__/webpack.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const config = merge(baseConfig, {
5959
directory: outputPath,
6060
},
6161
host: '0.0.0.0',
62+
historyApiFallback: true,
6263
client: {
6364
overlay: false,
6465
},

packages/ui-avatar/src/Avatar/README.md

Lines changed: 81 additions & 166 deletions
Large diffs are not rendered by default.

packages/ui-spinner/src/Spinner/README.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ The `size` prop allows you to select from `x-small`, `small`, `medium` and `larg
1111
---
1212
type: example
1313
---
14-
<div>
15-
<Spinner renderTitle="Loading" size="x-small"/>
16-
<Spinner renderTitle="Loading" size="small" margin="0 0 0 medium" />
17-
<Spinner renderTitle="Loading" margin="0 0 0 medium" />
18-
<Spinner renderTitle="Loading" size="large" margin="0 0 0 medium" />
14+
<div style={{ display: 'flex', alignItems: 'center', gap: 'spacing.spaceMd' }}>
15+
<Spinner renderTitle="Loading" size="x-small" margin="spacing.spaceXs"/>
16+
<Spinner renderTitle="Loading" size="small" margin="spacing.spaceXs" />
17+
<Spinner renderTitle="Loading" margin="spacing.spaceXs" />
18+
<Spinner renderTitle="Loading" size="large" margin="spacing.spaceXs" />
1919
</div>
2020
```
2121

@@ -28,8 +28,8 @@ dark backgrounds.
2828
---
2929
type: example
3030
---
31-
<View background="primary-inverse" as="div">
32-
<Spinner renderTitle="Loading" variant="inverse" />
31+
<View background="primary-inverse" as="div" margin="spacing.spaceXs" padding="spacing.spaceLg">
32+
<Spinner renderTitle="Loading" variant="inverse" margin="spacing.spaceXs" />
3333
</View>
3434
```
3535

@@ -41,11 +41,11 @@ The `delay` prop allows you to delay the rendering of the spinner a desired time
4141
---
4242
type: example
4343
---
44-
<div>
45-
<Spinner renderTitle="Loading" size="x-small" delay={1000} />
46-
<Spinner renderTitle="Loading" size="small" margin="0 0 0 medium" delay={2000} />
47-
<Spinner renderTitle="Loading" margin="0 0 0 medium" delay={3000} />
48-
<Spinner renderTitle="Loading" size="large" margin="0 0 0 medium" delay={4000} />
44+
<div style={{ display: 'flex', alignItems: 'center', gap: 'spacing.spaceMd' }}>
45+
<Spinner renderTitle="Loading" size="x-small" delay={1000} margin="spacing.spaceXs" />
46+
<Spinner renderTitle="Loading" size="small" margin="spacing.spaceXs" delay={2000} />
47+
<Spinner renderTitle="Loading" margin="spacing.spaceXs" delay={3000} />
48+
<Spinner renderTitle="Loading" size="large" margin="spacing.spaceXs" delay={4000} />
4949
</div>
5050
```
5151

@@ -57,5 +57,7 @@ The `renderTitle` prop is read to screen readers.
5757
---
5858
type: example
5959
---
60-
<Spinner renderTitle={() => "Hello world"} />
60+
<div>
61+
<Spinner renderTitle={() => "Hello world"} margin="spacing.spaceXs" />
62+
</div>
6163
```

packages/ui-spinner/src/Spinner/index.tsx

Lines changed: 55 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -22,150 +22,103 @@
2222
* SOFTWARE.
2323
*/
2424

25-
import { Component } from 'react'
25+
import { useState, useEffect, useId, forwardRef } from 'react'
2626

27-
import { View } from '@instructure/ui-view'
28-
import {
29-
callRenderProp,
30-
omitProps,
31-
withDeterministicId
32-
} from '@instructure/ui-react-utils'
27+
import { useStyle } from '@instructure/emotion'
28+
import { callRenderProp, omitProps } from '@instructure/ui-react-utils'
3329
import { logError as error } from '@instructure/console'
3430

35-
import { withStyle } from '@instructure/emotion'
36-
3731
import generateStyle from './styles'
3832
import generateComponentTheme from './theme'
39-
import type { SpinnerProps, SpinnerState } from './props'
33+
import type { SpinnerProps } from './props'
4034
import { allowedProps } from './props'
4135

4236
/**
4337
---
4438
category: components
4539
---
4640
**/
47-
@withDeterministicId()
48-
@withStyle(generateStyle, generateComponentTheme)
49-
class Spinner extends Component<SpinnerProps, SpinnerState> {
50-
static readonly componentId = 'Spinner'
51-
static allowedProps = allowedProps
52-
static defaultProps = {
53-
as: 'div',
54-
size: 'medium',
55-
variant: 'default'
56-
}
57-
58-
ref: Element | null = null
59-
private readonly titleId?: string
60-
private delayTimeout?: NodeJS.Timeout
61-
62-
handleRef = (el: Element | null) => {
63-
const { elementRef } = this.props
64-
65-
this.ref = el
66-
67-
if (typeof elementRef === 'function') {
68-
elementRef(el)
69-
}
70-
}
71-
72-
constructor(props: SpinnerProps) {
73-
super(props)
74-
75-
this.titleId = props.deterministicId!()
76-
77-
this.state = {
78-
shouldRender: !props.delay
79-
}
80-
}
81-
82-
componentDidMount() {
83-
this.props.makeStyles?.()
84-
const { delay } = this.props
85-
41+
const Spinner = forwardRef<HTMLDivElement, SpinnerProps>((props, ref) => {
42+
const {
43+
size = 'medium',
44+
variant = 'default',
45+
delay,
46+
renderTitle,
47+
margin,
48+
// elementRef,
49+
themeOverride
50+
} = props
51+
52+
const [shouldRender, setShouldRender] = useState(!delay)
53+
const titleId = useId()
54+
55+
const styles = useStyle({
56+
generateStyle,
57+
generateComponentTheme,
58+
params: {
59+
size,
60+
variant,
61+
themeOverride,
62+
margin
63+
},
64+
componentId: 'Spinner',
65+
displayName: 'Spinner'
66+
})
67+
68+
useEffect(() => {
8669
if (delay) {
87-
this.delayTimeout = setTimeout(() => {
88-
this.setState({ shouldRender: true })
70+
const delayTimeout = setTimeout(() => {
71+
setShouldRender(true)
8972
}, delay)
90-
}
91-
}
92-
93-
componentDidUpdate() {
94-
this.props.makeStyles?.()
95-
}
96-
97-
componentWillUnmount() {
98-
clearTimeout(this.delayTimeout)
99-
}
10073

101-
radius() {
102-
switch (this.props.size) {
103-
case 'x-small':
104-
return '0.5em'
105-
case 'small':
106-
return '1em'
107-
case 'large':
108-
return '2.25em'
109-
default:
110-
return '1.75em'
74+
return () => clearTimeout(delayTimeout)
11175
}
112-
}
113-
114-
renderSpinner() {
115-
const passthroughProps = View.omitViewProps(
116-
omitProps(this.props, Spinner.allowedProps),
117-
Spinner
118-
)
76+
return undefined
77+
}, [delay])
11978

120-
const hasTitle = this.props.renderTitle
79+
const renderSpinner = () => {
80+
const hasTitle = renderTitle
12181
error(
12282
!!hasTitle,
12383
'[Spinner] The renderTitle prop is necessary for screen reader support.'
12484
)
12585

86+
const passthroughProps = omitProps(props, allowedProps)
87+
12688
return (
127-
<View
128-
{...passthroughProps}
129-
as={this.props.as}
130-
elementRef={this.handleRef}
131-
css={this.props.styles?.spinner}
132-
margin={this.props.margin}
133-
data-cid="Spinner"
134-
>
89+
<div {...passthroughProps} css={styles?.spinner} ref={ref}>
13590
<svg
136-
css={this.props.styles?.circle}
91+
css={styles?.circle}
13792
role="img"
138-
aria-labelledby={this.titleId}
93+
aria-labelledby={titleId}
13994
focusable="false"
14095
>
141-
<title id={this.titleId}>
142-
{callRenderProp(this.props.renderTitle)}
143-
</title>
96+
<title id={titleId}>{callRenderProp(renderTitle)}</title>
14497
<g role="presentation">
145-
{this.props.variant !== 'inverse' && (
98+
{variant !== 'inverse' && (
14699
<circle
147-
css={this.props.styles?.circleTrack}
100+
css={styles?.circleTrack}
148101
cx="50%"
149102
cy="50%"
150-
r={this.radius()}
103+
r={styles?.radius as string}
151104
/>
152105
)}
153106
<circle
154-
css={this.props.styles?.circleSpin}
107+
css={styles?.circleSpin}
155108
cx="50%"
156109
cy="50%"
157-
r={this.radius()}
110+
r={styles?.radius as string}
158111
/>
159112
</g>
160113
</svg>
161-
</View>
114+
</div>
162115
)
163116
}
164117

165-
render() {
166-
return this.state.shouldRender ? this.renderSpinner() : null
167-
}
168-
}
118+
return shouldRender ? renderSpinner() : null
119+
})
120+
121+
Spinner.displayName = 'Spinner'
169122

170123
export default Spinner
171124
export { Spinner }

packages/ui-spinner/src/Spinner/props.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,20 @@ import type {
2828
ComponentStyle
2929
} from '@instructure/emotion'
3030
import type {
31-
AsElementType,
3231
OtherHTMLAttributes,
3332
SpinnerTheme
3433
} from '@instructure/shared-types'
3534
import type { WithDeterministicIdProps } from '@instructure/ui-react-utils'
3635
import { Renderable } from '@instructure/shared-types'
3736

3837
type SpinnerOwnProps = {
39-
/**
40-
* Render Spinner "as" another HTML element
41-
*/
42-
as?: AsElementType
4338
/**
4439
* delay spinner rendering for a time (in ms). Used to prevent flickering in case of very fast load times
4540
*/
4641
delay?: number
4742
/**
48-
* provides a reference to the underlying html root element
49-
*/
50-
elementRef?: (element: Element | null) => void
51-
/**
52-
* Valid values are `0`, `none`, `auto`, `xxx-small`, `xx-small`, `x-small`,
53-
* `small`, `medium`, `large`, `x-large`, `xx-large`. Apply these values via
54-
* familiar CSS-like shorthand. For example: `margin="small auto large"`.
43+
* Valid values are from themes. See theme.semantics.spacing. Apply these values via
44+
* familiar CSS-like shorthand. For example: `margin="spaceLg gap.cards.sm 20px padding.container.sm"`.
5545
*/
5646
margin?: Spacing
5747
/**
@@ -82,16 +72,15 @@ type SpinnerState = {
8272
}
8373

8474
type SpinnerStyle = ComponentStyle<
85-
'spinner' | 'circle' | 'circleTrack' | 'circleSpin'
75+
'spinner' | 'circle' | 'circleTrack' | 'circleSpin' | 'radius'
8676
>
77+
8778
const allowedProps: AllowedPropKeys = [
8879
'delay',
8980
'renderTitle',
9081
'size',
9182
'variant',
92-
'margin',
93-
'elementRef',
94-
'as'
83+
'margin'
9584
]
9685

9786
export type { SpinnerProps, SpinnerState, SpinnerStyle }

0 commit comments

Comments
 (0)