@simplybusiness/mobius 3.6.2 → 3.8.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/components/Button/Button.d.ts +3 -2
  3. package/dist/cjs/components/Button/Button.js +9 -14
  4. package/dist/cjs/components/Button/Button.js.map +1 -1
  5. package/dist/cjs/components/DatePicker/DatePicker.js.map +1 -1
  6. package/dist/cjs/components/Modal/Modal.js +2 -0
  7. package/dist/cjs/components/Modal/Modal.js.map +1 -1
  8. package/dist/cjs/hooks/index.d.ts +2 -0
  9. package/dist/cjs/hooks/index.js +2 -0
  10. package/dist/cjs/hooks/index.js.map +1 -1
  11. package/dist/cjs/hooks/useBodyScrollLock/index.d.ts +1 -0
  12. package/dist/cjs/hooks/useBodyScrollLock/index.js +18 -0
  13. package/dist/cjs/hooks/useBodyScrollLock/index.js.map +1 -0
  14. package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.d.ts +3 -0
  15. package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.js +25 -0
  16. package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.js.map +1 -0
  17. package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.test.d.ts +1 -0
  18. package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.test.js +31 -0
  19. package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.test.js.map +1 -0
  20. package/dist/cjs/hooks/useButton/index.d.ts +1 -0
  21. package/dist/cjs/hooks/useButton/index.js +18 -0
  22. package/dist/cjs/hooks/useButton/index.js.map +1 -0
  23. package/dist/cjs/hooks/useButton/useButton.d.ts +35 -0
  24. package/dist/cjs/hooks/useButton/useButton.js +32 -0
  25. package/dist/cjs/hooks/useButton/useButton.js.map +1 -0
  26. package/dist/cjs/hooks/useButton/useButton.test.d.ts +1 -0
  27. package/dist/cjs/hooks/useButton/useButton.test.js +143 -0
  28. package/dist/cjs/hooks/useButton/useButton.test.js.map +1 -0
  29. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  30. package/dist/esm/components/Button/Button.js +10 -15
  31. package/dist/esm/components/Button/Button.js.map +1 -1
  32. package/dist/esm/components/DatePicker/DatePicker.js.map +1 -1
  33. package/dist/esm/components/Modal/Modal.js +2 -0
  34. package/dist/esm/components/Modal/Modal.js.map +1 -1
  35. package/dist/esm/hooks/index.js +2 -0
  36. package/dist/esm/hooks/index.js.map +1 -1
  37. package/dist/esm/hooks/useBodyScrollLock/index.js +2 -0
  38. package/dist/esm/hooks/useBodyScrollLock/index.js.map +1 -0
  39. package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.js +21 -0
  40. package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.js.map +1 -0
  41. package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.test.js +29 -0
  42. package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.test.js.map +1 -0
  43. package/dist/esm/hooks/useButton/index.js +2 -0
  44. package/dist/esm/hooks/useButton/index.js.map +1 -0
  45. package/dist/esm/hooks/useButton/useButton.js +28 -0
  46. package/dist/esm/hooks/useButton/useButton.js.map +1 -0
  47. package/dist/esm/hooks/useButton/useButton.test.js +141 -0
  48. package/dist/esm/hooks/useButton/useButton.test.js.map +1 -0
  49. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  50. package/dist/mobius.d.ts +43 -2
  51. package/package.json +1 -1
  52. package/src/components/Button/Button.tsx +10 -34
  53. package/src/components/DatePicker/DatePicker.tsx +1 -0
  54. package/src/components/Modal/Modal.tsx +2 -0
  55. package/src/hooks/index.tsx +2 -0
  56. package/src/hooks/useBodyScrollLock/index.ts +1 -0
  57. package/src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts +34 -0
  58. package/src/hooks/useBodyScrollLock/useBodyScrollLock.ts +25 -0
  59. package/src/hooks/useButton/index.tsx +1 -0
  60. package/src/hooks/useButton/useButton.test.tsx +183 -0
  61. package/src/hooks/useButton/useButton.tsx +75 -0
package/dist/mobius.d.ts CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  import { AriaBreadcrumbItemProps } from '@react-types/breadcrumbs';
4
4
  import { AriaBreadcrumbsProps } from '@react-types/breadcrumbs';
