@spark-web/button 1.3.1 → 1.4.0

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.
@@ -1,6 +1,8 @@
1
1
  export { BaseButton } from './BaseButton';
2
2
  export { Button } from './Button';
3
3
  export { ButtonLink } from './ButtonLink';
4
+ export { useButtonStyles } from './useButtonStyles';
4
5
  export type { BaseButtonProps } from './BaseButton';
5
6
  export type { ButtonProps } from './Button';
6
7
  export type { ButtonLinkProps } from './ButtonLink';
8
+ export type { UseButtonStylesProps } from './useButtonStyles';
@@ -1,10 +1,88 @@
1
- import type { BoxProps } from '@spark-web/box';
2
1
  import type { ButtonProminence, ButtonSize, ButtonTone } from './types';
3
- declare type UseButtonStylesProps = {
2
+ /**
3
+ * useButtonStyles
4
+ *
5
+ * Custom hook for styling buttons and certain links.
6
+ * Returns a tuple where the first item is an object of props to spread onto the
7
+ * underlying `Box` component, and the second item is a CSS object that can be
8
+ * passed to Emotion's `css` function.
9
+ */
10
+ export declare function useButtonStyles({ iconOnly, prominence, size, tone, }: UseButtonStylesProps): readonly [{
11
+ readonly alignItems: "center";
12
+ readonly background: "body" | "input" | "infoLight" | "criticalLight" | "positiveLight" | "cautionLight" | "muted" | "disabled" | "backdrop" | "surface" | "surfaceMuted" | "surfacePressed" | "fieldAccent" | "inputPressed" | "inputDisabled" | "accent" | "accentMuted" | "neutral" | "neutralLow" | "primary" | "primaryLow" | "primaryMuted" | "secondary" | "secondaryLow" | "secondaryMuted" | "caution" | "cautionLow" | "cautionMuted" | "critical" | "criticalLow" | "criticalMuted" | "info" | "infoLow" | "infoMuted" | "positive" | "positiveLow" | "positiveMuted" | undefined;
13
+ readonly border: import("@spark-web/theme").ResponsiveProp<"standard" | "fieldAccent" | "accent" | "accentMuted" | "neutral" | "primary" | "secondary" | "caution" | "cautionMuted" | "critical" | "criticalMuted" | "info" | "infoMuted" | "positive" | "positiveMuted" | "standardInverted" | "field" | "fieldHover" | "fieldDisabled" | "primaryHover" | "primaryActive" | "secondaryHover" | "secondaryActive"> | undefined;
14
+ readonly borderWidth: import("@spark-web/theme").ResponsiveProp<"standard" | "large"> | undefined;
15
+ readonly borderRadius: "small" | "medium";
16
+ readonly cursor: "pointer";
17
+ readonly display: "inline-flex";
18
+ readonly gap: "small";
19
+ readonly height: "large" | "medium";
20
+ readonly justifyContent: "center";
21
+ readonly paddingX: "medium" | "xlarge" | undefined;
22
+ readonly position: "relative";
23
+ readonly width: "large" | "medium" | undefined;
24
+ }, {
25
+ readonly '&:not([aria-disabled=true])': {
26
+ readonly ':hover': {
27
+ readonly borderColor: string | undefined;
28
+ readonly backgroundColor: string | undefined;
29
+ readonly '> *': {
30
+ readonly color: string | undefined;
31
+ readonly stroke: string | undefined;
32
+ readonly transitionProperty: string;
33
+ readonly transitionTimingFunction: string;
34
+ readonly transitionDuration: string;
35
+ };
36
+ };
37
+ readonly ':active': {
38
+ readonly borderColor: string | undefined;
39
+ readonly backgroundColor: string | undefined;
40
+ readonly transform: "scale(0.98)";
41
+ readonly '> *': {
42
+ readonly color: string | undefined;
43
+ readonly stroke: string | undefined;
44
+ readonly transitionProperty: string;
45
+ readonly transitionTimingFunction: string;
46
+ readonly transitionDuration: string;
47
+ };
48
+ };
49
+ readonly ':focus': {
50
+ boxShadow: string;
51
+ outline: string;
52
+ outlineOffset: string;
53
+ } | {
54
+ outline: string;
55
+ outlineOffset: string;
56
+ };
57
+ };
58
+ readonly '&[aria-disabled=true]': {
59
+ readonly backgroundColor: string | undefined;
60
+ readonly borderColor: string | undefined;
61
+ readonly cursor: "default";
62
+ readonly '*': {
63
+ readonly color: string | undefined;
64
+ readonly stroke: string | undefined;
65
+ };
66
+ readonly ':focus': {
67
+ boxShadow: string;
68
+ outline: string;
69
+ outlineOffset: string;
70
+ } | {
71
+ outline: string;
72
+ outlineOffset: string;
73
+ };
74
+ };
75
+ readonly transitionProperty: string;
76
+ readonly transitionTimingFunction: string;
77
+ readonly transitionDuration: string;
78
+ }];
79
+ export declare type UseButtonStylesProps = {
80
+ /** Whether the children of the button is a single icon or not. */
4
81
  iconOnly: boolean;
82
+ /** Sets the visual prominence of the button. */
5
83
  prominence: ButtonProminence;
84
+ /** Sets the size of the button. */
6
85
  size: ButtonSize;
86
+ /** Sets the tone of the button. */
7
87
  tone: ButtonTone;
8
88
  };
9
- export declare function useButtonStyles({ iconOnly, prominence, size, tone, }: UseButtonStylesProps): Partial<BoxProps>;
10
- export {};
@@ -8,13 +8,13 @@ var box = require('@spark-web/box');
8
8
  var utils = require('@spark-web/utils');
9
9
  var react = require('react');
10
10
  var jsxRuntime = require('react/jsx-runtime');
11
+ var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
12
+ var css = require('@emotion/css');
11
13
  var a11y = require('@spark-web/a11y');
12
14
  var spinner = require('@spark-web/spinner');
13
15
  var text = require('@spark-web/text');
14
- var css = require('@emotion/css');
15
16
  var theme = require('@spark-web/theme');
16
17
  var link = require('@spark-web/link');
17
- var internal = require('@spark-web/utils/internal');
18
18
  var ts = require('@spark-web/utils/ts');
19
19
 
20
20
  var _excluded$2 = ["onClick", "disabled", "type"];
@@ -44,8 +44,9 @@ var BaseButton = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
44
44
  }, [disabled, onClickProp]);
