@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.
- package/CHANGELOG.md +16 -0
- package/dist/cjs/components/Button/Button.d.ts +3 -2
- package/dist/cjs/components/Button/Button.js +9 -14
- package/dist/cjs/components/Button/Button.js.map +1 -1
- package/dist/cjs/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/cjs/components/Modal/Modal.js +2 -0
- package/dist/cjs/components/Modal/Modal.js.map +1 -1
- package/dist/cjs/hooks/index.d.ts +2 -0
- package/dist/cjs/hooks/index.js +2 -0
- package/dist/cjs/hooks/index.js.map +1 -1
- package/dist/cjs/hooks/useBodyScrollLock/index.d.ts +1 -0
- package/dist/cjs/hooks/useBodyScrollLock/index.js +18 -0
- package/dist/cjs/hooks/useBodyScrollLock/index.js.map +1 -0
- package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.d.ts +3 -0
- package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.js +25 -0
- package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.js.map +1 -0
- package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.test.d.ts +1 -0
- package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.test.js +31 -0
- package/dist/cjs/hooks/useBodyScrollLock/useBodyScrollLock.test.js.map +1 -0
- package/dist/cjs/hooks/useButton/index.d.ts +1 -0
- package/dist/cjs/hooks/useButton/index.js +18 -0
- package/dist/cjs/hooks/useButton/index.js.map +1 -0
- package/dist/cjs/hooks/useButton/useButton.d.ts +35 -0
- package/dist/cjs/hooks/useButton/useButton.js +32 -0
- package/dist/cjs/hooks/useButton/useButton.js.map +1 -0
- package/dist/cjs/hooks/useButton/useButton.test.d.ts +1 -0
- package/dist/cjs/hooks/useButton/useButton.test.js +143 -0
- package/dist/cjs/hooks/useButton/useButton.test.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/components/Button/Button.js +10 -15
- package/dist/esm/components/Button/Button.js.map +1 -1
- package/dist/esm/components/DatePicker/DatePicker.js.map +1 -1
- package/dist/esm/components/Modal/Modal.js +2 -0
- package/dist/esm/components/Modal/Modal.js.map +1 -1
- package/dist/esm/hooks/index.js +2 -0
- package/dist/esm/hooks/index.js.map +1 -1
- package/dist/esm/hooks/useBodyScrollLock/index.js +2 -0
- package/dist/esm/hooks/useBodyScrollLock/index.js.map +1 -0
- package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.js +21 -0
- package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.js.map +1 -0
- package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.test.js +29 -0
- package/dist/esm/hooks/useBodyScrollLock/useBodyScrollLock.test.js.map +1 -0
- package/dist/esm/hooks/useButton/index.js +2 -0
- package/dist/esm/hooks/useButton/index.js.map +1 -0
- package/dist/esm/hooks/useButton/useButton.js +28 -0
- package/dist/esm/hooks/useButton/useButton.js.map +1 -0
- package/dist/esm/hooks/useButton/useButton.test.js +141 -0
- package/dist/esm/hooks/useButton/useButton.test.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/mobius.d.ts +43 -2
- package/package.json +1 -1
- package/src/components/Button/Button.tsx +10 -34
- package/src/components/DatePicker/DatePicker.tsx +1 -0
- package/src/components/Modal/Modal.tsx +2 -0
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/useBodyScrollLock/index.ts +1 -0
- package/src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts +34 -0
- package/src/hooks/useBodyScrollLock/useBodyScrollLock.ts +25 -0
- package/src/hooks/useButton/index.tsx +1 -0
- package/src/hooks/useButton/useButton.test.tsx +183 -0
- 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
|
|
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,16 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Ref, forwardRef, RefAttributes,
|
|
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
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
72
|
-
const {
|
|
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
|
-
{...
|
|
97
|
+
{...buttonProps}
|
|
122
98
|
{...hoverProps}
|
|
123
99
|
{...focusProps}
|
|
124
100
|
{...otherProps}
|
|
@@ -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",
|
package/src/hooks/index.tsx
CHANGED
|
@@ -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
|
+
}
|