@spark-web/button 1.3.0 → 1.4.1

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,43 +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
- '*': {
350
- color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined,
351
- stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined
352
- },
353
- ':focus': disabledFocusRingStyles
354
- }
355
- }))
356
- };
357
- 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
+ })];
358
369
  }
359
370
 
360
371
  var _excluded$1 = ["aria-controls", "aria-describedby", "aria-expanded", "data", "disabled", "id", "loading", "onClick", "prominence", "size", "tone", "type"];
@@ -383,20 +394,26 @@ var Button = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
383
394
  props = _objectWithoutProperties(_ref, _excluded$1);
384
395
 
385
396
  var iconOnly = Boolean(props.label);
386
- var buttonStyleProps = useButtonStyles({
397
+
398
+ var _useButtonStyles = useButtonStyles({
387
399
  iconOnly: iconOnly,
388
400
  size: size,
389
401
  tone: tone,
390
402
  prominence: prominence
391
- });
403
+ }),
404
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
405
+ boxProps = _useButtonStyles2[0],
406
+ buttonStyles = _useButtonStyles2[1];
407
+
392
408
  var isDisabled = disabled || loading;
393
409
  var isLoading = loading && !disabled;
394
410
  var variant = variants[prominence][tone];
395
- return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, buttonStyleProps), {}, {
411
+ return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
396
412
  "aria-controls": ariaControls,
397
413
  "aria-describedby": ariaDescribedBy,
398
414
  "aria-expanded": ariaExpanded,
399
415
  "aria-label": props.label,
416
+ className: css.css(buttonStyles),
400
417
  data: data,
401
418
  disabled: isDisabled,
402
419
  id: id,
@@ -439,7 +456,7 @@ function Loading(_ref2) {
439
456
  var _excluded = ["data", "href", "id", "prominence", "size", "tone"];
440
457
 
441
458
  /** The appearance of a `Button`, with the semantics of a link. */
442
- var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
459
+ var ButtonLink = ts.forwardRefWithAs(function (_ref, forwardedRef) {
443
460
  var data = _ref.data,
444
461
  href = _ref.href,
445
462
  id = _ref.id,
@@ -449,25 +466,31 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
449
466
  size = _ref$size === void 0 ? 'medium' : _ref$size,
450
467
  _ref$tone = _ref.tone,
451
468
  tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
452
- props = _objectWithoutProperties(_ref, _excluded);
469
+ consumerProps = _objectWithoutProperties(_ref, _excluded);
453
470
 
454
- var LinkComponent = link.useLinkComponent(ref);
455
- var iconOnly = Boolean(props.label);
456
- var buttonStyleProps = useButtonStyles({
471
+ var LinkComponent = link.useLinkComponent(forwardedRef);
472
+ var iconOnly = Boolean(consumerProps.label);
473
+
474
+ var _useButtonStyles = useButtonStyles({
457
475
  iconOnly: iconOnly,
458
476
  prominence: prominence,
459
477
  size: size,
460
478
  tone: tone
461
- });
462
- return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread(_objectSpread({
463
- "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,
464
486
  as: LinkComponent,
465
487
  asElement: "a",
466
- id: id,
488
+ className: css.css(buttonStyles),
489
+ data: data,
467
490
  href: href,
468
- ref: ref
469
- }, buttonStyleProps), data ? internal.buildDataAttributes(data) : undefined), {}, {
470
- children: resolveButtonChildren(_objectSpread(_objectSpread({}, props), {}, {
491
+ id: id,
492
+ ref: forwardedRef,
493
+ children: resolveButtonChildren(_objectSpread(_objectSpread({}, consumerProps), {}, {
471
494
  isLoading: false,
472
495
  prominence: prominence,
473
496
  size: size,
@@ -479,3 +502,4 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
479
502
  exports.BaseButton = BaseButton;
480
503
  exports.Button = Button;
481
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,43 +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
- '*': {
350
- color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined,
351
- stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme$1.color.foreground[variant.textToneDisabled] : undefined
352
- },
353
- ':focus': disabledFocusRingStyles
354
- }
355
- }))
356
- };
357
- 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
+ })];
358
369
  }
359
370
 
360
371
  var _excluded$1 = ["aria-controls", "aria-describedby", "aria-expanded", "data", "disabled", "id", "loading", "onClick", "prominence", "size", "tone", "type"];
@@ -383,20 +394,26 @@ var Button = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
383
394
  props = _objectWithoutProperties(_ref, _excluded$1);
384
395
 
385
396
  var iconOnly = Boolean(props.label);
386
- var buttonStyleProps = useButtonStyles({
397
+
398
+ var _useButtonStyles = useButtonStyles({
387
399
  iconOnly: iconOnly,
388
400
  size: size,
389
401
  tone: tone,
390
402
  prominence: prominence
391
- });
403
+ }),
404
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
405
+ boxProps = _useButtonStyles2[0],
406
+ buttonStyles = _useButtonStyles2[1];
407
+
392
408
  var isDisabled = disabled || loading;
393
409
  var isLoading = loading && !disabled;
394
410
  var variant = variants[prominence][tone];
395
- return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, buttonStyleProps), {}, {
411
+ return /*#__PURE__*/jsxRuntime.jsxs(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
396
412
  "aria-controls": ariaControls,
397
413
  "aria-describedby": ariaDescribedBy,
398
414
  "aria-expanded": ariaExpanded,
399
415
  "aria-label": props.label,
416
+ className: css.css(buttonStyles),
400
417
  data: data,
401
418
  disabled: isDisabled,
402
419
  id: id,
@@ -439,7 +456,7 @@ function Loading(_ref2) {
439
456
  var _excluded = ["data", "href", "id", "prominence", "size", "tone"];
440
457
 
441
458
  /** The appearance of a `Button`, with the semantics of a link. */
442
- var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
459
+ var ButtonLink = ts.forwardRefWithAs(function (_ref, forwardedRef) {
443
460
  var data = _ref.data,
444
461
  href = _ref.href,
445
462
  id = _ref.id,
@@ -449,25 +466,31 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
449
466
  size = _ref$size === void 0 ? 'medium' : _ref$size,
450
467
  _ref$tone = _ref.tone,
451
468
  tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
452
- props = _objectWithoutProperties(_ref, _excluded);
469
+ consumerProps = _objectWithoutProperties(_ref, _excluded);
453
470
 
454
- var LinkComponent = link.useLinkComponent(ref);
455
- var iconOnly = Boolean(props.label);
456
- var buttonStyleProps = useButtonStyles({
471
+ var LinkComponent = link.useLinkComponent(forwardedRef);
472
+ var iconOnly = Boolean(consumerProps.label);
473
+
474
+ var _useButtonStyles = useButtonStyles({
457
475
  iconOnly: iconOnly,
458
476
  prominence: prominence,
459
477
  size: size,
460
478
  tone: tone
461
- });
462
- return /*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread(_objectSpread({
463
- "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,
464
486
  as: LinkComponent,
465
487
  asElement: "a",
466
- id: id,
488
+ className: css.css(buttonStyles),
489
+ data: data,
467
490
  href: href,
468
- ref: ref
469
- }, buttonStyleProps), data ? internal.buildDataAttributes(data) : undefined), {}, {
470
- children: resolveButtonChildren(_objectSpread(_objectSpread({}, props), {}, {
491
+ id: id,
492
+ ref: forwardedRef,
493
+ children: resolveButtonChildren(_objectSpread(_objectSpread({}, consumerProps), {}, {
471
494
  isLoading: false,
472
495
  prominence: prominence,
473
496
  size: size,
@@ -479,3 +502,4 @@ var ButtonLink = ts.forwardRefWithAs(function (_ref, ref) {
479
502
  exports.BaseButton = BaseButton;
480
503
  exports.Button = Button;
481
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,43 +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
- '*': {
346
- color: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme.color.foreground[variant.textToneDisabled] : undefined,
347
- stroke: variant !== null && variant !== void 0 && variant.textToneDisabled ? theme.color.foreground[variant.textToneDisabled] : undefined
348
- },
349
- ':focus': disabledFocusRingStyles
350
- }
351
- }))
352
- };
353
- 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
+ })];
354
365
  }
355
366
 
356
367
  var _excluded$1 = ["aria-controls", "aria-describedby", "aria-expanded", "data", "disabled", "id", "loading", "onClick", "prominence", "size", "tone", "type"];
@@ -379,20 +390,26 @@ var Button = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
379
390
  props = _objectWithoutProperties(_ref, _excluded$1);
380
391
 
381
392
  var iconOnly = Boolean(props.label);
382
- var buttonStyleProps = useButtonStyles({
393
+
394
+ var _useButtonStyles = useButtonStyles({
383
395
  iconOnly: iconOnly,
384
396
  size: size,
385
397
  tone: tone,
386
398
  prominence: prominence
387
- });
399
+ }),
400
+ _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
401
+ boxProps = _useButtonStyles2[0],
402
+ buttonStyles = _useButtonStyles2[1];
403
+
388
404
  var isDisabled = disabled || loading;
389
405
  var isLoading = loading && !disabled;
390
406
  var variant = variants[prominence][tone];
391
- return /*#__PURE__*/jsxs(BaseButton, _objectSpread(_objectSpread({}, buttonStyleProps), {}, {
407
+ return /*#__PURE__*/jsxs(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
392
408
  "aria-controls": ariaControls,
393
409
  "aria-describedby": ariaDescribedBy,
394
410
  "aria-expanded": ariaExpanded,
395
411
  "aria-label": props.label,
412
+ className: css(buttonStyles),
396
413
  data: data,
397
414
  disabled: isDisabled,
398
415
  id: id,
@@ -435,7 +452,7 @@ function Loading(_ref2) {
435
452
  var _excluded = ["data", "href", "id", "prominence", "size", "tone"];
436
453
 
437
454
  /** The appearance of a `Button`, with the semantics of a link. */
438
- var ButtonLink = forwardRefWithAs(function (_ref, ref) {
455
+ var ButtonLink = forwardRefWithAs(function (_ref, forwardedRef) {
439
456
  var data = _ref.data,
440
457
  href = _ref.href,
441
458
  id = _ref.id,
@@ -445,25 +462,31 @@ var ButtonLink = forwardRefWithAs(function (_ref, ref) {
445
462
  size = _ref$size === void 0 ? 'medium' : _ref$size,
446
463
  _ref$tone = _ref.tone,
447
464
  tone = _ref$tone === void 0 ? 'primary' : _ref$tone,
448
- props = _objectWithoutProperties(_ref, _excluded);
465
+ consumerProps = _objectWithoutProperties(_ref, _excluded);
449
466
 
450
- var LinkComponent = useLinkComponent(ref);
451
- var iconOnly = Boolean(props.label);
452
- var buttonStyleProps = useButtonStyles({
467
+ var LinkComponent = useLinkComponent(forwardedRef);
468
+ var iconOnly = Boolean(consumerProps.label);
469
+
470
+ var _useButtonStyles = useButtonStyles({
453
471
  iconOnly: iconOnly,
454
472
  prominence: prominence,
455
473
  size: size,
456
474
  tone: tone
457
- });
458
- return /*#__PURE__*/jsx(Box, _objectSpread(_objectSpread(_objectSpread({
459
- "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,
460
482
  as: LinkComponent,
461
483
  asElement: "a",
462
- id: id,
484
+ className: css(buttonStyles),
485
+ data: data,
463
486
  href: href,
464
- ref: ref
465
- }, buttonStyleProps), data ? buildDataAttributes(data) : undefined), {}, {
466
- children: resolveButtonChildren(_objectSpread(_objectSpread({}, props), {}, {
487
+ id: id,
488
+ ref: forwardedRef,
489
+ children: resolveButtonChildren(_objectSpread(_objectSpread({}, consumerProps), {}, {
467
490
  isLoading: false,
468
491
  prominence: prominence,
469
492
  size: size,
@@ -472,4 +495,4 @@ var ButtonLink = forwardRefWithAs(function (_ref, ref) {
472
495
  }));
473
496
  });
474
497
 
475
- 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.0",
3
+ "version": "1.4.1",
4
4
  "license": "MIT",
5
5
  "main": "dist/spark-web-button.cjs.js",
6
6
  "module": "dist/spark-web-button.esm.js",
@@ -10,14 +10,14 @@
10
10
  "dependencies": {
11
11
  "@babel/runtime": "^7.18.9",
12
12
  "@emotion/css": "^11.9.0",
13
- "@spark-web/a11y": "^1.2.0",
14
- "@spark-web/box": "^1.0.7",
15
- "@spark-web/icon": "^1.1.5",
16
- "@spark-web/link": "^1.0.7",
17
- "@spark-web/spinner": "^1.0.5",
18
- "@spark-web/text": "^1.0.7",
19
- "@spark-web/theme": "^3.0.3",
20
- "@spark-web/utils": "^1.2.0"
13
+ "@spark-web/a11y": "^1.3.0",
14
+ "@spark-web/box": "^1.0.8",
15
+ "@spark-web/icon": "^1.2.0",
16
+ "@spark-web/link": "^1.0.8",
17
+ "@spark-web/spinner": "^1.0.6",
18
+ "@spark-web/text": "^1.1.0",
19
+ "@spark-web/theme": "^3.0.5",
20
+ "@spark-web/utils": "^1.2.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/react": "^17.0.12",