45
45
  return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread({}, consumerProps), {}, {
46
46
  as: "button",
47
- ref: composedRef,
48
- "aria-disabled": disabled,
47
+ ref: composedRef // Hide aria-disabled attribute when button is not disabled
48
+ ,
49
+ "aria-disabled": disabled || undefined,
49
50
  onClick: onClick,
50
51
  type: type
51
52
  }));
@@ -286,6 +287,15 @@ function HiddenWhenLoading(_ref2) {
286
287
  });
287
288
  }
288
289
 
290
+ /**
291
+ * useButtonStyles
292
+ *
293
+ * Custom hook for styling buttons and certain links.
294
+ * Returns a tuple where the first item is an object of props to spread onto the
295
+ * underlying `Box` component, and the second item is a CSS object that can be
296
+ * passed to Emotion's `css` function.
297
+ */
298
+
289
299
  function useButtonStyles(_ref) {
290
300
  var iconOnly = _ref.iconOnly,
291
301
  prominence = _ref.prominence,
@@ -301,11 +311,11 @@ function useButtonStyles(_ref) {
301
311
  var variant = variants[prominence][tone];
302
312
  var isLarge = size === 'large';
303
313
  var transitionColors = {
304
- transitionProperty: 'color, background-color, border-color, text-decoration-color',
305
- transitionTimingFunction: 'cubic-bezier(0.02, 1.505, 0.745, 1.235)',
314
+ transitionProperty: 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
315
+ transitionTimingFunction: theme$1.animation.standard.easing,
306
316
  transitionDuration: "".concat(theme$1.animation.standard.duration, "ms")
307
317
  };
308
- var buttonStyleProps = {
318
+ return [{
309
319
  alignItems: 'center',
310
320
  background: variant === null || variant === void 0 ? void 0 : variant.background,
311
321
  border: variant === null || variant === void 0 ? void 0 : variant.border,
@@ -318,44 +328,44 @@ function useButtonStyles(_ref) {
318
328
  justifyContent: 'center',
319
329
  paddingX: iconOnly ? undefined : mapTokens.spacing[size],
320
330
  position: 'relative',
321
- width: iconOnly ? mapTokens.size[size] : undefined,
322
- // interactions styles
323
- className: css.css(_objectSpread(_objectSpread({}, transitionColors), {}, {
324
- '&:not([aria-disabled=true])': {
325
- ':hover': {
326
- borderColor: variant !== null && variant !== void 0 && variant.borderHover ? theme$1.border.color[variant.borderHover] : undefined,
327
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundHover ? theme$1.backgroundInteractions[variant.backgroundHover] : undefined,
328
- // Style button text when hovering
329
- '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
330
- color: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined,
331
- stroke: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined
332
- })
333
- },
334
- ':active': {
335
- borderColor: variant !== null && variant !== void 0 && variant.borderActive ? theme$1.border.color[variant.borderActive] : undefined,
336
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundActive ? theme$1.backgroundInteractions[variant === null || variant === void 0 ? void 0 : variant.backgroundActive] : undefined,
337
- transform: 'scale(0.98)',
338
- // Style button text when it's active
339
- '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
340
- color: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined,
341
- stroke: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined
342
- })
343
- },
344
- ':focus': focusRingStyles
331
+ width: iconOnly ? mapTokens.size[size] : undefined
332
+ }, _objectSpread(_objectSpread({}, transitionColors), {}, {
333
+ // Styles for buttons that aren't disabled.
334
+ // Using the :not() pseudo-class so we don't have to undo the styles when
335
+ // the button is disabled.
336
+ '&:not([aria-disabled=true])': {
337
+ ':hover': {
338
+ borderColor: variant !== null && variant !== void 0 && variant.borderHover ? theme$1.border.color[variant.borderHover] : undefined,
339
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundHover ? theme$1.backgroundInteractions[variant.backgroundHover] : undefined,
340
+ // Style button text when hovering
341
+ '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
342
+ color: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined,
343
+ stroke: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined
344
+ })
345
345
  },
346
- '&[aria-disabled=true]': {
347
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundDisabled ? theme$1.color.background[variant === null || variant === void 0 ? void 0 : variant.backgroundDisabled] : undefined,
348
- borderColor: variant !== null && variant !== void 0 && variant.borderDisabled ? theme$1.border.color[variant.borderDisabled] : undefined,
349
- cursor: 'default',
350
- '*': {
351
- color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined,
352
- stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined
353
- },
354
- ':focus': disabledFocusRingStyles
355
- }
356
- }))
357
- };
358
- return buttonStyleProps;
346
+ ':active': {
347
+ borderColor: variant !== null && variant !== void 0 && variant.borderActive ? theme$1.border.color[variant.borderActive] : undefined,
348
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundActive ? theme$1.backgroundInteractions[variant === null || variant === void 0 ? void 0 : variant.backgroundActive] : undefined,
349
+ transform: 'scale(0.98)',
350
+ // Style button text when it's active
351
+ '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
352
+ color: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined,
353
+ stroke: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined
354
+ })
355
+ },
356
+ ':focus': focusRingStyles
357
+ },
358
+ '&[aria-disabled=true]': {
359
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundDisabled ? theme$1.color.background[variant === null || variant === void 0 ? void 0 : variant.backgroundDisabled] : undefined,
360
+ borderColor: variant !== null && variant !== void 0 && variant.borderDisabled ? theme$1.border.color[variant.borderDisabled] : undefined,
361
+ cursor: 'default',
362
+ '*': {
363
+ color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined,
364
+ stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined
365
+ },
366
+ ':focus': disabledFocusRingStyles
367
+ }
368
+ })];
359
369
  }