5
- import type { AriaButtonProps } from '@react-types/button';
6
5
  import type { AriaCheckboxGroupProps } from '@react-types/checkbox';
7
6
  import { AriaCheckboxProps } from '@react-types/checkbox';
8
7
  import { AriaDatePickerProps } from '@react-aria/datepicker';
8
+ import { AriaFocusRingProps } from '@react-aria/focus';
9
9
  import { AriaLinkProps } from '@react-types/link';
10
10
  import { AriaNumberFieldProps } from '@react-aria/numberfield';
11
11
  import type { AriaProgressBarProps } from '@react-types/progress';
@@ -27,6 +27,7 @@ import { HoverProps } from '@react-aria/interactions';
27
27
  import { HTMLAttributeAnchorTarget } from 'react';
28
28
  import { HTMLInputTypeAttribute } from 'react';
29
29
  import { IconName } from '@simplybusiness/icons';
30
+ import { JSXElementConstructor } from 'react';
30
31
  import { LabelableProps } from '@react-types/shared';
31
32
  import { Link as Link_2 } from 'react-router-dom';
32
33
  import { parseDate } from '@internationalized/date';
@@ -171,7 +172,7 @@ export declare const Button: ForwardedRefComponent<ButtonProps, ButtonElementTyp
171
172
 
172
173
  export declare type ButtonElementType = HTMLButtonElement;
173
174
 
