@instructure/ui-spinner 11.7.7-snapshot-6 → 11.7.7-snapshot-8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -4
- package/es/Spinner/v2/AISpinnerIcon.js +44 -0
- package/es/Spinner/v2/index.js +46 -24
- package/es/Spinner/v2/styles.js +23 -9
- package/lib/Spinner/v2/AISpinnerIcon.js +47 -0
- package/lib/Spinner/v2/index.js +45 -23
- package/lib/Spinner/v2/styles.js +23 -9
- package/package.json +14 -12
- package/src/Spinner/v2/AISpinnerIcon.tsx +45 -0
- package/src/Spinner/v2/README.md +21 -1
- package/src/Spinner/v2/index.tsx +46 -24
- package/src/Spinner/v2/props.ts +5 -3
- package/src/Spinner/v2/styles.ts +22 -5
- package/tsconfig.build.json +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Spinner/v2/AISpinnerIcon.d.ts +4 -0
- package/types/Spinner/v2/AISpinnerIcon.d.ts.map +1 -0
- package/types/Spinner/v2/index.d.ts +3 -3
- package/types/Spinner/v2/index.d.ts.map +1 -1
- package/types/Spinner/v2/props.d.ts +5 -3
- package/types/Spinner/v2/props.d.ts.map +1 -1
- package/types/Spinner/v2/styles.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [11.7.7-snapshot-
|
|
7
|
-
|
|
8
|
-
**Note:** Version bump only for package @instructure/ui-spinner
|
|
9
|
-
|
|
6
|
+
## [11.7.7-snapshot-8](https://github.com/instructure/instructure-ui/compare/v11.7.6...v11.7.7-snapshot-8) (2026-09-23)
|
|
10
7
|
|
|
8
|
+
### Features
|
|
11
9
|
|
|
10
|
+
* **ui-spinner,ui-icons:** add ai and ai-on-color spinner variants ([cb3bd29](https://github.com/instructure/instructure-ui/commit/cb3bd2953f89f72a6d3a06c338a217eedce1a392))
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
## [11.7.6](https://github.com/instructure/instructure-ui/compare/v11.7.5...v11.7.6) (2026-09-14)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { wrapCustomIcon } from '@instructure/ui-icons';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ---
|
|
29
|
+
* private: true
|
|
30
|
+
* ---
|
|
31
|
+
* The star path from packages/ui-icons/svg/Custom/igniteai-logo.svg, without
|
|
32
|
+
* the small sparkle. The star alone is centered in the viewBox, so it rotates
|
|
33
|
+
* evenly. If that logo changes, update this file too.
|
|
34
|
+
*/
|
|
35
|
+
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
36
|
+
const AISpinnerPaths = ({
|
|
37
|
+
color = 'currentColor'
|
|
38
|
+
}) => _jsx("path", {
|
|
39
|
+
d: "M11.0621 2.53451C11.3843 1.66389 12.6157 1.66389 12.9379 2.53451L15.0815 8.32767C15.1828 8.60139 15.3986 8.8172 15.6723 8.91848L21.4655 11.0621C22.3361 11.3843 22.3361 12.6157 21.4655 12.9379L15.6723 15.0815C15.3986 15.1828 15.1828 15.3986 15.0815 15.6723L12.9379 21.4655C12.6157 22.3361 11.3843 22.3361 11.0621 21.4655L8.91849 15.6723C8.8172 15.3986 8.60139 15.1828 8.32767 15.0815L2.53451 12.9379C1.66389 12.6157 1.66389 11.3843 2.53451 11.0621L8.32767 8.91849C8.60139 8.8172 8.8172 8.60139 8.91848 8.32767L11.0621 2.53451Z",
|
|
40
|
+
fill: color
|
|
41
|
+
});
|
|
42
|
+
const AISpinnerIcon = wrapCustomIcon(AISpinnerPaths, 'AISpinner', '0 0 24 24');
|
|
43
|
+
export default AISpinnerIcon;
|
|
44
|
+
export { AISpinnerIcon };
|
package/es/Spinner/v2/index.js
CHANGED
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
import { useState, useEffect, forwardRef } from 'react';
|
|
26
26
|
import { useStyleNew } from '@instructure/emotion';
|
|
27
27
|
import { callRenderProp, omitProps, useDeterministicId } from '@instructure/ui-react-utils';
|
|
28
|
+
import { ScreenReaderContent } from '@instructure/ui-a11y-content';
|
|
28
29
|
import { logError as error } from '@instructure/console';
|
|
30
|
+
import { AISpinnerIcon } from './AISpinnerIcon.js';
|
|
29
31
|
import generateStyle from './styles.js';
|
|
30
32
|
import { allowedProps } from './props.js';
|
|
31
33
|
|
|
@@ -34,7 +36,7 @@ import { allowedProps } from './props.js';
|
|
|
34
36
|
category: components
|
|
35
37
|
---
|
|
36
38
|
**/
|
|
37
|
-
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
39
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
|
|
38
40
|
const Spinner = /*#__PURE__*/forwardRef((props, ref) => {
|
|
39
41
|
const {
|
|
40
42
|
size = 'medium',
|
|
@@ -67,37 +69,57 @@ const Spinner = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
67
69
|
}
|
|
68
70
|
return undefined;
|
|
69
71
|
}, [delay]);
|
|
72
|
+
const isAI = variant === 'ai' || variant === 'ai-on-color';
|
|
73
|
+
const renderCircle = () => _jsxs("svg", {
|
|
74
|
+
css: styles?.circle,
|
|
75
|
+
role: "img",
|
|
76
|
+
"aria-labelledby": titleId,
|
|
77
|
+
focusable: "false",
|
|
78
|
+
children: [_jsx("title", {
|
|
79
|
+
id: titleId,
|
|
80
|
+
children: callRenderProp(renderTitle)
|
|
81
|
+
}), _jsxs("g", {
|
|
82
|
+
role: "presentation",
|
|
83
|
+
children: [variant === 'default' && _jsx("circle", {
|
|
84
|
+
css: styles?.circleTrack,
|
|
85
|
+
cx: "50%",
|
|
86
|
+
cy: "50%",
|
|
87
|
+
r: styles?.radius
|
|
88
|
+
}), _jsx("circle", {
|
|
89
|
+
css: styles?.circleSpin,
|
|
90
|
+
cx: "50%",
|
|
91
|
+
cy: "50%",
|
|
92
|
+
r: styles?.radius
|
|
93
|
+
})]
|
|
94
|
+
})]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// The icon accepts only a string `title`, but `renderTitle` can be any node.
|
|
98
|
+
// So the label goes on the wrapping element instead.
|
|
99
|
+
const renderAIIcon = () => _jsxs(_Fragment, {
|
|
100
|
+
children: [_jsx(ScreenReaderContent, {
|
|
101
|
+
id: titleId,
|
|
102
|
+
children: callRenderProp(renderTitle)
|
|
103
|
+
}), _jsx("span", {
|
|
104
|
+
css: styles?.aiSpin,
|
|
105
|
+
children: _jsx(AISpinnerIcon, {
|
|
106
|
+
color: variant === 'ai' ? 'ai' : 'onColor'
|
|
107
|
+
})
|
|
108
|
+
})]
|
|
109
|
+
});
|
|
70
110
|
const renderSpinner = () => {
|
|
71
111
|
error(!!renderTitle, '[Spinner] The `renderTitle` prop is necessary for screen reader support.');
|
|
72
112
|
const passthroughProps = omitProps(props, allowedProps);
|
|
73
113
|
return _jsx("div", {
|
|
114
|
+
...(isAI && {
|
|
115
|
+
role: 'img',
|
|
116
|
+
'aria-labelledby': titleId
|
|
117
|
+
}),
|
|
74
118
|
...passthroughProps,
|
|
75
119
|
css: styles?.spinner,
|
|
76
120
|
ref: ref,
|
|
77
121
|
"data-cid": "Spinner",
|
|
78
|
-
children:
|
|
79
|
-
css: styles?.circle,
|
|
80
|
-
role: "img",
|
|
81
|
-
"aria-labelledby": titleId,
|
|
82
|
-
focusable: "false",
|
|
83
|
-
children: [_jsx("title", {
|
|
84
|
-
id: titleId,
|
|
85
|
-
children: callRenderProp(renderTitle)
|
|
86
|
-
}), _jsxs("g", {
|
|
87
|
-
role: "presentation",
|
|
88
|
-
children: [variant !== 'inverse' && _jsx("circle", {
|
|
89
|
-
css: styles?.circleTrack,
|
|
90
|
-
cx: "50%",
|
|
91
|
-
cy: "50%",
|
|
92
|
-
r: styles?.radius
|
|
93
|
-
}), _jsx("circle", {
|
|
94
|
-
css: styles?.circleSpin,
|
|
95
|
-
cx: "50%",
|
|
96
|
-
cy: "50%",
|
|
97
|
-
r: styles?.radius
|
|
98
|
-
})]
|
|
99
|
-
})]
|
|
100
|
-
})
|
|
122
|
+
children: isAI ? renderAIIcon() : renderCircle()
|
|
101
123
|
});
|
|
102
124
|
};
|
|
103
125
|
return shouldRender ? renderSpinner() : null;
|
package/es/Spinner/v2/styles.js
CHANGED
|
@@ -134,14 +134,10 @@ const generateStyle = (componentTheme, params, sharedTokens) => {
|
|
|
134
134
|
transformOrigin: `calc(${componentTheme.spinnerSizeLg} / 2) calc(${componentTheme.spinnerSizeLg} / 2)` //7em
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
inverse: {
|
|
142
|
-
stroke: componentTheme.inverseColor
|
|
143
|
-
}
|
|
144
|
-
};
|
|
137
|
+
|
|
138
|
+
// Only the circle variants use this. The AI variants render an icon instead,
|
|
139
|
+
// and take their color from the icon's own tokens.
|
|
140
|
+
const isInverse = variant === 'inverse';
|
|
145
141
|
return {
|
|
146
142
|
spinner: {
|
|
147
143
|
label: 'spinner',
|
|
@@ -185,7 +181,25 @@ const generateStyle = (componentTheme, params, sharedTokens) => {
|
|
|
185
181
|
animationTimingFunction: 'ease',
|
|
186
182
|
strokeDasharray: `calc(${radii[size]} * 2 * 3.14159 * 0.75) 1000px`,
|
|
187
183
|
...circleSpinSizes[size],
|
|
188
|
-
|
|
184
|
+
stroke: isInverse ? componentTheme.inverseColor : componentTheme.color
|
|
185
|
+
},
|
|
186
|
+
aiSpin: {
|
|
187
|
+
label: 'spinner__aiSpin',
|
|
188
|
+
display: 'block',
|
|
189
|
+
// The icon sits in an inline-block span. Without this, the line adds
|
|
190
|
+
// extra space below it, and the icon no longer rotates around its center.
|
|
191
|
+
lineHeight: 0,
|
|
192
|
+
...circleSizes[size],
|
|
193
|
+
animationName: rotate,
|
|
194
|
+
animationDuration: '2.25s',
|
|
195
|
+
animationIterationCount: 'infinite',
|
|
196
|
+
animationTimingFunction: 'linear',
|
|
197
|
+
// The icon has no `size` prop, so its svg has no width or height. Use the
|
|
198
|
+
// same size as the circle, so the layout stays the same in every variant.
|
|
199
|
+
'& svg': {
|
|
200
|
+
display: 'block',
|
|
201
|
+
...circleSizes[size]
|
|
202
|
+
}
|
|
189
203
|
},
|
|
190
204
|
radius: radii[size]
|
|
191
205
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.AISpinnerIcon = void 0;
|
|
7
|
+
var _wrapCustomIcon = require("@instructure/ui-icons/lib/custom/wrapCustomIcon.js");
|
|
8
|
+
var _jsxRuntime = require("@emotion/react/jsx-runtime");
|
|
9
|
+
/*
|
|
10
|
+
* The MIT License (MIT)
|
|
11
|
+
*
|
|
12
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
13
|
+
*
|
|
14
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
* in the Software without restriction, including without limitation the rights
|
|
17
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
* furnished to do so, subject to the following conditions:
|
|
20
|
+
*
|
|
21
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
* copies or substantial portions of the Software.
|
|
23
|
+
*
|
|
24
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
* SOFTWARE.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* ---
|
|
35
|
+
* private: true
|
|
36
|
+
* ---
|
|
37
|
+
* The star path from packages/ui-icons/svg/Custom/igniteai-logo.svg, without
|
|
38
|
+
* the small sparkle. The star alone is centered in the viewBox, so it rotates
|
|
39
|
+
* evenly. If that logo changes, update this file too.
|
|
40
|
+
*/const AISpinnerPaths = ({
|
|
41
|
+
color = 'currentColor'
|
|
42
|
+
}) => (0, _jsxRuntime.jsx)("path", {
|
|
43
|
+
d: "M11.0621 2.53451C11.3843 1.66389 12.6157 1.66389 12.9379 2.53451L15.0815 8.32767C15.1828 8.60139 15.3986 8.8172 15.6723 8.91848L21.4655 11.0621C22.3361 11.3843 22.3361 12.6157 21.4655 12.9379L15.6723 15.0815C15.3986 15.1828 15.1828 15.3986 15.0815 15.6723L12.9379 21.4655C12.6157 22.3361 11.3843 22.3361 11.0621 21.4655L8.91849 15.6723C8.8172 15.3986 8.60139 15.1828 8.32767 15.0815L2.53451 12.9379C1.66389 12.6157 1.66389 11.3843 2.53451 11.0621L8.32767 8.91849C8.60139 8.8172 8.8172 8.60139 8.91848 8.32767L11.0621 2.53451Z",
|
|
44
|
+
fill: color
|
|
45
|
+
});
|
|
46
|
+
const AISpinnerIcon = exports.AISpinnerIcon = (0, _wrapCustomIcon.wrapCustomIcon)(AISpinnerPaths, 'AISpinner', '0 0 24 24');
|
|
47
|
+
var _default = exports.default = AISpinnerIcon;
|
package/lib/Spinner/v2/index.js
CHANGED
|
@@ -10,7 +10,9 @@ var _emotion = require("@instructure/emotion");
|
|
|
10
10
|
var _callRenderProp = require("@instructure/ui-react-utils/lib/callRenderProp.js");
|
|
11
11
|
var _omitProps = require("@instructure/ui-react-utils/lib/omitProps.js");
|
|
12
12
|
var _useDeterministicId = require("@instructure/ui-react-utils/lib/DeterministicIdContext/useDeterministicId.js");
|
|
13
|
+
var _ScreenReaderContent = require("@instructure/ui-a11y-content/lib/ScreenReaderContent");
|
|
13
14
|
var _console = require("@instructure/console");
|
|
15
|
+
var _AISpinnerIcon = require("./AISpinnerIcon.js");
|
|
14
16
|
var _styles = _interopRequireDefault(require("./styles.js"));
|
|
15
17
|
var _props = require("./props.js");
|
|
16
18
|
var _jsxRuntime = require("@emotion/react/jsx-runtime");
|
|
@@ -74,37 +76,57 @@ category: components
|
|
|
74
76
|
}
|
|
75
77
|
return undefined;
|
|
76
78
|
}, [delay]);
|
|
79
|
+
const isAI = variant === 'ai' || variant === 'ai-on-color';
|
|
80
|
+
const renderCircle = () => (0, _jsxRuntime.jsxs)("svg", {
|
|
81
|
+
css: styles?.circle,
|
|
82
|
+
role: "img",
|
|
83
|
+
"aria-labelledby": titleId,
|
|
84
|
+
focusable: "false",
|
|
85
|
+
children: [(0, _jsxRuntime.jsx)("title", {
|
|
86
|
+
id: titleId,
|
|
87
|
+
children: (0, _callRenderProp.callRenderProp)(renderTitle)
|
|
88
|
+
}), (0, _jsxRuntime.jsxs)("g", {
|
|
89
|
+
role: "presentation",
|
|
90
|
+
children: [variant === 'default' && (0, _jsxRuntime.jsx)("circle", {
|
|
91
|
+
css: styles?.circleTrack,
|
|
92
|
+
cx: "50%",
|
|
93
|
+
cy: "50%",
|
|
94
|
+
r: styles?.radius
|
|
95
|
+
}), (0, _jsxRuntime.jsx)("circle", {
|
|
96
|
+
css: styles?.circleSpin,
|
|
97
|
+
cx: "50%",
|
|
98
|
+
cy: "50%",
|
|
99
|
+
r: styles?.radius
|
|
100
|
+
})]
|
|
101
|
+
})]
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// The icon accepts only a string `title`, but `renderTitle` can be any node.
|
|
105
|
+
// So the label goes on the wrapping element instead.
|
|
106
|
+
const renderAIIcon = () => (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
107
|
+
children: [(0, _jsxRuntime.jsx)(_ScreenReaderContent.ScreenReaderContent, {
|
|
108
|
+
id: titleId,
|
|
109
|
+
children: (0, _callRenderProp.callRenderProp)(renderTitle)
|
|
110
|
+
}), (0, _jsxRuntime.jsx)("span", {
|
|
111
|
+
css: styles?.aiSpin,
|
|
112
|
+
children: (0, _jsxRuntime.jsx)(_AISpinnerIcon.AISpinnerIcon, {
|
|
113
|
+
color: variant === 'ai' ? 'ai' : 'onColor'
|
|
114
|
+
})
|
|
115
|
+
})]
|
|
116
|
+
});
|
|
77
117
|
const renderSpinner = () => {
|
|
78
118
|
(0, _console.logError)(!!renderTitle, '[Spinner] The `renderTitle` prop is necessary for screen reader support.');
|
|
79
119
|
const passthroughProps = (0, _omitProps.omitProps)(props, _props.allowedProps);
|
|
80
120
|
return (0, _jsxRuntime.jsx)("div", {
|
|
121
|
+
...(isAI && {
|
|
122
|
+
role: 'img',
|
|
123
|
+
'aria-labelledby': titleId
|
|
124
|
+
}),
|
|
81
125
|
...passthroughProps,
|
|
82
126
|
css: styles?.spinner,
|
|
83
127
|
ref: ref,
|
|
84
128
|
"data-cid": "Spinner",
|
|
85
|
-
children: (
|
|
86
|
-
css: styles?.circle,
|
|
87
|
-
role: "img",
|
|
88
|
-
"aria-labelledby": titleId,
|
|
89
|
-
focusable: "false",
|
|
90
|
-
children: [(0, _jsxRuntime.jsx)("title", {
|
|
91
|
-
id: titleId,
|
|
92
|
-
children: (0, _callRenderProp.callRenderProp)(renderTitle)
|
|
93
|
-
}), (0, _jsxRuntime.jsxs)("g", {
|
|
94
|
-
role: "presentation",
|
|
95
|
-
children: [variant !== 'inverse' && (0, _jsxRuntime.jsx)("circle", {
|
|
96
|
-
css: styles?.circleTrack,
|
|
97
|
-
cx: "50%",
|
|
98
|
-
cy: "50%",
|
|
99
|
-
r: styles?.radius
|
|
100
|
-
}), (0, _jsxRuntime.jsx)("circle", {
|
|
101
|
-
css: styles?.circleSpin,
|
|
102
|
-
cx: "50%",
|
|
103
|
-
cy: "50%",
|
|
104
|
-
r: styles?.radius
|
|
105
|
-
})]
|
|
106
|
-
})]
|
|
107
|
-
})
|
|
129
|
+
children: isAI ? renderAIIcon() : renderCircle()
|
|
108
130
|
});
|
|
109
131
|
};
|
|
110
132
|
return shouldRender ? renderSpinner() : null;
|
package/lib/Spinner/v2/styles.js
CHANGED
|
@@ -140,14 +140,10 @@ const generateStyle = (componentTheme, params, sharedTokens) => {
|
|
|
140
140
|
transformOrigin: `calc(${componentTheme.spinnerSizeLg} / 2) calc(${componentTheme.spinnerSizeLg} / 2)` //7em
|
|
141
141
|
}
|
|
142
142
|
};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
inverse: {
|
|
148
|
-
stroke: componentTheme.inverseColor
|
|
149
|
-
}
|
|
150
|
-
};
|
|
143
|
+
|
|
144
|
+
// Only the circle variants use this. The AI variants render an icon instead,
|
|
145
|
+
// and take their color from the icon's own tokens.
|
|
146
|
+
const isInverse = variant === 'inverse';
|
|
151
147
|
return {
|
|
152
148
|
spinner: {
|
|
153
149
|
label: 'spinner',
|
|
@@ -191,7 +187,25 @@ const generateStyle = (componentTheme, params, sharedTokens) => {
|
|
|
191
187
|
animationTimingFunction: 'ease',
|
|
192
188
|
strokeDasharray: `calc(${radii[size]} * 2 * 3.14159 * 0.75) 1000px`,
|
|
193
189
|
...circleSpinSizes[size],
|
|
194
|
-
|
|
190
|
+
stroke: isInverse ? componentTheme.inverseColor : componentTheme.color
|
|
191
|
+
},
|
|
192
|
+
aiSpin: {
|
|
193
|
+
label: 'spinner__aiSpin',
|
|
194
|
+
display: 'block',
|
|
195
|
+
// The icon sits in an inline-block span. Without this, the line adds
|
|
196
|
+
// extra space below it, and the icon no longer rotates around its center.
|
|
197
|
+
lineHeight: 0,
|
|
198
|
+
...circleSizes[size],
|
|
199
|
+
animationName: rotate,
|
|
200
|
+
animationDuration: '2.25s',
|
|
201
|
+
animationIterationCount: 'infinite',
|
|
202
|
+
animationTimingFunction: 'linear',
|
|
203
|
+
// The icon has no `size` prop, so its svg has no width or height. Use the
|
|
204
|
+
// same size as the circle, so the layout stays the same in every variant.
|
|
205
|
+
'& svg': {
|
|
206
|
+
display: 'block',
|
|
207
|
+
...circleSizes[size]
|
|
208
|
+
}
|
|
195
209
|
},
|
|
196
210
|
radius: radii[size]
|
|
197
211
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-spinner",
|
|
3
|
-
"version": "11.7.7-snapshot-
|
|
3
|
+
"version": "11.7.7-snapshot-8",
|
|
4
4
|
"description": "A spinner/loading component",
|
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
6
6
|
"module": "./es/index.js",
|
|
@@ -15,20 +15,22 @@
|
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@babel/runtime": "^7.29.7",
|
|
18
|
-
"@instructure/
|
|
19
|
-
"@instructure/
|
|
20
|
-
"@instructure/ui-
|
|
21
|
-
"@instructure/shared-types": "11.7.7-snapshot-
|
|
22
|
-
"@instructure/ui-
|
|
23
|
-
"@instructure/ui-
|
|
24
|
-
"@instructure/ui-
|
|
25
|
-
"@instructure/
|
|
26
|
-
"@instructure/ui-view": "11.7.7-snapshot-
|
|
18
|
+
"@instructure/console": "11.7.7-snapshot-8",
|
|
19
|
+
"@instructure/emotion": "11.7.7-snapshot-8",
|
|
20
|
+
"@instructure/ui-a11y-content": "11.7.7-snapshot-8",
|
|
21
|
+
"@instructure/shared-types": "11.7.7-snapshot-8",
|
|
22
|
+
"@instructure/ui-color-utils": "11.7.7-snapshot-8",
|
|
23
|
+
"@instructure/ui-icons": "11.7.7-snapshot-8",
|
|
24
|
+
"@instructure/ui-react-utils": "11.7.7-snapshot-8",
|
|
25
|
+
"@instructure/ui-themes": "11.7.7-snapshot-8",
|
|
26
|
+
"@instructure/ui-view": "11.7.7-snapshot-8",
|
|
27
|
+
"@instructure/uid": "11.7.7-snapshot-8",
|
|
28
|
+
"@instructure/ui-utils": "11.7.7-snapshot-8"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"vitest": "^5.0.0",
|
|
30
|
-
"@instructure/ui-
|
|
31
|
-
"@instructure/ui-
|
|
32
|
+
"@instructure/ui-axe-check": "11.7.7-snapshot-8",
|
|
33
|
+
"@instructure/ui-babel-preset": "11.7.7-snapshot-8"
|
|
32
34
|
},
|
|
33
35
|
"peerDependencies": {
|
|
34
36
|
"react": ">=18 <=19"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { wrapCustomIcon } from '@instructure/ui-icons'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ---
|
|
29
|
+
* private: true
|
|
30
|
+
* ---
|
|
31
|
+
* The star path from packages/ui-icons/svg/Custom/igniteai-logo.svg, without
|
|
32
|
+
* the small sparkle. The star alone is centered in the viewBox, so it rotates
|
|
33
|
+
* evenly. If that logo changes, update this file too.
|
|
34
|
+
*/
|
|
35
|
+
const AISpinnerPaths = ({ color = 'currentColor' }: { color?: string }) => (
|
|
36
|
+
<path
|
|
37
|
+
d="M11.0621 2.53451C11.3843 1.66389 12.6157 1.66389 12.9379 2.53451L15.0815 8.32767C15.1828 8.60139 15.3986 8.8172 15.6723 8.91848L21.4655 11.0621C22.3361 11.3843 22.3361 12.6157 21.4655 12.9379L15.6723 15.0815C15.3986 15.1828 15.1828 15.3986 15.0815 15.6723L12.9379 21.4655C12.6157 22.3361 11.3843 22.3361 11.0621 21.4655L8.91849 15.6723C8.8172 15.3986 8.60139 15.1828 8.32767 15.0815L2.53451 12.9379C1.66389 12.6157 1.66389 11.3843 2.53451 11.0621L8.32767 8.91849C8.60139 8.8172 8.8172 8.60139 8.91848 8.32767L11.0621 2.53451Z"
|
|
38
|
+
fill={color}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const AISpinnerIcon = wrapCustomIcon(AISpinnerPaths, 'AISpinner', '0 0 24 24')
|
|
43
|
+
|
|
44
|
+
export default AISpinnerIcon
|
|
45
|
+
export { AISpinnerIcon }
|
package/src/Spinner/v2/README.md
CHANGED
|
@@ -28,7 +28,7 @@ dark backgrounds.
|
|
|
28
28
|
---
|
|
29
29
|
type: example
|
|
30
30
|
---
|
|
31
|
-
<View background="
|
|
31
|
+
<View background="info" as="div" >
|
|
32
32
|
<Spinner renderTitle="Loading" variant="inverse" />
|
|
33
33
|
</View>
|
|
34
34
|
```
|
|
@@ -61,3 +61,23 @@ type: example
|
|
|
61
61
|
<Spinner renderTitle={() => "Hello world"} />
|
|
62
62
|
</div>
|
|
63
63
|
```
|
|
64
|
+
|
|
65
|
+
### AI variants
|
|
66
|
+
|
|
67
|
+
Use the `ai` variants to show that an AI feature is working. Use `ai-on-color`
|
|
68
|
+
on colored backgrounds.
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
---
|
|
72
|
+
type: example
|
|
73
|
+
---
|
|
74
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
|
75
|
+
<Spinner renderTitle="Loading" variant="ai" size="x-small" />
|
|
76
|
+
<Spinner renderTitle="Loading" variant="ai" size="small" />
|
|
77
|
+
<Spinner renderTitle="Loading" variant="ai" />
|
|
78
|
+
<Spinner renderTitle="Loading" variant="ai" size="large" />
|
|
79
|
+
<View background="info" as="div" padding="small">
|
|
80
|
+
<Spinner renderTitle="Loading" variant="ai-on-color" />
|
|
81
|
+
</View>
|
|
82
|
+
</div>
|
|
83
|
+
```
|
package/src/Spinner/v2/index.tsx
CHANGED
|
@@ -30,8 +30,10 @@ import {
|
|
|
30
30
|
omitProps,
|
|
31
31
|
useDeterministicId
|
|
32
32
|
} from '@instructure/ui-react-utils'
|
|
33
|
+
import { ScreenReaderContent } from '@instructure/ui-a11y-content'
|
|
33
34
|
import { logError as error } from '@instructure/console'
|
|
34
35
|
|
|
36
|
+
import { AISpinnerIcon } from './AISpinnerIcon.js'
|
|
35
37
|
import generateStyle from './styles.js'
|
|
36
38
|
import type { SpinnerProps } from './props'
|
|
37
39
|
import { allowedProps } from './props.js'
|
|
@@ -78,6 +80,48 @@ const Spinner = forwardRef<HTMLDivElement, SpinnerProps>((props, ref) => {
|
|
|
78
80
|
return undefined
|
|
79
81
|
}, [delay])
|
|
80
82
|
|
|
83
|
+
const isAI = variant === 'ai' || variant === 'ai-on-color'
|
|
84
|
+
|
|
85
|
+
const renderCircle = () => (
|
|
86
|
+
<svg
|
|
87
|
+
css={styles?.circle}
|
|
88
|
+
role="img"
|
|
89
|
+
aria-labelledby={titleId}
|
|
90
|
+
focusable="false"
|
|
91
|
+
>
|
|
92
|
+
<title id={titleId}>{callRenderProp(renderTitle)}</title>
|
|
93
|
+
<g role="presentation">
|
|
94
|
+
{variant === 'default' && (
|
|
95
|
+
<circle
|
|
96
|
+
css={styles?.circleTrack}
|
|
97
|
+
cx="50%"
|
|
98
|
+
cy="50%"
|
|
99
|
+
r={styles?.radius as string}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
<circle
|
|
103
|
+
css={styles?.circleSpin}
|
|
104
|
+
cx="50%"
|
|
105
|
+
cy="50%"
|
|
106
|
+
r={styles?.radius as string}
|
|
107
|
+
/>
|
|
108
|
+
</g>
|
|
109
|
+
</svg>
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// The icon accepts only a string `title`, but `renderTitle` can be any node.
|
|
113
|
+
// So the label goes on the wrapping element instead.
|
|
114
|
+
const renderAIIcon = () => (
|
|
115
|
+
<>
|
|
116
|
+
<ScreenReaderContent id={titleId}>
|
|
117
|
+
{callRenderProp(renderTitle)}
|
|
118
|
+
</ScreenReaderContent>
|
|
119
|
+
<span css={styles?.aiSpin}>
|
|
120
|
+
<AISpinnerIcon color={variant === 'ai' ? 'ai' : 'onColor'} />
|
|
121
|
+
</span>
|
|
122
|
+
</>
|
|
123
|
+
)
|
|
124
|
+
|
|
81
125
|
const renderSpinner = () => {
|
|
82
126
|
error(
|
|
83
127
|
!!renderTitle,
|
|
@@ -88,35 +132,13 @@ const Spinner = forwardRef<HTMLDivElement, SpinnerProps>((props, ref) => {
|
|
|
88
132
|
|
|
89
133
|
return (
|
|
90
134
|
<div
|
|
135
|
+
{...(isAI && { role: 'img', 'aria-labelledby': titleId })}
|
|
91
136
|
{...passthroughProps}
|
|
92
137
|
css={styles?.spinner}
|
|
93
138
|
ref={ref}
|
|
94
139
|
data-cid="Spinner"
|
|
95
140
|
>
|
|
96
|
-
|
|
97
|
-
css={styles?.circle}
|
|
98
|
-
role="img"
|
|
99
|
-
aria-labelledby={titleId}
|
|
100
|
-
focusable="false"
|
|
101
|
-
>
|
|
102
|
-
<title id={titleId}>{callRenderProp(renderTitle)}</title>
|
|
103
|
-
<g role="presentation">
|
|
104
|
-
{variant !== 'inverse' && (
|
|
105
|
-
<circle
|
|
106
|
-
css={styles?.circleTrack}
|
|
107
|
-
cx="50%"
|
|
108
|
-
cy="50%"
|
|
109
|
-
r={styles?.radius as string}
|
|
110
|
-
/>
|
|
111
|
-
)}
|
|
112
|
-
<circle
|
|
113
|
-
css={styles?.circleSpin}
|
|
114
|
-
cx="50%"
|
|
115
|
-
cy="50%"
|
|
116
|
-
r={styles?.radius as string}
|
|
117
|
-
/>
|
|
118
|
-
</g>
|
|
119
|
-
</svg>
|
|
141
|
+
{isAI ? renderAIIcon() : renderCircle()}
|
|
120
142
|
</div>
|
|
121
143
|
)
|
|
122
144
|
}
|
package/src/Spinner/v2/props.ts
CHANGED
|
@@ -52,9 +52,11 @@ type SpinnerOwnProps = {
|
|
|
52
52
|
*/
|
|
53
53
|
size?: 'x-small' | 'small' | 'medium' | 'large'
|
|
54
54
|
/**
|
|
55
|
-
* Different color schemes for use with light or dark backgrounds
|
|
55
|
+
* Different color schemes for use with light or dark backgrounds.
|
|
56
|
+
* The `ai` variants show the AI icon instead of the circle. `ai` uses the
|
|
57
|
+
* theme's AI gradient, and `ai-on-color` is for colored backgrounds.
|
|
56
58
|
*/
|
|
57
|
-
variant?: 'default' | 'inverse'
|
|
59
|
+
variant?: 'default' | 'inverse' | 'ai' | 'ai-on-color'
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
type PropKeys = keyof SpinnerOwnProps
|
|
@@ -67,7 +69,7 @@ type SpinnerProps = SpinnerOwnProps &
|
|
|
67
69
|
WithDeterministicIdProps
|
|
68
70
|
|
|
69
71
|
type SpinnerStyle = ComponentStyle<
|
|
70
|
-
'spinner' | 'circle' | 'circleTrack' | 'circleSpin' | 'radius'
|
|
72
|
+
'spinner' | 'circle' | 'circleTrack' | 'circleSpin' | 'radius' | 'aiSpin'
|
|
71
73
|
>
|
|
72
74
|
|
|
73
75
|
const allowedProps: AllowedPropKeys = [
|