360
370
 
361
371
  var _excluded$1 = ["aria-controls", "aria-describedby", "aria-expanded", "data", "disabled", "id", "loading", "onClick", "prominence", "size", "tone", "type"];
@@ -384,20 +394,26 @@ var Button = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
384
394
  props = _objectWithoutProperties(_ref, _excluded$1);
385
395
 
386
396
  var iconOnly = Boolean(props.label);
387
- var buttonStyleProps = useButtonStyles({
397
+
398
+ var _useButtonStyles = useButtonStyles({
388
399
  iconOnly: iconOnly,
389
400
  size: size,
390
401
  tone: tone,
391
402
  prominence: prominence
392
- });
403
+ }),
404
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
405
+ boxProps = _useButtonStyles2[0],
406
+ buttonStyles = _useButtonStyles2[1];
407
+
393
408
  var isDisabled = disabled || loading;
394
409
  var isLoading = loading && !disabled;
395
410
  var variant = variants[prominence][tone];
396
- return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, buttonStyleProps), {}, {
411
+ return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
397
412
  "aria-controls": ariaControls,
398
413
  "aria-describedby": ariaDescribedBy,
399
414
  "aria-expanded": ariaExpanded,
400
415
  "aria-label": props.label,
416
+ className: css.css(buttonStyles),
401
417
  data: data,
402
418
  disabled: isDisabled,
403
419
  id: id,
@@ -440,7 +456,7 @@ function Loading(_ref2) {
440
456
  var _excluded = ["data", "href", "id", "prominence", "size", "tone"];
441
457
 
442
458
  /** The appearance of a `Button`, with the semantics of a link. */
443
- var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
459
+ var ButtonLink = ts.forwardRefWithAs(function (_ref, forwardedRef) {
444
460
  var data = _ref.data,
445
461
  href = _ref.href,
446
462
  id = _ref.id,
@@ -450,25 +466,31 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
450
466
  size = _ref$size === void 0 ? 'medium' : _ref$size,
451
467
  _ref$tone = _ref.tone,
452
468
  tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
453
- props = _objectWithoutProperties(_ref, _excluded);
469
+ consumerProps = _objectWithoutProperties(_ref, _excluded);
454
470
 
455
- var LinkComponent = link.useLinkComponent(ref);
456
- var iconOnly = Boolean(props.label);
457
- var buttonStyleProps = useButtonStyles({
471
+ var LinkComponent = link.useLinkComponent(forwardedRef);
472
+ var iconOnly = Boolean(consumerProps.label);
473
+
474
+ var _useButtonStyles = useButtonStyles({
458
475
  iconOnly: iconOnly,
459
476
  prominence: prominence,
460
477
  size: size,
461
478
  tone: tone
462
- });
463
- return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread(_objectSpread({
464
- "aria-label": props.label,
479
+ }),
480
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
481
+ boxProps = _useButtonStyles2[0],
482
+ buttonStyles = _useButtonStyles2[1];
483
+
484
+ return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread({}, boxProps), {}, {
485
+ "aria-label": consumerProps.label,
465
486
  as: LinkComponent,
466
487
  asElement: "a",
467
- id: id,
488
+ className: css.css(buttonStyles),
489
+ data: data,
468
490
  href: href,
469
- ref: ref
470
- }, buttonStyleProps), data ? internal.buildDataAttributes(data) : undefined), {}, {
471
- children: resolveButtonChildren(_objectSpread(_objectSpread({}, props), {}, {
491
+ id: id,
492
+ ref: forwardedRef,
493
+ children: resolveButtonChildren(_objectSpread(_objectSpread({}, consumerProps), {}, {
472
494
  isLoading: false,
473
495
  prominence: prominence,
474
496
  size: size,
@@ -480,3 +502,4 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
480
502
  exports.BaseButton = BaseButton;
481
503
  exports.Button = Button;
482
504
  exports.ButtonLink = ButtonLink;
505
+ exports.useButtonStyles = useButtonStyles;
@@ -8,13 +8,13 @@ var box = require('@spark-web/box');
8
8
  var utils = require('@spark-web/utils');
9
9
  var react = require('react');
10
10
  var jsxRuntime = require('react/jsx-runtime');
11
+ var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
12
+ var css = require('@emotion/css');
11
13
  var a11y = require('@spark-web/a11y');
12
14
  var spinner = require('@spark-web/spinner');
13
15
  var text = require('@spark-web/text');
14
- var css = require('@emotion/css');
15
16
  var theme = require('@spark-web/theme');
16
17
  var link = require('@spark-web/link');
17
- var internal = require('@spark-web/utils/internal');
18
18
  var ts = require('@spark-web/utils/ts');
19
19
 
20
20
  var _excluded$2 = ["onClick", "disabled", "type"];
@@ -44,8 +44,9 @@ var BaseButton = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
44
44
  }, [disabled, onClickProp]);
45
45
  return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread({}, consumerProps), {}, {
46
46
  as: "button",
47
- ref: composedRef,
48
- "aria-disabled": disabled,
47
+ ref: composedRef // Hide aria-disabled attribute when button is not disabled
48
+ ,
49
+ "aria-disabled": disabled || undefined,
49
50
  onClick: onClick,
50
51
  type: type
51
52
  }));
@@ -286,6 +287,15 @@ function HiddenWhenLoading(_ref2) {
286
287
  });
287
288
  }
288
289
 
290
+ /**
291
+ * useButtonStyles
292
+ *
293
+ * Custom hook for styling buttons and certain links.
294
+ * Returns a tuple where the first item is an object of props to spread onto the
295
+ * underlying `Box` component, and the second item is a CSS object that can be
296
+ * passed to Emotion's `css` function.
297
+ */
298
+
289
299
  function useButtonStyles(_ref) {
290
300
  var iconOnly = _ref.iconOnly,
291
301
  prominence = _ref.prominence,
@@ -301,11 +311,11 @@ function useButtonStyles(_ref) {
301
311
  var variant = variants[prominence][tone];
302
312
  var isLarge = size === 'large';
303
313
  var transitionColors = {
304
- transitionProperty: 'color, background-color, border-color, text-decoration-color',
305
- transitionTimingFunction: 'cubic-bezier(0.02, 1.505, 0.745, 1.235)',
314
+ transitionProperty: 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
315
+ transitionTimingFunction: theme$1.animation.standard.easing,
306
316
  transitionDuration: "".concat(theme$1.animation.standard.duration, "ms")
307
317
  };
308
- var buttonStyleProps = {
318
+ return [{
309
319
  alignItems: 'center',
310
320
  background: variant === null || variant === void 0 ? void 0 : variant.background,
311
321
  border: variant === null || variant === void 0 ? void 0 : variant.border,
@@ -318,44 +328,44 @@ function useButtonStyles(_ref) {
318
328
  justifyContent: 'center',
319
329
  paddingX: iconOnly ? undefined : mapTokens.spacing[size],
320
330
  position: 'relative',
321
- width: iconOnly ? mapTokens.size[size] : undefined,
322
- // interactions styles
323
- className: css.css(_objectSpread(_objectSpread({}, transitionColors), {}, {
324
- '&:not([aria-disabled=true])': {
325
- ':hover': {
326
- borderColor: variant !== null && variant !== void 0 && variant.borderHover ? theme$1.border.color[variant.borderHover] : undefined,
327
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundHover ? theme$1.backgroundInteractions[variant.backgroundHover] : undefined,
328
- // Style button text when hovering
329
- '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
330
- color: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined,
331
- stroke: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined
332
- })
333
- },
334
- ':active': {
335
- borderColor: variant !== null && variant !== void 0 && variant.borderActive ? theme$1.border.color[variant.borderActive] : undefined,
336
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundActive ? theme$1.backgroundInteractions[variant === null || variant === void 0 ? void 0 : variant.backgroundActive] : undefined,
337
- transform: 'scale(0.98)',
338
- // Style button text when it's active
339
- '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
340
- color: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined,
341
- stroke: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined
342
- })
343
- },
344
- ':focus': focusRingStyles
331
+ width: iconOnly ? mapTokens.size[size] : undefined
332
+ }, _objectSpread(_objectSpread({}, transitionColors), {}, {
333
+ // Styles for buttons that aren't disabled.
334
+ // Using the :not() pseudo-class so we don't have to undo the styles when
335
+ // the button is disabled.
336
+ '&:not([aria-disabled=true])': {
337
+ ':hover': {
338
+ borderColor: variant !== null && variant !== void 0 && variant.borderHover ? theme$1.border.color[variant.borderHover] : undefined,
339
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundHover ? theme$1.backgroundInteractions[variant.backgroundHover] : undefined,
340
+ // Style button text when hovering
341
+ '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
342
+ color: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined,
343
+ stroke: variant !== null && variant !== void 0 && variant.textToneHover ? theme$1.color.foreground[variant.textToneHover] : undefined
344
+ })
345
345
  },
346
- '&[aria-disabled=true]': {
347
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundDisabled ? theme$1.color.background[variant === null || variant === void 0 ? void 0 : variant.backgroundDisabled] : undefined,
348
- borderColor: variant !== null && variant !== void 0 && variant.borderDisabled ? theme$1.border.color[variant.borderDisabled] : undefined,
349
- cursor: 'default',
350
- '*': {
351
- color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined,
352
- stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined
353
- },
354
- ':focus': disabledFocusRingStyles
355
- }
356
- }))
357
- };
358
- return buttonStyleProps;
346
+ ':active': {
347
+ borderColor: variant !== null && variant !== void 0 && variant.borderActive ? theme$1.border.color[variant.borderActive] : undefined,
348
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundActive ? theme$1.backgroundInteractions[variant === null || variant === void 0 ? void 0 : variant.backgroundActive] : undefined,
349
+ transform: 'scale(0.98)',
350
+ // Style button text when it's active
351
+ '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
352
+ color: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined,
353
+ stroke: variant !== null && variant !== void 0 && variant.textToneActive ? theme$1.color.foreground[variant.textToneActive] : undefined
354
+ })
355
+ },
356
+ ':focus': focusRingStyles
357
+ },
358
+ '&[aria-disabled=true]': {
359
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundDisabled ? theme$1.color.background[variant === null || variant === void 0 ? void 0 : variant.backgroundDisabled] : undefined,
360
+ borderColor: variant !== null && variant !== void 0 && variant.borderDisabled ? theme$1.border.color[variant.borderDisabled] : undefined,
361
+ cursor: 'default',
362
+ '*': {
363
+ color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined,
364
+ stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined
365
+ },
366
+ ':focus': disabledFocusRingStyles
367
+ }
368
+ })];
359
369
  }
360
370
 
361
371
  var _excluded$1 = ["aria-controls", "aria-describedby", "aria-expanded", "data", "disabled", "id", "loading", "onClick", "prominence", "size", "tone", "type"];
@@ -384,20 +394,26 @@ var Button = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
384
394
  props = _objectWithoutProperties(_ref, _excluded$1);
385
395
 
386
396
  var iconOnly = Boolean(props.label);
387
- var buttonStyleProps = useButtonStyles({
397
+
398
+ var _useButtonStyles = useButtonStyles({
388
399
  iconOnly: iconOnly,
389
400
  size: size,
390
401
  tone: tone,
391
402
  prominence: prominence
392
- });
403
+ }),
404
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
405
+ boxProps = _useButtonStyles2[0],
406
+ buttonStyles = _useButtonStyles2[1];
407
+
393
408
  var isDisabled = disabled || loading;
394
409
  var isLoading = loading && !disabled;
395
410
  var variant = variants[prominence][tone];
396
- return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, buttonStyleProps), {}, {
411
+ return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
397
412
  "aria-controls": ariaControls,
398
413
  "aria-describedby": ariaDescribedBy,
399
414
  "aria-expanded": ariaExpanded,
400
415
  "aria-label": props.label,
416
+ className: css.css(buttonStyles),
401
417
  data: data,
402
418
  disabled: isDisabled,
403
419
  id: id,
@@ -440,7 +456,7 @@ function Loading(_ref2) {
440
456
  var _excluded = ["data", "href", "id", "prominence", "size", "tone"];
441
457
 
442
458
  /** The appearance of a `Button`, with the semantics of a link. */
443
- var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
459
+ var ButtonLink = ts.forwardRefWithAs(function (_ref, forwardedRef) {
444
460
  var data = _ref.data,
445
461
  href = _ref.href,
446
462
  id = _ref.id,
@@ -450,25 +466,31 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
450
466
  size = _ref$size === void 0 ? 'medium' : _ref$size,
451
467
  _ref$tone = _ref.tone,
452
468
  tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
453
- props = _objectWithoutProperties(_ref, _excluded);
469
+ consumerProps = _objectWithoutProperties(_ref, _excluded);
454
470
 
455
- var LinkComponent = link.useLinkComponent(ref);
456
- var iconOnly = Boolean(props.label);
457
- var buttonStyleProps = useButtonStyles({
471
+ var LinkComponent = link.useLinkComponent(forwardedRef);
472
+ var iconOnly = Boolean(consumerProps.label);
473
+
474
+ var _useButtonStyles = useButtonStyles({
458
475
  iconOnly: iconOnly,
459
476
  prominence: prominence,
460
477
  size: size,
461
478
  tone: tone
462
- });
463
- return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread(_objectSpread({
464
- "aria-label": props.label,
479
+ }),
480
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
481
+ boxProps = _useButtonStyles2[0],
482
+ buttonStyles = _useButtonStyles2[1];
483
+
484
+ return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread({}, boxProps), {}, {
485
+ "aria-label": consumerProps.label,
465
486
  as: LinkComponent,
466
487
  asElement: "a",
467
- id: id,
488
+ className: css.css(buttonStyles),
489
+ data: data,
468
490
  href: href,
469
- ref: ref
470
- }, buttonStyleProps), data ? internal.buildDataAttributes(data) : undefined), {}, {
471
- children: resolveButtonChildren(_objectSpread(_objectSpread({}, props), {}, {
491
+ id: id,
492
+ ref: forwardedRef,
493
+ children: resolveButtonChildren(_objectSpread(_objectSpread({}, consumerProps), {}, {
472
494
  isLoading: false,
473
495
  prominence: prominence,
474
496
  size: size,
@@ -480,3 +502,4 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
480
502
  exports.BaseButton = BaseButton;
481
503
  exports.Button = Button;
482
504
  exports.ButtonLink = ButtonLink;
505
+ exports.useButtonStyles = useButtonStyles;
@@ -4,13 +4,13 @@ import { Box } from '@spark-web/box';
4
4
  import { useComposedRefs } from '@spark-web/utils';
5
5
  import { forwardRef, useRef, useCallback, Children, isValidElement, cloneElement } from 'react';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import _slicedToArray from '@babel/runtime/helpers/esm/slicedToArray';
8
+ import { css } from '@emotion/css';
7
9
  import { useFocusRing, VisuallyHidden } from '@spark-web/a11y';
8
10
  import { Spinner } from '@spark-web/spinner';
9
11
  import { Text } from '@spark-web/text';
10
- import { css } from '@emotion/css';
11
12
  import { useTheme } from '@spark-web/theme';
12
13
  import { useLinkComponent } from '@spark-web/link';
13
- import { buildDataAttributes } from '@spark-web/utils/internal';
14
14
  import { forwardRefWithAs } from '@spark-web/utils/ts';
15
15
 
16
16
  var _excluded$2 = ["onClick", "disabled", "type"];
@@ -40,8 +40,9 @@ var BaseButton = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
40
40
  }, [disabled, onClickProp]);
41
41
  return /*#__PURE__*/jsx(Box, _objectSpread(_objectSpread({}, consumerProps), {}, {
42
42
  as: "button",
43
- ref: composedRef,
44
- "aria-disabled": disabled,
43
+ ref: composedRef // Hide aria-disabled attribute when button is not disabled
44
+ ,
45
+ "aria-disabled": disabled || undefined,
45
46
  onClick: onClick,
46
47
  type: type
47
48
  }));
@@ -282,6 +283,15 @@ function HiddenWhenLoading(_ref2) {
282
283
  });
283
284
  }
284
285
 
286
+ /**
287
+ * useButtonStyles
288
+ *
289
+ * Custom hook for styling buttons and certain links.
290
+ * Returns a tuple where the first item is an object of props to spread onto the
291
+ * underlying `Box` component, and the second item is a CSS object that can be
292
+ * passed to Emotion's `css` function.
293
+ */
294
+
285
295
  function useButtonStyles(_ref) {
286
296
  var iconOnly = _ref.iconOnly,
287
297
  prominence = _ref.prominence,
@@ -297,11 +307,11 @@ function useButtonStyles(_ref) {
297
307
  var variant = variants[prominence][tone];
298
308
  var isLarge = size === 'large';
299
309
  var transitionColors = {
300
- transitionProperty: 'color, background-color, border-color, text-decoration-color',
301
- transitionTimingFunction: 'cubic-bezier(0.02, 1.505, 0.745, 1.235)',
310
+ transitionProperty: 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
311
+ transitionTimingFunction: theme.animation.standard.easing,
302
312
  transitionDuration: "".concat(theme.animation.standard.duration, "ms")
303
313
  };
304
- var buttonStyleProps = {
314
+ return [{
305
315
  alignItems: 'center',
306
316
  background: variant === null || variant === void 0 ? void 0 : variant.background,
307
317
  border: variant === null || variant === void 0 ? void 0 : variant.border,
@@ -314,44 +324,44 @@ function useButtonStyles(_ref) {
314
324
  justifyContent: 'center',
315
325
  paddingX: iconOnly ? undefined : mapTokens.spacing[size],
316
326
  position: 'relative',
317
- width: iconOnly ? mapTokens.size[size] : undefined,
318
- // interactions styles
319
- className: css(_objectSpread(_objectSpread({}, transitionColors), {}, {
320
- '&:not([aria-disabled=true])': {
321
- ':hover': {
322
- borderColor: variant !== null && variant !== void 0 && variant.borderHover ? theme.border.color[variant.borderHover] : undefined,
323
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundHover ? theme.backgroundInteractions[variant.backgroundHover] : undefined,
324
- // Style button text when hovering
325
- '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
326
- color: variant !== null && variant !== void 0 && variant.textToneHover ? theme.color.foreground[variant.textToneHover] : undefined,
327
- stroke: variant !== null && variant !== void 0 && variant.textToneHover ? theme.color.foreground[variant.textToneHover] : undefined
328
- })
329
- },
330
- ':active': {
331
- borderColor: variant !== null && variant !== void 0 && variant.borderActive ? theme.border.color[variant.borderActive] : undefined,
332
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundActive ? theme.backgroundInteractions[variant === null || variant === void 0 ? void 0 : variant.backgroundActive] : undefined,
333
- transform: 'scale(0.98)',
334
- // Style button text when it's active
335
- '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
336
- color: variant !== null && variant !== void 0 && variant.textToneActive ? theme.color.foreground[variant.textToneActive] : undefined,
337
- stroke: variant !== null && variant !== void 0 && variant.textToneActive ? theme.color.foreground[variant.textToneActive] : undefined
338
- })
339
- },
340
- ':focus': focusRingStyles
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.border.color[variant.borderHover] : undefined,
335
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundHover ? theme.backgroundInteractions[variant.backgroundHover] : undefined,
336
+ // Style button text when hovering
337
+ '> *': _objectSpread(_objectSpread({}, transitionColors), {}, {
338
+ color: variant !== null && variant !== void 0 && variant.textToneHover ? theme.color.foreground[variant.textToneHover] : undefined,
339
+ stroke: variant !== null && variant !== void 0 && variant.textToneHover ? theme.color.foreground[variant.textToneHover] : undefined
340
+ })
341
341
  },
342
- '&[aria-disabled=true]': {
343
- backgroundColor: variant !== null && variant !== void 0 && variant.backgroundDisabled ? theme.color.background[variant === null || variant === void 0 ? void 0 : variant.backgroundDisabled] : undefined,
344
- borderColor: variant !== null && variant !== void 0 && variant.borderDisabled ? theme.border.color[variant.borderDisabled] : undefined,
345
- cursor: 'default',
346
- '*': {
347
- color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme.color.foreground[variant.textToneDisabled] : undefined,
348
- stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme.color.foreground[variant.textToneDisabled] : undefined
349
- },
350
- ':focus': disabledFocusRingStyles
351
- }
352
- }))
353
- };
354
- return buttonStyleProps;
342
+ ':active': {
343
+ borderColor: variant !== null && variant !== void 0 && variant.borderActive ? theme.border.color[variant.borderActive] : undefined,
344
+ backgroundColor: variant !== null && variant !== void 0 && variant.backgroundActive ? theme.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.color.foreground[variant.textToneActive] : undefined,
349
+ stroke: variant !== null && variant !== void 0 && variant.textToneActive ? theme.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.color.background[variant === null || variant === void 0 ? void 0 : variant.backgroundDisabled] : undefined,
356
+ borderColor: variant !== null && variant !== void 0 && variant.borderDisabled ? theme.border.color[variant.borderDisabled] : undefined,
357
+ cursor: 'default',
358
+ '*': {
359
+ color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme.color.foreground[variant.textToneDisabled] : undefined,
360
+ stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme.color.foreground[variant.textToneDisabled] : undefined
361
+ },
362
+ ':focus': disabledFocusRingStyles
363
+ }
364
+ })];
355
365
  }
356
366
 
357
367
  var _excluded$1 = ["aria-controls", "aria-describedby", "aria-expanded", "data", "disabled", "id", "loading", "onClick", "prominence", "size", "tone", "type"];
@@ -380,20 +390,26 @@ var Button = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
380
390
  props = _objectWithoutProperties(_ref, _excluded$1);
381
391
 
382
392
  var iconOnly = Boolean(props.label);
383
- var buttonStyleProps = useButtonStyles({
393
+
394
+ var _useButtonStyles = useButtonStyles({
384
395
  iconOnly: iconOnly,
385
396
  size: size,
386
397
  tone: tone,
387
398
  prominence: prominence
388
- });
399
+ }),
400
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
401
+ boxProps = _useButtonStyles2[0],
402
+ buttonStyles = _useButtonStyles2[1];
403
+
389
404
  var isDisabled = disabled || loading;
390
405
  var isLoading = loading && !disabled;
391
406
  var variant = variants[prominence][tone];
392
- return /*#__PURE__*/jsxs(BaseButton, _objectSpread(_objectSpread({}, buttonStyleProps), {}, {
407
+ return /*#__PURE__*/jsxs(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
393
408
  "aria-controls": ariaControls,
394
409
  "aria-describedby": ariaDescribedBy,
395
410
  "aria-expanded": ariaExpanded,
396
411
  "aria-label": props.label,
412
+ className: css(buttonStyles),
397
413
  data: data,
398
414
  disabled: isDisabled,
399
415
  id: id,
@@ -436,7 +452,7 @@ function Loading(_ref2) {
436
452
  var _excluded = ["data", "href", "id", "prominence", "size", "tone"];
437
453
 
438
454
  /** The appearance of a `Button`, with the semantics of a link. */
439
- var ButtonLink = forwardRefWithAs(function (_ref, ref) {
455
+ var ButtonLink = forwardRefWithAs(function (_ref, forwardedRef) {
440
456
  var data = _ref.data,
441
457
  href = _ref.href,
442
458
  id = _ref.id,
@@ -446,25 +462,31 @@ var ButtonLink = forwardRefWithAs(function (_ref, ref) {
446
462
  size = _ref$size === void 0 ? 'medium' : _ref$size,
447
463
  _ref$tone = _ref.tone,
448
464
  tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
449
- props = _objectWithoutProperties(_ref, _excluded);
465
+ consumerProps = _objectWithoutProperties(_ref, _excluded);
450
466
 
451
- var LinkComponent = useLinkComponent(ref);
452
- var iconOnly = Boolean(props.label);
453
- var buttonStyleProps = useButtonStyles({
467
+ var LinkComponent = useLinkComponent(forwardedRef);
468
+ var iconOnly = Boolean(consumerProps.label);
469
+
470
+ var _useButtonStyles = useButtonStyles({
454
471
  iconOnly: iconOnly,
455
472
  prominence: prominence,
456
473
  size: size,
457
474
  tone: tone
458
- });
459
- return /*#__PURE__*/jsx(Box, _objectSpread(_objectSpread(_objectSpread({
460
- "aria-label": props.label,
475
+ }),
476
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
477
+ boxProps = _useButtonStyles2[0],
478
+ buttonStyles = _useButtonStyles2[1];
479
+
480
+ return /*#__PURE__*/jsx(Box, _objectSpread(_objectSpread({}, boxProps), {}, {
481
+ "aria-label": consumerProps.label,
461
482
  as: LinkComponent,
462
483
  asElement: "a",
463
- id: id,
484
+ className: css(buttonStyles),
485
+ data: data,
464
486
  href: href,
465
- ref: ref
466
- }, buttonStyleProps), data ? buildDataAttributes(data) : undefined), {}, {
467
- children: resolveButtonChildren(_objectSpread(_objectSpread({}, props), {}, {
487
+ id: id,
488
+ ref: forwardedRef,
489
+ children: resolveButtonChildren(_objectSpread(_objectSpread({}, consumerProps), {}, {
468
490
  isLoading: false,
469
491
  prominence: prominence,
470
492
  size: size,
@@ -473,4 +495,4 @@ var ButtonLink = forwardRefWithAs(function (_ref, ref) {
473
495
  }));
474
496
  });
475
497
 
476
- export { BaseButton, Button, ButtonLink };
498
+ export { BaseButton, Button, ButtonLink, useButtonStyles };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spark-web/button",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/spark-web-button.cjs.js",
6
6
  "module": "dist/spark-web-button.esm.js",
@@ -16,7 +16,7 @@
16
16
  "@spark-web/link": "^1.0.7",
17
17
  "@spark-web/spinner": "^1.0.5",
18
18
  "@spark-web/text": "^1.0.7",
19
- "@spark-web/theme": "^3.0.3",
19
+ "@spark-web/theme": "^3.0.4",
20
20
  "@spark-web/utils": "^1.2.0"
21
21
  },
22
22
  "devDependencies": {