174
- export declare interface ButtonProps extends Omit<AriaButtonProps<"button" | "a" | "span">, "aria-haspopup" | "aria-expanded">, DOMProps, RefAttributes<ButtonElementType> {
175
+ export declare interface ButtonProps extends UseButtonProps, AriaFocusRingProps, DOMProps, RefAttributes<ButtonElementType> {
175
176
  /** Custom class name for setting specific CSS */
176
177
  className?: string;
177
178
  /** Shortlist of styles */
@@ -1224,8 +1225,48 @@ declare type UrlProps = {
1224
1225
  allOccupations: string;
1225
1226
  };
1226
1227
 
1228
+ export declare function useBodyScrollLock({ enabled, }?: {
1229
+ enabled?: boolean;
1230
+ }): void;
1231
+
1227
1232
  export declare const useBreakpoint: () => BreakpointType;
1228
1233
 
1234
+ export declare function useButton({ elementType, type, isDisabled, href, target, rel, role, onClick, onPress, }: UseButtonProps): {
1235
+ buttonProps: {
1236
+ onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
1237
+ type: "button" | "reset" | "submit";
1238
+ role?: undefined;
1239
+ href: string | undefined;
1240
+ target: string | undefined;
1241
+ rel: string | undefined;
1242
+ tabIndex: number;
1243
+ disabled: true | undefined;
1244
+ "aria-disabled": true | undefined;
1245
+ } | {
1246
+ onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
1247
+ role: string | undefined;
1248
+ type?: undefined;
1249
+ href: string | undefined;
1250
+ target: string | undefined;
1251
+ rel: string | undefined;
1252
+ tabIndex: number;
1253
+ disabled: true | undefined;
1254
+ "aria-disabled": true | undefined;
1255
+ };
1256
+ };
1257
+
1258
+ export declare interface UseButtonProps {
1259
+ elementType?: "button" | "a" | "span" | "input" | JSXElementConstructor<any> | undefined;
1260
+ type?: "button" | "submit" | "reset";
1261
+ isDisabled?: boolean;
1262
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
1263
+ onPress?: (event: React.MouseEvent<HTMLButtonElement>) => void;
1264
+ href?: string;
1265
+ target?: string;
1266
+ rel?: string;
1267
+ role?: string;
1268
+ }
1269
+
1229
1270
  export declare const useHideColumns: {
1230
1271
  (setHiddenColumns: (cols: string[]) => void, collapseData?: CollapseDataType): void;
1231
1272
  displayName: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplybusiness/mobius",
3
3
  "license": "UNLICENSED",
4
- "version": "3.6.2",
4
+ "version": "3.8.0",
5
5
  "description": "Core library of Mobius react components",
6
6
  "repository": {
7
7
  "type": "git",
@@ -1,16 +1,15 @@
1
1
  "use client";
2
2
 
3
- import { Ref, forwardRef, RefAttributes, useRef, ReactNode } from "react";
4
- import { useButton } from "@react-aria/button";
3
+ import { Ref, forwardRef, RefAttributes, ReactNode } from "react";
5
4
  import { useHover } from "@react-aria/interactions";
6
- import type { AriaButtonProps } from "@react-types/button";
7
5
  import { DOMProps } from "@react-types/shared";
8
6
  import clsx from "clsx";
9
- import { useFocusRing } from "@react-aria/focus";
7
+ import { AriaFocusRingProps, useFocusRing } from "@react-aria/focus";
10
8
  import type { IconName } from "@simplybusiness/icons";
11
9
  import { ForwardedRefComponent } from "../../types/components";
12
10
  import { Loading } from "./Loading";
13
11
  import { Icon } from "../Icon";
12
+ import { UseButtonProps, useButton } from "../../hooks/useButton";
14
13
 
15
14
  export type ButtonElementType = HTMLButtonElement;
16
15
 
@@ -25,10 +24,8 @@ export type Variant =
25
24
  export type Size = "small" | "medium" | "large";
26
25
 
27
26
  export interface ButtonProps
28
- extends Omit<
29
- AriaButtonProps<"button" | "a" | "span">,
30
- "aria-haspopup" | "aria-expanded"
31
- >,
27
+ extends UseButtonProps,
28
+ AriaFocusRingProps,
32
29
  DOMProps,
33
30
  RefAttributes<ButtonElementType> {
34
31
  /** Custom class name for setting specific CSS */
@@ -60,35 +57,14 @@ const Button: ForwardedRefComponent<ButtonProps, ButtonElementType> =
60
57
  size = "medium",
61
58
  icon,
62
59
  iconPosition = "left",
60
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
63
61
  onPress,
64
- onPressChange,
65
- onPressEnd,
66
- onPressStart,
67
- onPressUp,
62
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
68
63
  onClick,
69
64
  ...otherProps
70
65
  } = props;
71
- const refObj = useRef(null);
72
- const { hoverProps, isHovered } = useHover({
73
- ...props,
74
- isDisabled,
75
- });
76
- const { buttonProps } = useButton(
77
- {
78
- onPress,
79
- onPressChange,
80
- onPressEnd,
81
- onPressStart,
82
- onPressUp,
83
- isDisabled,
84
- },
85
- refObj,
86
- );
87
- const mergedButtonProps = {
88
- ...buttonProps,
89
- onClick: (event: React.MouseEvent<HTMLButtonElement>) =>
90
- onClick?.(event) || buttonProps.onClick?.(event),
91
- };
66
+ const { hoverProps, isHovered } = useHover(props);
67
+ const { buttonProps } = useButton(props);
92
68
 
93
69
  const { focusProps, isFocusVisible } = useFocusRing(props);
94
70
 
@@ -118,7 +94,7 @@ const Button: ForwardedRefComponent<ButtonProps, ButtonElementType> =
118
94
  return (
119
95
  <Component
120
96
  ref={ref}
121
- {...mergedButtonProps}
97
+ {...buttonProps}
122
98
  {...hoverProps}
123
99
  {...focusProps}
124
100
  {...otherProps}
@@ -77,6 +77,7 @@ export function DatePicker(props: DatePickerProps) {
77
77
  className={dateFieldGroupClasses}
78
78
  >
79
79
  <DateField {...fieldProps} className={dateFieldClasses} />
80
+ {/* @ts-expect-error wrong type for onPress */}
80
81
  <Button
81
82
  variant="secondary"
82
83
  {...buttonProps}
@@ -11,6 +11,7 @@ import ReactModal from "react-modal";
11
11
  import clsx from "clsx";
12
12
  import { SizeType } from "../../types/size";
13
13
  import { sizeClasses } from "../../utils/sizeClasses";
14
+ import { useBodyScrollLock } from "../../hooks/useBodyScrollLock";
14
15
 
15
16
  export type ModalSizeType = SizeType | "fullscreen";
16
17
  export interface ModalProps {
@@ -46,6 +47,7 @@ const Modal = ({
46
47
  "(prefers-reduced-motion: no-preference)",
47
48
  );
48
49
  const shouldAnimate = noPreference.matches && !!animation;
50
+ useBodyScrollLock({ enabled: isOpen });
49
51
 
50
52
  const modalClasses = clsx(
51
53
  "mobius",
@@ -1,2 +1,4 @@
1
1
  export * from "./useBreakpoint";
2
2
  export * from "./useHideColumns";
3
+ export * from "./useButton";
4
+ export * from "./useBodyScrollLock";
@@ -0,0 +1 @@
1
+ export * from "./useBodyScrollLock";
@@ -0,0 +1,34 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { useBodyScrollLock } from "./useBodyScrollLock";
3
+
4
+ describe("useBodyScrollLock", () => {
5
+ it("should be defined", () => {
6
+ expect(useBodyScrollLock).toBeDefined();
7
+ });
8
+
9
+ it("should not throw", () => {
10
+ expect(() => renderHook(() => useBodyScrollLock())).not.toThrow();
11
+ });
12
+
13
+ it("should disable scroll by default", () => {
14
+ renderHook(() => useBodyScrollLock());
15
+ expect(document.body.style.overflow).toBe("hidden");
16
+ });
17
+
18
+ it("should disable scroll when enabled is true", () => {
19
+ renderHook(() => useBodyScrollLock({ enabled: true }));
20
+ expect(document.body.style.overflow).toBe("hidden");
21
+ });
22
+
23
+ it("should enable scroll when enabled is false", () => {
24
+ renderHook(() => useBodyScrollLock({ enabled: false }));
25
+ expect(document.body.style.overflow).toBe("");
26
+ });
27
+
28
+ it("should enable scroll on unmount", () => {
29
+ const { unmount } = renderHook(() => useBodyScrollLock());
30
+ expect(document.body.style.overflow).toBe("hidden");
31
+ unmount();
32
+ expect(document.body.style.overflow).toBe("");
33
+ });
34
+ });
@@ -0,0 +1,25 @@
1
+ import { useEffect } from "react";
2
+
3
+ export function useBodyScrollLock({
4
+ enabled = true,
5
+ }: { enabled?: boolean } = {}) {
6
+ function disableScrollLock() {
7
+ document.body.style.removeProperty("overflow");
8
+ }
9
+
10
+ function enableScrollLock() {
11
+ document.body.style.overflow = "hidden";
12
+ }
13
+
14
+ useEffect(() => {
15
+ if (enabled) {
16
+ enableScrollLock();
17
+ } else {
18
+ disableScrollLock();
19
+ }
20
+
21
+ return () => {
22
+ disableScrollLock();
23
+ };
24
+ }, [enabled]);
25
+ }
@@ -0,0 +1 @@
1
+ export * from "./useButton";
@@ -0,0 +1,183 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { useButton } from "./useButton";
3
+
4
+ describe("useButton", () => {
5
+ it("should return an object", () => {
6
+ const { result } = renderHook(() => useButton({}));
7
+ expect(result.current).toBeInstanceOf(Object);
8
+ });
9
+
10
+ it("should return an object with buttonProps", () => {
11
+ const { result } = renderHook(() => useButton({}));
12
+ expect(result.current).toHaveProperty("buttonProps");
13
+ });
14
+
15
+ describe("type", () => {
16
+ it("should return an object with buttonProps with type property", () => {
17
+ const { result } = renderHook(() => useButton({}));
18
+ expect(result.current.buttonProps).toHaveProperty("type");
19
+ });
20
+
21
+ it("should return an object with buttonProps with type set to button when elementType is button", () => {
22
+ const { result } = renderHook(() => useButton({ elementType: "button" }));
23
+ expect(result.current.buttonProps).toHaveProperty("type", "button");
24
+ });
25
+
26
+ it("should return an object with buttonProps with type set to passed in type when elementType is button", () => {
27
+ const { result } = renderHook(() => useButton({ type: "submit" }));
28
+ expect(result.current.buttonProps).toHaveProperty("type", "submit");
29
+ });
30
+
31
+ it("should return an object without buttonProps with type property when elementType is not button", () => {
32
+ const { result } = renderHook(() => useButton({ elementType: "a" }));
33
+ expect(result.current.buttonProps).not.toHaveProperty("type");
34
+ });
35
+ });
36
+
37
+ describe("isDisabled", () => {
38
+ it("should return an object with buttonProps with disabled property set to undefined", () => {
39
+ const { result } = renderHook(() => useButton({}));
40
+ expect(result.current.buttonProps).toHaveProperty("disabled", undefined);
41
+ });
42
+
43
+ it("should return an object with buttonProps with disabled set to true when isDisabled is set to true", () => {
44
+ const { result } = renderHook(() => useButton({ isDisabled: true }));
45
+ expect(result.current.buttonProps).toHaveProperty("disabled", true);
46
+ });
47
+
48
+ it("should return an object with buttonProps with aria-disabled property set to undefined", () => {
49
+ const { result } = renderHook(() => useButton({}));
50
+ expect(result.current.buttonProps).toHaveProperty(
51
+ "aria-disabled",
52
+ undefined,
53
+ );
54
+ });
55
+
56
+ it("should return an object with buttonProps with aria-disabled property set to true when isDisabled is set to true", () => {
57
+ const { result } = renderHook(() => useButton({ isDisabled: true }));
58
+ expect(result.current.buttonProps).toHaveProperty("aria-disabled", true);
59
+ });
60
+ });
61
+
62
+ describe("href", () => {
63
+ describe("when elementType is a", () => {
64
+ it("should return an object with buttonProps with href property set to undefined", () => {
65
+ const { result } = renderHook(() => useButton({}));
66
+ expect(result.current.buttonProps).toHaveProperty("href", undefined);
67
+ });
68
+
69
+ it("should return an object with buttonProps with href property set to passed in href", () => {
70
+ const { result } = renderHook(() =>
71
+ useButton({ elementType: "a", href: "test" }),
72
+ );
73
+ expect(result.current.buttonProps).toHaveProperty("href", "test");
74
+ });
75
+ });
76
+
77
+ describe("when elementType is not a", () => {
78
+ it("should return an object with buttonProps with href property set to undefined", () => {
79
+ const { result } = renderHook(() =>
80
+ useButton({ elementType: "button" }),
81
+ );
82
+ expect(result.current.buttonProps).toHaveProperty("href", undefined);
83
+ });
84
+ });
85
+ });
86
+
87
+ describe("target", () => {
88
+ describe("when elementType is a", () => {
89
+ it("should return an object with buttonProps with target property set to undefined", () => {
90
+ const { result } = renderHook(() => useButton({}));
91
+ expect(result.current.buttonProps).toHaveProperty("target", undefined);
92
+ });
93
+
94
+ it("should return an object with buttonProps with target property set to passed in target", () => {
95
+ const { result } = renderHook(() =>
96
+ useButton({ elementType: "a", target: "test" }),
97
+ );
98
+ expect(result.current.buttonProps).toHaveProperty("target", "test");
99
+ });
100
+ });
101
+
102
+ describe("when elementType is not a", () => {
103
+ it("should return an object with buttonProps with target property set to undefined", () => {
104
+ const { result } = renderHook(() =>
105
+ useButton({ elementType: "button" }),
106
+ );
107
+ expect(result.current.buttonProps).toHaveProperty("target", undefined);
108
+ });
109
+ });
110
+ });
111
+
112
+ describe("rel", () => {
113
+ describe("when elementType is a", () => {
114
+ it("should return an object with buttonProps with rel property set to undefined", () => {
115
+ const { result } = renderHook(() => useButton({}));
116
+ expect(result.current.buttonProps).toHaveProperty("rel", undefined);
117
+ });
118
+
119
+ it("should return an object with buttonProps with rel property set to passed in rel", () => {
120
+ const { result } = renderHook(() =>
121
+ useButton({ elementType: "a", rel: "test" }),
122
+ );
123
+ expect(result.current.buttonProps).toHaveProperty("rel", "test");
124
+ });
125
+ });
126
+
127
+ describe("when elementType is not a", () => {
128
+ it("should return an object with buttonProps with rel property set to undefined", () => {
129
+ const { result } = renderHook(() =>
130
+ useButton({ elementType: "button" }),
131
+ );
132
+ expect(result.current.buttonProps).toHaveProperty("rel", undefined);
133
+ });
134
+ });
135
+ });
136
+
137
+ describe("events", () => {
138
+ it("should return an object with buttonProps with onClick property", () => {
139
+ const { result } = renderHook(() => useButton({}));
140
+ expect(result.current.buttonProps).toHaveProperty("onClick");
141
+ });
142
+
143
+ describe("onClick", () => {
144
+ it("should call onClick when buttonProps onClick is called", () => {
145
+ const onClick = jest.fn();
146
+ const { result } = renderHook(() => useButton({ onClick }));
147
+ // @ts-expect-error - We don't care about the event argument
148
+ result.current.buttonProps.onClick();
149
+ expect(onClick).toHaveBeenCalled();
150
+ });
151
+
152
+ it("should not call onClick when buttonProps onClick is called and isDisabled is true", () => {
153
+ const onClick = jest.fn();
154
+ const { result } = renderHook(() =>
155
+ useButton({ onClick, isDisabled: true }),
156
+ );
157
+ // @ts-expect-error - We don't care about the event argument
158
+ result.current.buttonProps.onClick();
159
+ expect(onClick).not.toHaveBeenCalled();
160
+ });
161
+ });
162
+
163
+ describe("onPress", () => {
164
+ it("should call onPress when buttonProps onClick is called", () => {
165
+ const onPress = jest.fn();
166
+ const { result } = renderHook(() => useButton({ onPress }));
167
+ // @ts-expect-error - We don't care about the event argument
168
+ result.current.buttonProps.onClick();
169
+ expect(onPress).toHaveBeenCalled();
170
+ });
171
+
172
+ it("should not call onPress when buttonProps onClick is called and isDisabled is true", () => {
173
+ const onPress = jest.fn();
174
+ const { result } = renderHook(() =>
175
+ useButton({ onPress, isDisabled: true }),
176
+ );
177
+ // @ts-expect-error - We don't care about the event argument
178
+ result.current.buttonProps.onClick();
179
+ expect(onPress).not.toHaveBeenCalled();
180
+ });
181
+ });
182
+ });
183
+ });
@@ -0,0 +1,75 @@
1
+ import { JSXElementConstructor, useCallback } from "react";
2
+
3
+ export interface UseButtonProps {
4
+ elementType?:
5
+ | "button"
6
+ | "a"
7
+ | "span"
8
+ | "input"
9
+ | JSXElementConstructor<any>
10
+ | undefined;
11
+ type?: "button" | "submit" | "reset";
12
+ isDisabled?: boolean;
13
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
14
+ onPress?: (event: React.MouseEvent<HTMLButtonElement>) => void;
15
+ href?: string;
16
+ target?: string;
17
+ rel?: string;
18
+ role?: string;
19
+ }
20
+
21
+ export function useButton({
22
+ elementType = "button",
23
+ type = "button",
24
+ isDisabled = false,
25
+ href,
26
+ target,
27
+ rel,
28
+ role,
29
+ onClick,
30
+ onPress,
31
+ }: UseButtonProps) {
32
+ const realOnClick = useCallback(
33
+ (event: React.MouseEvent<HTMLButtonElement>) => {
34
+ if (isDisabled) {
35
+ return;
36
+ }
37
+
38
+ onClick?.(event);
39
+ onPress?.(event);
40
+ },
41
+ [isDisabled, onClick, onPress],
42
+ );
43
+
44
+ function getRole() {
45
+ if (role) {
46
+ return role;
47
+ }
48
+
49
+ if (elementType === "a") {
50
+ return undefined;
51
+ }
52
+
53
+ return "button";
54
+ }
55
+
56
+ const extraProps =
57
+ elementType === "button"
58
+ ? { type }
59
+ : {
60
+ role: getRole(),
61
+ };
62
+
63
+ return {
64
+ buttonProps: {
65
+ href: elementType === "a" ? href : undefined,
66
+ target: elementType === "a" ? target : undefined,
67
+ rel: elementType === "a" ? rel : undefined,
68
+ tabIndex: isDisabled ? -1 : 0,
69
+ disabled: isDisabled || undefined,
70
+ "aria-disabled": isDisabled || undefined,
71
+ ...extraProps,
72
+ onClick: realOnClick,
73
+ },
74
+ };
75
+ }