@spark-web/button 2.0.0-rc.0 → 2.0.0-rc.10

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.
@@ -0,0 +1,492 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
6
+ var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
7
+ var box = require('@spark-web/box');
8
+ var utils = require('@spark-web/utils');
9
+ var react = require('react');
10
+ var jsxRuntime = require('@emotion/react/jsx-runtime');
11
+ var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
12
+ var react$1 = require('@emotion/react');
13
+ var a11y = require('@spark-web/a11y');
14
+ var spinner = require('@spark-web/spinner');
15
+ var text = require('@spark-web/text');
16
+ var theme = require('@spark-web/theme');
17
+ var link = require('@spark-web/link');
18
+ var ts = require('@spark-web/utils/ts');
19
+
20
+ var _excluded$2 = ["onClick", "disabled", "type"];
21
+ var BaseButton = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
22
+ var onClickProp = _ref.onClick,
23
+ _ref$disabled = _ref.disabled,
24
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
25
+ _ref$type = _ref.type,
26
+ type = _ref$type === void 0 ? 'button' : _ref$type,
27
+ consumerProps = _objectWithoutProperties(_ref, _excluded$2);
28
+ var internalRef = react.useRef(null);
29
+ var composedRef = utils.useComposedRefs(internalRef, forwardedRef);
30
+ /**
31
+ * In Safari buttons are not focused automatically by the browser once
32
+ * pressed, the default behaviour is to focus the nearest focusable ancestor.
33
+ * To fix this we need to manually focus the button element after the user
34
+ * presses the element.
35
+ */
36
+ var onClick = react.useCallback(function (event) {
37
+ var _internalRef$current;
38
+ (_internalRef$current = internalRef.current) === null || _internalRef$current === void 0 || _internalRef$current.focus();
39
+ var preventableClickHandler = getPreventableClickHandler(onClickProp, disabled);
40
+ preventableClickHandler(event);
41
+ }, [disabled, onClickProp]);
42
+ return jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread({}, consumerProps), {}, {
43
+ as: "button",
44
+ ref: composedRef
45
+ // Hide aria-disabled attribute when button is not disabled
46
+ ,
47
+ "aria-disabled": disabled || undefined,
48
+ onClick: onClick,
49
+ type: type
50
+ }));
51
+ });
52
+ BaseButton.displayName = 'BaseButton';
53
+
54
+ /**
55
+ * handle "disabled" behaviour w/o disabling buttons
56
+ * @see https://axesslab.com/disabled-buttons-suck/
57
+ */
58
+ function getPreventableClickHandler(onClick, disabled) {
59
+ return function handleClick(event) {
60
+ if (disabled) {
61
+ event.preventDefault();
62
+ } else {
63
+ onClick === null || onClick === void 0 || onClick(event);
64
+ }
65
+ };
66
+ }
67
+
68
+ var highDisabledStyles = {
69
+ backgroundDisabled: 'disabled',
70
+ borderDisabled: 'fieldDisabled',
71
+ textToneDisabled: 'neutralInverted'
72
+ };
73
+ var highDisabledAltStyles = {
74
+ backgroundDisabled: 'neutral',
75
+ borderDisabled: 'standard',
76
+ textToneDisabled: 'placeholder'
77
+ };
78
+ var lowDisabledStyles = {
79
+ backgroundDisabled: 'inputDisabled',
80
+ textToneDisabled: 'disabled'
81
+ };
82
+ var lowDisabledAltStyles = {
83
+ backgroundDisabled: 'inputDisabled',
84
+ borderDisabled: 'fieldDisabled',
85
+ textToneDisabled: 'disabled'
86
+ };
87
+ var noneDisabledStyles = {
88
+ backgroundDisabled: 'neutral',
89
+ textToneDisabled: 'disabled'
90
+ };
91
+ var variants = {
92
+ high: {
93
+ primary: _objectSpread({
94
+ background: 'primary',
95
+ backgroundHover: 'primaryHover',
96
+ backgroundActive: 'primaryActive'
97
+ }, highDisabledStyles),
98
+ secondary: _objectSpread({
99
+ background: 'secondary',
100
+ backgroundHover: 'secondaryHover',
101
+ backgroundActive: 'secondaryActive'
102
+ }, highDisabledStyles),
103
+ neutral: _objectSpread({
104
+ background: 'neutral',
105
+ border: 'field',
106
+ backgroundHover: 'neutralHover',
107
+ backgroundActive: 'neutralActive'
108
+ }, highDisabledAltStyles),
109
+ positive: _objectSpread({
110
+ background: 'positive',
111
+ backgroundHover: 'positiveHover',
112
+ backgroundActive: 'positiveActive'
113
+ }, highDisabledStyles),
114
+ critical: _objectSpread({
115
+ background: 'critical',
116
+ backgroundHover: 'criticalHover',
117
+ backgroundActive: 'criticalActive'
118
+ }, highDisabledStyles),
119
+ caution: undefined,
120
+ info: undefined
121
+ },
122
+ low: {
123
+ primary: _objectSpread({
124
+ background: 'surface',
125
+ border: 'primary',
126
+ borderWidth: 'large',
127
+ textTone: 'primary',
128
+ backgroundHover: 'none',
129
+ borderHover: 'primaryHover',
130
+ textToneHover: 'primaryHover',
131
+ backgroundActive: 'none',
132
+ borderActive: 'primaryActive',
133
+ textToneActive: 'primaryActive'
134
+ }, lowDisabledAltStyles),
135
+ secondary: _objectSpread({
136
+ background: 'surface',
137
+ border: 'secondary',
138
+ borderWidth: 'large',
139
+ textTone: 'secondary',
140
+ backgroundHover: 'none',
141
+ borderHover: 'secondaryHover',
142
+ textToneHover: 'secondaryHover',
143
+ backgroundActive: 'none',
144
+ borderActive: 'secondaryActive',
145
+ textToneActive: 'secondaryActive'
146
+ }, lowDisabledAltStyles),
147
+ neutral: _objectSpread({
148
+ background: 'neutralLow',
149
+ backgroundHover: 'neutralLowHover',
150
+ backgroundActive: 'neutralLowActive'
151
+ }, lowDisabledStyles),
152
+ positive: _objectSpread({
153
+ background: 'positiveLow',
154
+ backgroundHover: 'positiveLowHover',
155
+ backgroundActive: 'positiveLowActive'
156
+ }, lowDisabledStyles),
157
+ caution: _objectSpread({
158
+ background: 'cautionLow',
159
+ backgroundHover: 'cautionLowHover',
160
+ backgroundActive: 'cautionLowActive'
161
+ }, lowDisabledStyles),
162
+ critical: _objectSpread({
163
+ background: 'criticalLow',
164
+ backgroundHover: 'criticalLowHover',
165
+ backgroundActive: 'criticalLowActive'
166
+ }, lowDisabledStyles),
167
+ info: _objectSpread({
168
+ background: 'infoLow',
169
+ backgroundHover: 'infoLowHover',
170
+ backgroundActive: 'infoLowActive'
171
+ }, lowDisabledStyles)
172
+ },
173
+ none: {
174
+ primary: _objectSpread({
175
+ background: 'surface',
176
+ textTone: 'primaryActive',
177
+ backgroundHover: 'primaryLowHover',
178
+ backgroundActive: 'primaryLowActive'
179
+ }, noneDisabledStyles),
180
+ secondary: _objectSpread({
181
+ background: 'surface',
182
+ textTone: 'secondaryActive',
183
+ backgroundHover: 'secondaryLowHover',
184
+ backgroundActive: 'secondaryLowActive'
185
+ }, noneDisabledStyles),
186
+ neutral: _objectSpread({
187
+ background: 'surface',
188
+ textTone: 'neutral',
189
+ backgroundHover: 'neutralLowHover',
190
+ backgroundActive: 'neutralLowActive'
191
+ }, noneDisabledStyles),
192
+ positive: _objectSpread({
193
+ background: 'surface',
194
+ textTone: 'positive',
195
+ backgroundHover: 'positiveLowHover',
196
+ backgroundActive: 'positiveLowActive'
197
+ }, noneDisabledStyles),
198
+ caution: _objectSpread({
199
+ background: 'surface',
200
+ textTone: 'caution',
201
+ backgroundHover: 'cautionLowHover',
202
+ backgroundActive: 'cautionLowActive'
203
+ }, noneDisabledStyles),
204
+ critical: _objectSpread({
205
+ background: 'surface',
206
+ textTone: 'critical',
207
+ backgroundHover: 'criticalLowHover',
208
+ backgroundActive: 'criticalLowActive'
209
+ }, noneDisabledStyles),
210
+ info: _objectSpread({
211
+ background: 'surface',
212
+ textTone: 'info',
213
+ backgroundHover: 'infoLowHover',
214
+ backgroundActive: 'infoLowActive'
215
+ }, noneDisabledStyles)
216
+ }
217
+ };
218
+ var mapTokens = {
219
+ fontSize: {
220
+ medium: 'small',
221
+ large: 'standard'
222
+ },
223
+ size: {
224
+ medium: 'medium',
225
+ large: 'large'
226
+ },
227
+ spacing: {
228
+ medium: 'medium',
229
+ large: 'xlarge'
230
+ }
231
+ };
232
+
233
+ var resolveButtonChildren = function resolveButtonChildren(_ref) {
234
+ var children = _ref.children,
235
+ isLoading = _ref.isLoading,
236
+ prominence = _ref.prominence,
237
+ size = _ref.size,
238
+ tone = _ref.tone;
239
+ var variant = variants[prominence][tone];
240
+ return react.Children.map(children, function (child) {
241
+ if (typeof child === 'string' || typeof child === 'number') {
242
+ return jsxRuntime.jsx(HiddenWhenLoading, {
243
+ isLoading: isLoading,
244
+ children: jsxRuntime.jsx(text.Text, {
245
+ as: "span",
246
+ baseline: false,
247
+ overflowStrategy: "nowrap",
248
+ weight: "semibold",
249
+ size: mapTokens.fontSize[size],
250
+ tone: variant === null || variant === void 0 ? void 0 : variant.textTone,
251
+ children: child
252
+ })
253
+ });
254
+ }
255
+ if ( /*#__PURE__*/react.isValidElement(child)) {
256
+ return jsxRuntime.jsx(HiddenWhenLoading, {
257
+ isLoading: isLoading,
258
+ children: /*#__PURE__*/react.cloneElement(child, {
259
+ // Dismiss buttons need to be `xxsmall`
260
+ // For everything else, we force them to be `xsmall`
261
+ size: child.props.size === 'xxsmall' ? child.props.size : 'xsmall',
262
+ // If the button is low prominence with a decorative tone we want to force
263
+ // the tone to be the same as the button
264
+ // We also don't want users to override the tone of the icon inside of the button
265
+ tone: variant === null || variant === void 0 ? void 0 : variant.textTone
266
+ })
267
+ });
268
+ }
269
+ return null;
270
+ });
271
+ };
272
+ function HiddenWhenLoading(_ref2) {
273
+ var children = _ref2.children,
274
+ isLoading = _ref2.isLoading;
275
+ return jsxRuntime.jsx(box.Box, {
276
+ as: "span",
277
+ display: "inline-flex",
278
+ alignItems: "center",
279
+ justifyContent: "center",
280
+ opacity: isLoading ? 0 : undefined,
281
+ children: children
282
+ });
283
+ }
284
+
285
+ ////////////////////////////////////////////////////////////////////////////////
286
+
287
+ /**
288
+ * useButtonStyles
289
+ *
290
+ * Custom hook for styling buttons and certain links.
291
+ * Returns a tuple where the first item is an object of props to spread onto the
292
+ * underlying `Box` component, and the second item is a CSS object that can be
293
+ * passed to Emotion's `css` function.
294
+ */
295
+ function useButtonStyles(_ref) {
296
+ var iconOnly = _ref.iconOnly,
297
+ prominence = _ref.prominence,
298
+ size = _ref.size,
299
+ tone = _ref.tone;
300
+ var theme$1 = theme.useTheme();
301
+ var focusRingStyles = a11y.useFocusRing({
302
+ tone: tone
303
+ });
304
+ var disabledFocusRingStyles = a11y.useFocusRing({
305
+ tone: 'disabled'
306
+ });
307
+ var variant = variants[prominence][tone];
308
+ var isLarge = size === 'large';
309
+ var transitionColors = {
310
+ transitionProperty: 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
311
+ transitionTimingFunction: theme$1.animation.standard.easing,
312
+ transitionDuration: "".concat(theme$1.animation.standard.duration, "ms")
313
+ };
314
+ return [{
315
+ alignItems: 'center',
316
+ background: variant === null || variant === void 0 ? void 0 : variant.background,
317
+ border: variant === null || variant === void 0 ? void 0 : variant.border,
318
+ borderWidth: variant === null || variant === void 0 ? void 0 : variant.borderWidth,
319
+ borderRadius: isLarge ? 'medium' : 'small',
320
+ cursor: 'pointer',
321
+ display: 'inline-flex',
322
+ gap: 'small',
323
+ height: mapTokens.size[size],
324
+ justifyContent: 'center',
325
+ paddingX: iconOnly ? undefined : mapTokens.spacing[size],
326
+ position: 'relative',
327
+ width: iconOnly ? mapTokens.size[size] : undefined
328
+ }, _objectSpread(_objectSpread({}, transitionColors), {}, {
329
+ // Styles for buttons that aren't disabled.
330
+ // Using the :not() pseudo-class so we don't have to undo the styles when
331
+ // the button is disabled.
332
+ '&:not([aria-disabled=true])': {
333
+ ':hover': {
334
+ borderColor: variant !== null && variant !== void 0 && variant.borderHover ? theme$1.border.color[variant.borderHover] : undefined,
335
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundHover ? theme$1.backgroundInteractions[variant.backgroundHover] : undefined,
336
+ // Style button text when hovering
337
+ '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
338
+ color: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined,
339
+ stroke: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined
340
+ })
341
+ },
342
+ ':active': {
343
+ borderColor: variant !== null && variant !== void 0 && variant.borderActive ? theme$1.border.color[variant.borderActive] : undefined,
344
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundActive ? theme$1.backgroundInteractions[variant === null || variant === void 0 ? void 0 : variant.backgroundActive] : undefined,
345
+ transform: 'scale(0.98)',
346
+ // Style button text when it's active
347
+ '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
348
+ color: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined,
349
+ stroke: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined
350
+ })
351
+ },
352
+ ':focus': focusRingStyles
353
+ },
354
+ '&[aria-disabled=true]': {
355
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundDisabled ? theme$1.color.background[variant === null || variant === void 0 ? void 0 : variant.backgroundDisabled] : undefined,
356
+ borderColor: variant !== null && variant !== void 0 && variant.borderDisabled ? theme$1.border.color[variant.borderDisabled] : undefined,
357
+ cursor: 'default',
358
+ '*': {
359
+ color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined,
360
+ stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined
361
+ },
362
+ ':focus': disabledFocusRingStyles
363
+ }
364
+ })];
365
+ }
366
+
367
+ var _excluded$1 = ["aria-controls", "aria-describedby", "aria-expanded", "data", "disabled", "id", "loading", "onClick", "prominence", "size", "tone", "type"];
368
+ /**
369
+ * Buttons are used to initialize an action, their label should express what
370
+ * action will occur when the user interacts with it.
371
+ */
372
+ var Button = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
373
+ var ariaControls = _ref['aria-controls'],
374
+ ariaDescribedBy = _ref['aria-describedby'],
375
+ ariaExpanded = _ref['aria-expanded'],
376
+ data = _ref.data,
377
+ disabled = _ref.disabled,
378
+ id = _ref.id,
379
+ _ref$loading = _ref.loading,
380
+ loading = _ref$loading === void 0 ? false : _ref$loading,
381
+ onClick = _ref.onClick,
382
+ _ref$prominence = _ref.prominence,
383
+ prominence = _ref$prominence === void 0 ? 'high' : _ref$prominence,
384
+ _ref$size = _ref.size,
385
+ size = _ref$size === void 0 ? 'medium' : _ref$size,
386
+ _ref$tone = _ref.tone,
387
+ tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
388
+ type = _ref.type,
389
+ props = _objectWithoutProperties(_ref, _excluded$1);
390
+ var iconOnly = Boolean(props.label);
391
+ var _useButtonStyles = useButtonStyles({
392
+ iconOnly: iconOnly,
393
+ size: size,
394
+ tone: tone,
395
+ prominence: prominence
396
+ }),
397
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
398
+ boxProps = _useButtonStyles2[0],
399
+ buttonStyles = _useButtonStyles2[1];
400
+ var variant = variants[prominence][tone];
401
+ return jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
402
+ "aria-controls": ariaControls,
403
+ "aria-describedby": ariaDescribedBy,
404
+ "aria-expanded": ariaExpanded,
405
+ "aria-label": props.label,
406
+ css: react$1.css(buttonStyles),
407
+ data: data,
408
+ disabled: loading || disabled,
409
+ id: id,
410
+ onClick: onClick,
411
+ ref: forwardedRef,
412
+ type: type,
413
+ children: [resolveButtonChildren(_objectSpread(_objectSpread({}, props), {}, {
414
+ isLoading: loading,
415
+ prominence: prominence,
416
+ size: size,
417
+ tone: tone
418
+ })), loading && jsxRuntime.jsx(Loading, {
419
+ tone: variant === null || variant === void 0 ? void 0 : variant.textTone
420
+ })]
421
+ }));
422
+ });
423
+ Button.displayName = 'Button';
424
+ function Loading(_ref2) {
425
+ var tone = _ref2.tone;
426
+ return jsxRuntime.jsxs(box.Box, {
427
+ as: "span",
428
+ position: "absolute",
429
+ top: 0,
430
+ bottom: 0,
431
+ left: 0,
432
+ right: 0,
433
+ display: "flex",
434
+ alignItems: "center",
435
+ justifyContent: "center",
436
+ children: [jsxRuntime.jsx(a11y.VisuallyHidden, {
437
+ children: "button loading indicator"
438
+ }), jsxRuntime.jsx(spinner.Spinner, {
439
+ size: "xsmall",
440
+ tone: tone
441
+ })]
442
+ });
443
+ }
444
+
445
+ var _excluded = ["data", "href", "target", "id", "prominence", "size", "tone"];
446
+ /** The appearance of a `Button`, with the semantics of a link. */
447
+ var ButtonLink = ts.forwardRefWithAs(function (_ref, forwardedRef) {
448
+ var data = _ref.data,
449
+ href = _ref.href,
450
+ target = _ref.target,
451
+ id = _ref.id,
452
+ _ref$prominence = _ref.prominence,
453
+ prominence = _ref$prominence === void 0 ? 'high' : _ref$prominence,
454
+ _ref$size = _ref.size,
455
+ size = _ref$size === void 0 ? 'medium' : _ref$size,
456
+ _ref$tone = _ref.tone,
457
+ tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
458
+ consumerProps = _objectWithoutProperties(_ref, _excluded);
459
+ var LinkComponent = link.useLinkComponent(forwardedRef);
460
+ var iconOnly = Boolean(consumerProps.label);
461
+ var _useButtonStyles = useButtonStyles({
462
+ iconOnly: iconOnly,
463
+ prominence: prominence,
464
+ size: size,
465
+ tone: tone
466
+ }),
467
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
468
+ boxProps = _useButtonStyles2[0],
469
+ buttonStyles = _useButtonStyles2[1];
470
+ return jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread({}, boxProps), {}, {
471
+ "aria-label": consumerProps.label,
472
+ as: LinkComponent,
473
+ asElement: "a",
474
+ css: react$1.css(buttonStyles),
475
+ data: data,
476
+ href: href,
477
+ id: id,
478
+ target: target,
479
+ ref: forwardedRef,
480
+ children: resolveButtonChildren(_objectSpread(_objectSpread({}, consumerProps), {}, {
481
+ isLoading: false,
482
+ prominence: prominence,
483
+ size: size,
484
+ tone: tone
485
+ }))
486
+ }));
487
+ });
488
+
489
+ exports.BaseButton = BaseButton;
490
+ exports.Button = Button;
491
+ exports.ButtonLink = ButtonLink;
492
+ exports.useButtonStyles = useButtonStyles;