@refraktor/core 0.0.1 → 0.0.2
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/.turbo/turbo-build.log +1 -1
- package/build/components/menu/menu-dropdown/menu-dropdown.d.ts.map +1 -1
- package/build/components/menu/menu-dropdown/menu-dropdown.js +3 -2
- package/build/components/menu/menu-sub-dropdown/menu-sub-dropdown.d.ts.map +1 -1
- package/build/components/menu/menu-sub-dropdown/menu-sub-dropdown.js +2 -1
- package/build/components/menu/use-menu.d.ts.map +1 -1
- package/build/components/menu/use-menu.js +2 -1
- package/build/components/modal/modal-close/modal-close.d.ts.map +1 -1
- package/build/components/modal/modal-close/modal-close.js +1 -1
- package/build/components/modal/modal-content/modal-content.d.ts.map +1 -1
- package/build/components/modal/modal-content/modal-content.js +1 -1
- package/build/components/modal/modal-header/modal-header.d.ts.map +1 -1
- package/build/components/modal/modal-header/modal-header.js +1 -1
- package/build/components/number-input/number-input.d.ts.map +1 -1
- package/build/components/number-input/number-input.js +22 -15
- package/build/components/number-input/number-input.test.d.ts +2 -0
- package/build/components/number-input/number-input.test.d.ts.map +1 -0
- package/build/components/number-input/number-input.test.js +14 -0
- package/build/components/number-input/number-input.types.d.ts +2 -2
- package/build/components/number-input/number-input.types.d.ts.map +1 -1
- package/build/components/popover/popover-dropdown/popover-dropdown.d.ts.map +1 -1
- package/build/components/popover/popover-dropdown/popover-dropdown.js +2 -1
- package/build/components/popover/use-popover.d.ts.map +1 -1
- package/build/components/popover/use-popover.js +2 -1
- package/build/components/portal/portal.js +1 -1
- package/build/components/select/select-dropdown/select-dropdown.d.ts.map +1 -1
- package/build/components/select/select-dropdown/select-dropdown.js +3 -2
- package/build/components/select/select-item/select-item.d.ts.map +1 -1
- package/build/components/select/select-item/select-item.js +1 -1
- package/build/components/select/select-root/select-root.d.ts.map +1 -1
- package/build/components/select/select-root/select-root.js +36 -6
- package/build/components/select/select-trigger/select-trigger.d.ts.map +1 -1
- package/build/components/select/select-trigger/select-trigger.js +1 -1
- package/build/components/select/select.context.d.ts +2 -0
- package/build/components/select/select.context.d.ts.map +1 -1
- package/build/components/select/select.test.js +17 -0
- package/build/components/select/select.types.d.ts +10 -0
- package/build/components/select/select.types.d.ts.map +1 -1
- package/build/components/select/use-select.d.ts.map +1 -1
- package/build/components/select/use-select.js +2 -1
- package/build/components/switch/switch.js +1 -1
- package/build/components/tabs/tabs-tab/tabs-tab.d.ts.map +1 -1
- package/build/components/tabs/tabs-tab/tabs-tab.js +0 -6
- package/build/components/tooltip/tooltip.d.ts.map +1 -1
- package/build/components/tooltip/tooltip.js +7 -3
- package/build/components/tooltip/use-tooltip.d.ts.map +1 -1
- package/build/components/tooltip/use-tooltip.js +2 -1
- package/build/components/transition/transition.d.ts.map +1 -1
- package/build/components/transition/transition.js +16 -12
- package/build/style.css +1 -1
- package/package.json +2 -2
- package/src/components/menu/menu-dropdown/menu-dropdown.tsx +4 -3
- package/src/components/menu/menu-sub-dropdown/menu-sub-dropdown.tsx +2 -0
- package/src/components/menu/use-menu.ts +2 -1
- package/src/components/modal/modal-close/modal-close.tsx +1 -5
- package/src/components/modal/modal-content/modal-content.tsx +3 -4
- package/src/components/modal/modal-header/modal-header.tsx +4 -2
- package/src/components/modal/modal-overlay/modal-overlay.tsx +64 -64
- package/src/components/modal/modal.test.tsx +110 -110
- package/src/components/modal/use-modal.ts +101 -101
- package/src/components/number-input/number-input.test.tsx +22 -0
- package/src/components/number-input/number-input.tsx +79 -51
- package/src/components/number-input/number-input.types.ts +8 -8
- package/src/components/popover/popover-dropdown/popover-dropdown.tsx +2 -0
- package/src/components/popover/use-popover.ts +2 -1
- package/src/components/portal/portal.tsx +1 -1
- package/src/components/select/select-dropdown/select-dropdown.tsx +4 -3
- package/src/components/select/select-item/select-item.tsx +0 -1
- package/src/components/select/select-root/select-root.tsx +87 -11
- package/src/components/select/select-trigger/select-trigger.tsx +2 -0
- package/src/components/select/select.context.ts +2 -0
- package/src/components/select/select.test.tsx +35 -0
- package/src/components/select/select.types.ts +15 -0
- package/src/components/select/use-select.ts +2 -1
- package/src/components/switch/switch.tsx +1 -1
- package/src/components/tabs/tabs-tab/tabs-tab.tsx +0 -8
- package/src/components/tooltip/tooltip.tsx +7 -1
- package/src/components/tooltip/use-tooltip.ts +2 -1
- package/src/components/transition/transition.tsx +18 -14
- package/src/style.css +0 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { render, screen, userEvent, waitFor } from "../../vitest";
|
|
3
|
-
import Modal from "./modal";
|
|
4
|
-
import { ModalContent } from "./modal-content";
|
|
5
|
-
import { ModalOverlay } from "./modal-overlay";
|
|
6
|
-
import { ModalRoot } from "./modal-root";
|
|
7
|
-
|
|
8
|
-
describe("@refraktor/core/Modal", () => {
|
|
9
|
-
const transitionProps = {
|
|
10
|
-
duration: 0,
|
|
11
|
-
immediate: true
|
|
12
|
-
} as const;
|
|
13
|
-
|
|
14
|
-
it("renders with compound subcomponents and closes with header close button", async () => {
|
|
15
|
-
const user = userEvent.setup();
|
|
16
|
-
|
|
17
|
-
await render(
|
|
18
|
-
<Modal defaultOpened transitionProps={transitionProps}>
|
|
19
|
-
<Modal.Overlay />
|
|
20
|
-
|
|
21
|
-
<Modal.Content>
|
|
22
|
-
<Modal.Header text="Delete item" />
|
|
23
|
-
<p>Are you sure?</p>
|
|
24
|
-
</Modal.Content>
|
|
25
|
-
</Modal>
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
expect(
|
|
29
|
-
await screen.findByRole("dialog", { name: "Delete item" })
|
|
30
|
-
).toBeInTheDocument();
|
|
31
|
-
|
|
32
|
-
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
33
|
-
|
|
34
|
-
await waitFor(() => {
|
|
35
|
-
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("calls onOpenedChange when clicking overlay in controlled mode", async () => {
|
|
40
|
-
const user = userEvent.setup();
|
|
41
|
-
const onOpenedChange = vi.fn();
|
|
42
|
-
|
|
43
|
-
await render(
|
|
44
|
-
<Modal
|
|
45
|
-
opened
|
|
46
|
-
onOpenedChange={onOpenedChange}
|
|
47
|
-
transitionProps={transitionProps}
|
|
48
|
-
>
|
|
49
|
-
<Modal.Overlay data-testid="overlay" />
|
|
50
|
-
<Modal.Content>Controlled modal</Modal.Content>
|
|
51
|
-
</Modal>
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
await user.click(await screen.findByTestId("overlay"));
|
|
55
|
-
|
|
56
|
-
expect(onOpenedChange).toHaveBeenCalledWith(false);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("closes on Escape key", async () => {
|
|
60
|
-
const user = userEvent.setup();
|
|
61
|
-
|
|
62
|
-
await render(
|
|
63
|
-
<Modal defaultOpened transitionProps={transitionProps}>
|
|
64
|
-
<Modal.Content>Keyboard close</Modal.Content>
|
|
65
|
-
</Modal>
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
await screen.findByRole("dialog");
|
|
69
|
-
|
|
70
|
-
await user.keyboard("{Escape}");
|
|
71
|
-
|
|
72
|
-
await waitFor(() => {
|
|
73
|
-
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("supports standalone subcomponents with ModalRoot", async () => {
|
|
78
|
-
await render(
|
|
79
|
-
<ModalRoot defaultOpened transitionProps={transitionProps}>
|
|
80
|
-
<ModalOverlay />
|
|
81
|
-
<ModalContent>Standalone composition</ModalContent>
|
|
82
|
-
</ModalRoot>
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
expect(await screen.findByRole("dialog")).toBeInTheDocument();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("locks and unlocks body scroll when enabled", async () => {
|
|
89
|
-
const user = userEvent.setup();
|
|
90
|
-
|
|
91
|
-
await render(
|
|
92
|
-
<Modal defaultOpened lockScroll transitionProps={transitionProps}>
|
|
93
|
-
<Modal.Content>
|
|
94
|
-
Scroll locked
|
|
95
|
-
<Modal.Close />
|
|
96
|
-
</Modal.Content>
|
|
97
|
-
</Modal>
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
await waitFor(() => {
|
|
101
|
-
expect(document.body).toHaveAttribute("data-scroll-locked");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
105
|
-
|
|
106
|
-
await waitFor(() => {
|
|
107
|
-
expect(document.body).not.toHaveAttribute("data-scroll-locked");
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
});
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { render, screen, userEvent, waitFor } from "../../vitest";
|
|
3
|
+
import Modal from "./modal";
|
|
4
|
+
import { ModalContent } from "./modal-content";
|
|
5
|
+
import { ModalOverlay } from "./modal-overlay";
|
|
6
|
+
import { ModalRoot } from "./modal-root";
|
|
7
|
+
|
|
8
|
+
describe("@refraktor/core/Modal", () => {
|
|
9
|
+
const transitionProps = {
|
|
10
|
+
duration: 0,
|
|
11
|
+
immediate: true
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
it("renders with compound subcomponents and closes with header close button", async () => {
|
|
15
|
+
const user = userEvent.setup();
|
|
16
|
+
|
|
17
|
+
await render(
|
|
18
|
+
<Modal defaultOpened transitionProps={transitionProps}>
|
|
19
|
+
<Modal.Overlay />
|
|
20
|
+
|
|
21
|
+
<Modal.Content>
|
|
22
|
+
<Modal.Header text="Delete item" />
|
|
23
|
+
<p>Are you sure?</p>
|
|
24
|
+
</Modal.Content>
|
|
25
|
+
</Modal>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(
|
|
29
|
+
await screen.findByRole("dialog", { name: "Delete item" })
|
|
30
|
+
).toBeInTheDocument();
|
|
31
|
+
|
|
32
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
33
|
+
|
|
34
|
+
await waitFor(() => {
|
|
35
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("calls onOpenedChange when clicking overlay in controlled mode", async () => {
|
|
40
|
+
const user = userEvent.setup();
|
|
41
|
+
const onOpenedChange = vi.fn();
|
|
42
|
+
|
|
43
|
+
await render(
|
|
44
|
+
<Modal
|
|
45
|
+
opened
|
|
46
|
+
onOpenedChange={onOpenedChange}
|
|
47
|
+
transitionProps={transitionProps}
|
|
48
|
+
>
|
|
49
|
+
<Modal.Overlay data-testid="overlay" />
|
|
50
|
+
<Modal.Content>Controlled modal</Modal.Content>
|
|
51
|
+
</Modal>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await user.click(await screen.findByTestId("overlay"));
|
|
55
|
+
|
|
56
|
+
expect(onOpenedChange).toHaveBeenCalledWith(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("closes on Escape key", async () => {
|
|
60
|
+
const user = userEvent.setup();
|
|
61
|
+
|
|
62
|
+
await render(
|
|
63
|
+
<Modal defaultOpened transitionProps={transitionProps}>
|
|
64
|
+
<Modal.Content>Keyboard close</Modal.Content>
|
|
65
|
+
</Modal>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
await screen.findByRole("dialog");
|
|
69
|
+
|
|
70
|
+
await user.keyboard("{Escape}");
|
|
71
|
+
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("supports standalone subcomponents with ModalRoot", async () => {
|
|
78
|
+
await render(
|
|
79
|
+
<ModalRoot defaultOpened transitionProps={transitionProps}>
|
|
80
|
+
<ModalOverlay />
|
|
81
|
+
<ModalContent>Standalone composition</ModalContent>
|
|
82
|
+
</ModalRoot>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(await screen.findByRole("dialog")).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("locks and unlocks body scroll when enabled", async () => {
|
|
89
|
+
const user = userEvent.setup();
|
|
90
|
+
|
|
91
|
+
await render(
|
|
92
|
+
<Modal defaultOpened lockScroll transitionProps={transitionProps}>
|
|
93
|
+
<Modal.Content>
|
|
94
|
+
Scroll locked
|
|
95
|
+
<Modal.Close />
|
|
96
|
+
</Modal.Content>
|
|
97
|
+
</Modal>
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
expect(document.body).toHaveAttribute("data-scroll-locked");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await user.click(screen.getByRole("button", { name: "Close" }));
|
|
105
|
+
|
|
106
|
+
await waitFor(() => {
|
|
107
|
+
expect(document.body).not.toHaveAttribute("data-scroll-locked");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
import { useUncontrolled } from "@refraktor/utils";
|
|
2
|
-
import { useCallback, useEffect } from "react";
|
|
3
|
-
|
|
4
|
-
interface UseModalProps {
|
|
5
|
-
opened?: boolean;
|
|
6
|
-
defaultOpened?: boolean;
|
|
7
|
-
onOpenedChange?: (opened: boolean) => void;
|
|
8
|
-
closeOnClickOutside?: boolean;
|
|
9
|
-
closeOnEscape?: boolean;
|
|
10
|
-
contentRef: React.MutableRefObject<HTMLElement | null>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface UseModalReturn {
|
|
14
|
-
opened: boolean;
|
|
15
|
-
open: () => void;
|
|
16
|
-
close: () => void;
|
|
17
|
-
toggle: () => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function useModal(options: UseModalProps): UseModalReturn {
|
|
21
|
-
const {
|
|
22
|
-
opened,
|
|
23
|
-
defaultOpened,
|
|
24
|
-
onOpenedChange,
|
|
25
|
-
closeOnClickOutside = true,
|
|
26
|
-
closeOnEscape = true,
|
|
27
|
-
contentRef
|
|
28
|
-
} = options;
|
|
29
|
-
|
|
30
|
-
const [isOpen, setIsOpen] = useUncontrolled({
|
|
31
|
-
value: opened,
|
|
32
|
-
defaultValue: defaultOpened,
|
|
33
|
-
finalValue: false,
|
|
34
|
-
onChange: onOpenedChange
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const open = useCallback(() => {
|
|
38
|
-
setIsOpen(true);
|
|
39
|
-
}, [setIsOpen]);
|
|
40
|
-
|
|
41
|
-
const close = useCallback(() => {
|
|
42
|
-
setIsOpen(false);
|
|
43
|
-
}, [setIsOpen]);
|
|
44
|
-
|
|
45
|
-
const toggle = useCallback(() => {
|
|
46
|
-
setIsOpen(!isOpen);
|
|
47
|
-
}, [isOpen, setIsOpen]);
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
if (!isOpen || !closeOnEscape) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
55
|
-
if (event.key === "Escape") {
|
|
56
|
-
setIsOpen(false);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
61
|
-
|
|
62
|
-
return () => {
|
|
63
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
64
|
-
};
|
|
65
|
-
}, [closeOnEscape, isOpen, setIsOpen]);
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (!isOpen || !closeOnClickOutside) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const handlePointerDown = (event: MouseEvent | TouchEvent) => {
|
|
73
|
-
const target = event.target;
|
|
74
|
-
|
|
75
|
-
if (!(target instanceof Node)) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (contentRef.current?.contains(target)) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
setIsOpen(false);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
document.addEventListener("mousedown", handlePointerDown);
|
|
87
|
-
document.addEventListener("touchstart", handlePointerDown);
|
|
88
|
-
|
|
89
|
-
return () => {
|
|
90
|
-
document.removeEventListener("mousedown", handlePointerDown);
|
|
91
|
-
document.removeEventListener("touchstart", handlePointerDown);
|
|
92
|
-
};
|
|
93
|
-
}, [closeOnClickOutside, contentRef, isOpen, setIsOpen]);
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
opened: isOpen,
|
|
97
|
-
open,
|
|
98
|
-
close,
|
|
99
|
-
toggle
|
|
100
|
-
};
|
|
101
|
-
}
|
|
1
|
+
import { useUncontrolled } from "@refraktor/utils";
|
|
2
|
+
import { useCallback, useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
interface UseModalProps {
|
|
5
|
+
opened?: boolean;
|
|
6
|
+
defaultOpened?: boolean;
|
|
7
|
+
onOpenedChange?: (opened: boolean) => void;
|
|
8
|
+
closeOnClickOutside?: boolean;
|
|
9
|
+
closeOnEscape?: boolean;
|
|
10
|
+
contentRef: React.MutableRefObject<HTMLElement | null>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseModalReturn {
|
|
14
|
+
opened: boolean;
|
|
15
|
+
open: () => void;
|
|
16
|
+
close: () => void;
|
|
17
|
+
toggle: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useModal(options: UseModalProps): UseModalReturn {
|
|
21
|
+
const {
|
|
22
|
+
opened,
|
|
23
|
+
defaultOpened,
|
|
24
|
+
onOpenedChange,
|
|
25
|
+
closeOnClickOutside = true,
|
|
26
|
+
closeOnEscape = true,
|
|
27
|
+
contentRef
|
|
28
|
+
} = options;
|
|
29
|
+
|
|
30
|
+
const [isOpen, setIsOpen] = useUncontrolled({
|
|
31
|
+
value: opened,
|
|
32
|
+
defaultValue: defaultOpened,
|
|
33
|
+
finalValue: false,
|
|
34
|
+
onChange: onOpenedChange
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const open = useCallback(() => {
|
|
38
|
+
setIsOpen(true);
|
|
39
|
+
}, [setIsOpen]);
|
|
40
|
+
|
|
41
|
+
const close = useCallback(() => {
|
|
42
|
+
setIsOpen(false);
|
|
43
|
+
}, [setIsOpen]);
|
|
44
|
+
|
|
45
|
+
const toggle = useCallback(() => {
|
|
46
|
+
setIsOpen(!isOpen);
|
|
47
|
+
}, [isOpen, setIsOpen]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!isOpen || !closeOnEscape) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
55
|
+
if (event.key === "Escape") {
|
|
56
|
+
setIsOpen(false);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
64
|
+
};
|
|
65
|
+
}, [closeOnEscape, isOpen, setIsOpen]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!isOpen || !closeOnClickOutside) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handlePointerDown = (event: MouseEvent | TouchEvent) => {
|
|
73
|
+
const target = event.target;
|
|
74
|
+
|
|
75
|
+
if (!(target instanceof Node)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (contentRef.current?.contains(target)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setIsOpen(false);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
87
|
+
document.addEventListener("touchstart", handlePointerDown);
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
document.removeEventListener("mousedown", handlePointerDown);
|
|
91
|
+
document.removeEventListener("touchstart", handlePointerDown);
|
|
92
|
+
};
|
|
93
|
+
}, [closeOnClickOutside, contentRef, isOpen, setIsOpen]);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
opened: isOpen,
|
|
97
|
+
open,
|
|
98
|
+
close,
|
|
99
|
+
toggle
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen } from "../../vitest";
|
|
3
|
+
import NumberInput from "./number-input";
|
|
4
|
+
|
|
5
|
+
describe("@refraktor/core/NumberInput", () => {
|
|
6
|
+
it("supports input wrapper props", async () => {
|
|
7
|
+
await render(
|
|
8
|
+
<NumberInput
|
|
9
|
+
label="Quantity"
|
|
10
|
+
description="Enter desired amount"
|
|
11
|
+
error="Quantity is required"
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const input = screen.getByLabelText("Quantity");
|
|
16
|
+
|
|
17
|
+
expect(input).toHaveAttribute("type", "number");
|
|
18
|
+
expect(input).toHaveAttribute("aria-invalid", "true");
|
|
19
|
+
expect(screen.getByText("Enter desired amount")).toBeInTheDocument();
|
|
20
|
+
expect(screen.getByText("Quantity is required")).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -69,6 +69,11 @@ const NumberInput = factory<NumberInputFactoryPayload>((_props, ref) => {
|
|
|
69
69
|
const { cx } = useTheme();
|
|
70
70
|
const {
|
|
71
71
|
id,
|
|
72
|
+
label,
|
|
73
|
+
description,
|
|
74
|
+
error,
|
|
75
|
+
required,
|
|
76
|
+
withAsterisk,
|
|
72
77
|
value,
|
|
73
78
|
defaultValue,
|
|
74
79
|
size,
|
|
@@ -134,8 +139,8 @@ const NumberInput = factory<NumberInputFactoryPayload>((_props, ref) => {
|
|
|
134
139
|
};
|
|
135
140
|
|
|
136
141
|
const stopHold = () => {
|
|
137
|
-
if (holdTimer.current)
|
|
138
|
-
if (repeatTimer.current)
|
|
142
|
+
if (holdTimer.current) clearInterval(holdTimer.current);
|
|
143
|
+
if (repeatTimer.current) clearTimeout(repeatTimer.current);
|
|
139
144
|
holdTimer.current = null;
|
|
140
145
|
repeatTimer.current = null;
|
|
141
146
|
};
|
|
@@ -292,56 +297,79 @@ const NumberInput = factory<NumberInputFactoryPayload>((_props, ref) => {
|
|
|
292
297
|
);
|
|
293
298
|
}, [controlsPosition, canIncrement, canDecrement, classes, controlSizing]);
|
|
294
299
|
|
|
300
|
+
const hasWrapper = label || description || error;
|
|
301
|
+
|
|
302
|
+
const field = (
|
|
303
|
+
<InputField
|
|
304
|
+
ref={ref}
|
|
305
|
+
id={_id}
|
|
306
|
+
type="number"
|
|
307
|
+
required={required}
|
|
308
|
+
error={!!error}
|
|
309
|
+
disabled={disabled}
|
|
310
|
+
size={size}
|
|
311
|
+
min={min}
|
|
312
|
+
max={max}
|
|
313
|
+
step={step}
|
|
314
|
+
value={_value}
|
|
315
|
+
onChange={handleChange}
|
|
316
|
+
onBlur={handleBlur}
|
|
317
|
+
onKeyDown={handleKeyDown}
|
|
318
|
+
onWheel={handleWheel}
|
|
319
|
+
leftSection={controlsPosition === "left" ? controls : undefined}
|
|
320
|
+
rightSection={controlsPosition === "right" ? controls : undefined}
|
|
321
|
+
className={className}
|
|
322
|
+
classNames={{
|
|
323
|
+
root: cx(
|
|
324
|
+
"px-0 gap-0 items-stretch overflow-hidden",
|
|
325
|
+
controlsPosition === "none"
|
|
326
|
+
? "px-2"
|
|
327
|
+
: controlsPosition === "left"
|
|
328
|
+
? "pr-2"
|
|
329
|
+
: "pl-2",
|
|
330
|
+
classes.root
|
|
331
|
+
),
|
|
332
|
+
leftSection: cx(
|
|
333
|
+
"h-full self-stretch",
|
|
334
|
+
controlsPosition === "left" && controlSizing.leftSpacing,
|
|
335
|
+
classes.leftSection
|
|
336
|
+
),
|
|
337
|
+
rightSection: cx(
|
|
338
|
+
"h-full self-stretch",
|
|
339
|
+
controlsPosition === "right" && controlSizing.rightSpacing,
|
|
340
|
+
classes.rightSection
|
|
341
|
+
),
|
|
342
|
+
...(classes as any)
|
|
343
|
+
}}
|
|
344
|
+
aria-describedby={
|
|
345
|
+
error
|
|
346
|
+
? `${_id}-error`
|
|
347
|
+
: description
|
|
348
|
+
? `${_id}-description`
|
|
349
|
+
: undefined
|
|
350
|
+
}
|
|
351
|
+
aria-disabled={disabled}
|
|
352
|
+
aria-valuemin={min}
|
|
353
|
+
aria-valuemax={max}
|
|
354
|
+
aria-valuenow={Number(_value)}
|
|
355
|
+
{...props}
|
|
356
|
+
/>
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
if (!hasWrapper) {
|
|
360
|
+
return field;
|
|
361
|
+
}
|
|
362
|
+
|
|
295
363
|
return (
|
|
296
|
-
<InputWrapper
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
value={_value}
|
|
306
|
-
onChange={handleChange}
|
|
307
|
-
onBlur={handleBlur}
|
|
308
|
-
onKeyDown={handleKeyDown}
|
|
309
|
-
onWheel={handleWheel}
|
|
310
|
-
leftSection={controlsPosition === "left" ? controls : undefined}
|
|
311
|
-
rightSection={
|
|
312
|
-
controlsPosition === "right" ? controls : undefined
|
|
313
|
-
}
|
|
314
|
-
className={className}
|
|
315
|
-
classNames={{
|
|
316
|
-
root: cx(
|
|
317
|
-
"px-0 gap-0 items-stretch overflow-hidden",
|
|
318
|
-
controlsPosition === "none"
|
|
319
|
-
? "px-2"
|
|
320
|
-
: controlsPosition === "left"
|
|
321
|
-
? "pr-2"
|
|
322
|
-
: "pl-2",
|
|
323
|
-
classes.root
|
|
324
|
-
),
|
|
325
|
-
leftSection: cx(
|
|
326
|
-
"h-full self-stretch",
|
|
327
|
-
controlsPosition === "left" &&
|
|
328
|
-
controlSizing.leftSpacing,
|
|
329
|
-
classes.leftSection
|
|
330
|
-
),
|
|
331
|
-
rightSection: cx(
|
|
332
|
-
"h-full self-stretch",
|
|
333
|
-
controlsPosition === "right" &&
|
|
334
|
-
controlSizing.rightSpacing,
|
|
335
|
-
classes.rightSection
|
|
336
|
-
),
|
|
337
|
-
...(classes as any)
|
|
338
|
-
}}
|
|
339
|
-
aria-disabled={disabled}
|
|
340
|
-
aria-valuemin={min}
|
|
341
|
-
aria-valuemax={max}
|
|
342
|
-
aria-valuenow={Number(_value)}
|
|
343
|
-
{...props}
|
|
344
|
-
/>
|
|
364
|
+
<InputWrapper
|
|
365
|
+
label={label}
|
|
366
|
+
description={description}
|
|
367
|
+
error={error}
|
|
368
|
+
required={required}
|
|
369
|
+
withAsterisk={withAsterisk}
|
|
370
|
+
inputId={_id}
|
|
371
|
+
>
|
|
372
|
+
{field}
|
|
345
373
|
</InputWrapper>
|
|
346
374
|
);
|
|
347
375
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createClassNamesConfig,
|
|
3
|
-
createComponentConfig,
|
|
4
|
-
FactoryPayload
|
|
5
|
-
} from "../../utils";
|
|
6
|
-
import { InputFieldClassNames,
|
|
1
|
+
import {
|
|
2
|
+
createClassNamesConfig,
|
|
3
|
+
createComponentConfig,
|
|
4
|
+
FactoryPayload
|
|
5
|
+
} from "../../utils";
|
|
6
|
+
import { InputFieldClassNames, InputProps } from "../input";
|
|
7
7
|
|
|
8
8
|
export type NumberInputControlsPosition = "left" | "right" | "none";
|
|
9
9
|
|
|
@@ -43,8 +43,8 @@ export interface _NumberInputProps {
|
|
|
43
43
|
classNames?: NumberInputClassNames;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export type NumberInputProps = _NumberInputProps &
|
|
47
|
-
Omit<
|
|
46
|
+
export type NumberInputProps = _NumberInputProps &
|
|
47
|
+
Omit<InputProps, "onChange" | "type" | keyof _NumberInputProps>;
|
|
48
48
|
|
|
49
49
|
export interface NumberInputFactoryPayload extends FactoryPayload {
|
|
50
50
|
props: NumberInputProps;
|
|
@@ -26,6 +26,7 @@ const PopoverDropdown = factory<PopoverDropdownFactoryPayload>(
|
|
|
26
26
|
transition="fade"
|
|
27
27
|
duration={200}
|
|
28
28
|
mounted={popover.opened}
|
|
29
|
+
style={{ position: "relative", zIndex: 1000 }}
|
|
29
30
|
{...transitionProps}
|
|
30
31
|
>
|
|
31
32
|
<div
|
|
@@ -42,6 +43,7 @@ const PopoverDropdown = factory<PopoverDropdownFactoryPayload>(
|
|
|
42
43
|
aria-modal="true"
|
|
43
44
|
style={{
|
|
44
45
|
...popover.floatingStyles,
|
|
46
|
+
zIndex: 1000,
|
|
45
47
|
...style
|
|
46
48
|
}}
|
|
47
49
|
className={cx(
|
|
@@ -142,7 +142,8 @@ export function usePopover(options: UsePopoverProps = {}): UsePopoverReturn {
|
|
|
142
142
|
open: isOpen,
|
|
143
143
|
onOpenChange: setIsOpen,
|
|
144
144
|
middleware: middleware,
|
|
145
|
-
whileElementsMounted: autoUpdate
|
|
145
|
+
whileElementsMounted: autoUpdate,
|
|
146
|
+
strategy: "fixed"
|
|
146
147
|
});
|
|
147
148
|
|
|
148
149
|
const click = useClick(floating.context, {
|
|
@@ -98,7 +98,7 @@ const Portal = factory<PortalFactoryPayload>((_props, ref) => {
|
|
|
98
98
|
return createPortal(<>{children}</>, nodeRef.current);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
Portal.displayName = "@
|
|
101
|
+
Portal.displayName = "@refraktor/core/Portal";
|
|
102
102
|
Portal.configure = createComponentConfig<PortalProps>();
|
|
103
103
|
|
|
104
104
|
export default Portal;
|