Skip to content

Commit edd9287

Browse files
committed
feat(divider): Update useVerticalDividerHeight to support any HTMLElement
1 parent 7ccd0a6 commit edd9287

File tree

6 files changed

+155
-149
lines changed

6 files changed

+155
-149
lines changed
Lines changed: 11 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,13 @@
1-
import { forwardRef, HTMLAttributes, Ref, useCallback, useState } from "react";
2-
import { applyRef } from "@react-md/utils";
1+
import { forwardRef, HTMLAttributes } from "react";
32

43
import { Divider } from "./Divider";
4+
import { useVerticalDividerHeight } from "./useVerticalDividerHeight";
55

66
export interface VerticalDividerProps extends HTMLAttributes<HTMLDivElement> {
7-
/**
8-
* The max height for the vertical divider. This number **must** be greater
9-
* than 0 to work correctly.
10-
*
11-
* When the value is between 0 and 1, it will be used as a multiplier with the
12-
* parent element's height. When the value is greater than 1, it will be used
13-
* in `Math.min(parentElementHeight, maxHeight)`.
14-
*/
7+
/** {@inheritDoc VerticalDividerHookOptions.maxHeight} */
158
maxHeight?: number;
169
}
1710

18-
interface VerticalDividerHeight {
19-
ref: (instance: HTMLDivElement | null) => void;
20-
height: number | undefined;
21-
}
22-
23-
/**
24-
* This is a small hook that is used to automatically create a vertical divider
25-
* based on the computed height of its parent element.
26-
*
27-
* @param maxHeight - The max height for the vertical divider. When the value is
28-
* between 0 and 1, it will be used as a percentage. Otherwise the smaller value
29-
* of parent element height and this will be used.
30-
*/
31-
export function useVerticalDividerHeight(
32-
maxHeight: number,
33-
forwardedRef?: Ref<HTMLDivElement | null> | undefined
34-
): VerticalDividerHeight {
35-
if (process.env.NODE_ENV !== "production" && maxHeight < 0) {
36-
throw new Error(
37-
"The `maxHeight` for a vertical divider height must be greater than 0"
38-
);
39-
}
40-
41-
const [height, setHeight] = useState<number | undefined>(undefined);
42-
const ref = useCallback(
43-
(instance: HTMLDivElement | null) => {
44-
applyRef(instance, forwardedRef);
45-
if (!instance || !instance.parentElement) {
46-
return;
47-
}
48-
49-
const height = instance.parentElement.offsetHeight;
50-
if (maxHeight <= 1) {
51-
setHeight(height * maxHeight);
52-
} else {
53-
setHeight(Math.min(height, maxHeight));
54-
}
55-
},
56-
[maxHeight, forwardedRef]
57-
);
58-
59-
return { ref, height };
60-
}
61-
6211
/**
6312
* This component is used to create a vertical divider based on a parent
6413
* element's height. This is really only needed when the parent element **has no
@@ -68,10 +17,13 @@ export function useVerticalDividerHeight(
6817
* the time) when it is not set on a parent element.
6918
*/
7019
export const VerticalDivider = forwardRef<HTMLDivElement, VerticalDividerProps>(
71-
function VerticalDivider({ style, maxHeight = 1, ...props }, forwardedRef) {
72-
const { ref, height } = useVerticalDividerHeight(maxHeight, forwardedRef);
73-
return (
74-
<Divider {...props} style={{ ...style, height }} ref={ref} vertical />
75-
);
20+
function VerticalDivider({ style, maxHeight = 1, ...props }, ref) {
21+
const heightProps = useVerticalDividerHeight({
22+
ref,
23+
style,
24+
maxHeight,
25+
});
26+
27+
return <Divider {...props} {...heightProps} vertical />;
7628
}
7729
);
Lines changed: 2 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { render } from "@testing-library/react";
2-
import { act, renderHook } from "@testing-library/react-hooks";
32

4-
import { useVerticalDividerHeight, VerticalDivider } from "../VerticalDivider";
3+
import { VerticalDivider } from "../VerticalDivider";
54

65
describe("VerticalDivider", () => {
76
it("should render as a div with the vertical divider class names", () => {
@@ -10,92 +9,6 @@ describe("VerticalDivider", () => {
109

1110
expect(divider.tagName).toBe("DIV");
1211
expect(divider.className).toBe("rmd-divider rmd-divider--vertical");
13-
});
14-
15-
describe("useVerticalDividerHeight", () => {
16-
it("should throw an error if the maxHeight is less than 0", () => {
17-
// can't use renderHook for this since the error will be caught in the ErrorBoundary
18-
const Test = () => {
19-
useVerticalDividerHeight(-1);
20-
return null;
21-
};
22-
23-
const consoleError = jest.spyOn(console, "error");
24-
// hide React uncaught error message
25-
consoleError.mockImplementation();
26-
27-
expect(() => render(<Test />)).toThrowError(
28-
"The `maxHeight` for a vertical divider height must be greater than 0"
29-
);
30-
});
31-
32-
it("should provide a ref callback and a height", () => {
33-
let { result } = renderHook(() => useVerticalDividerHeight(5));
34-
expect(result.current).toEqual({
35-
ref: expect.any(Function),
36-
height: undefined,
37-
});
38-
39-
({ result } = renderHook(() => useVerticalDividerHeight(0)));
40-
expect(result.current).toEqual({
41-
ref: expect.any(Function),
42-
height: undefined,
43-
});
44-
45-
({ result } = renderHook(() => useVerticalDividerHeight(1)));
46-
expect(result.current).toEqual({
47-
ref: expect.any(Function),
48-
height: undefined,
49-
});
50-
});
51-
52-
it("should update the height value after the ref is called with an element", () => {
53-
const div = document.createElement("div");
54-
const parentDiv = document.createElement("div");
55-
parentDiv.appendChild(div);
56-
Object.defineProperty(parentDiv, "offsetHeight", { value: 100 });
57-
58-
const { result } = renderHook(() => useVerticalDividerHeight(1));
59-
expect(result.current.height).toBeUndefined();
60-
61-
act(() => result.current.ref(div));
62-
expect(result.current.height).toBe(100);
63-
});
64-
65-
it("should use the maxHeight as a multiplier if it is less than 1", () => {
66-
const div = document.createElement("div");
67-
const parentDiv = document.createElement("div");
68-
parentDiv.appendChild(div);
69-
Object.defineProperty(parentDiv, "offsetHeight", { value: 100 });
70-
71-
const { result } = renderHook(() => useVerticalDividerHeight(0.6));
72-
expect(result.current.height).toBeUndefined();
73-
74-
act(() => result.current.ref(div));
75-
expect(result.current.height).toBe(60);
76-
});
77-
78-
it("should use the maxHeight as a pixel value if it is greater than 1", () => {
79-
const div = document.createElement("div");
80-
const parentDiv = document.createElement("div");
81-
parentDiv.appendChild(div);
82-
Object.defineProperty(parentDiv, "offsetHeight", {
83-
value: 100,
84-
writable: true,
85-
});
86-
87-
const { result } = renderHook(() => useVerticalDividerHeight(80));
88-
expect(result.current.height).toBeUndefined();
89-
90-
act(() => result.current.ref(div));
91-
expect(result.current.height).toBe(80);
92-
93-
Object.defineProperty(parentDiv, "offsetHeight", {
94-
value: 40,
95-
writable: true,
96-
});
97-
act(() => result.current.ref(div));
98-
expect(result.current.height).toBe(40);
99-
});
12+
expect(container).toMatchSnapshot();
10013
});
10114
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`VerticalDivider should render as a div with the vertical divider class names 1`] = `
4+
<div>
5+
<div
6+
class="rmd-divider rmd-divider--vertical"
7+
role="separator"
8+
style="height: 0px;"
9+
/>
10+
</div>
11+
`;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { act, renderHook } from "@testing-library/react-hooks";
2+
3+
import { useVerticalDividerHeight } from "../useVerticalDividerHeight";
4+
5+
describe("useVerticalDivider", () => {
6+
it("should update the height value after the ref is called with an element", () => {
7+
const div = document.createElement("div");
8+
const parentDiv = document.createElement("div");
9+
parentDiv.appendChild(div);
10+
Object.defineProperty(parentDiv, "offsetHeight", { value: 100 });
11+
12+
const { result } = renderHook(() =>
13+
useVerticalDividerHeight({ maxHeight: 1 })
14+
);
15+
expect(result.current.style?.height).toBeUndefined();
16+
17+
act(() => result.current.ref(div));
18+
expect(result.current.style?.height).toBe(100);
19+
});
20+
21+
it("should use the maxHeight as a multiplier if it is less than 1", () => {
22+
const div = document.createElement("div");
23+
const parentDiv = document.createElement("div");
24+
parentDiv.appendChild(div);
25+
Object.defineProperty(parentDiv, "offsetHeight", { value: 100 });
26+
27+
const { result } = renderHook(() =>
28+
useVerticalDividerHeight({ maxHeight: 0.6 })
29+
);
30+
expect(result.current.style?.height).toBeUndefined();
31+
32+
act(() => result.current.ref(div));
33+
expect(result.current.style?.height).toBe(60);
34+
});
35+
36+
it("should use the maxHeight as a pixel value if it is greater than 1", () => {
37+
const div = document.createElement("div");
38+
const parentDiv = document.createElement("div");
39+
parentDiv.appendChild(div);
40+
Object.defineProperty(parentDiv, "offsetHeight", {
41+
value: 100,
42+
writable: true,
43+
});
44+
45+
const { result } = renderHook(() =>
46+
useVerticalDividerHeight({ maxHeight: 80 })
47+
);
48+
expect(result.current.style?.height).toBeUndefined();
49+
50+
act(() => result.current.ref(div));
51+
expect(result.current.style?.height).toBe(80);
52+
53+
Object.defineProperty(parentDiv, "offsetHeight", {
54+
value: 40,
55+
writable: true,
56+
});
57+
act(() => result.current.ref(div));
58+
expect(result.current.style?.height).toBe(40);
59+
});
60+
});

packages/divider/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
* @module @react-md/divider
33
*/
44
export * from "./Divider";
5-
5+
export * from "./useVerticalDividerHeight";
66
export * from "./VerticalDivider";
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { CSSProperties, Ref, RefCallback, useCallback, useState } from "react";
2+
import { applyRef } from "@react-md/utils";
3+
4+
/** @remarks \@since 5.0.0 */
5+
export interface VerticalDividerHookOptions<E extends HTMLElement> {
6+
/**
7+
* An optional ref to merge with the returned ref.
8+
*/
9+
ref?: Ref<E>;
10+
11+
/**
12+
* An optional style object to merge with the divider's height style.
13+
*/
14+
style?: CSSProperties;
15+
16+
/**
17+
* The max height for the vertical divider. When this is `<= 0`, the hook will
18+
* be disabled.
19+
*
20+
* When the value is between 0 and 1, it will be used as a multiplier with the
21+
* parent element's height. When the value is greater than 1, it will be used
22+
* in `Math.min(parentElementHeight, maxHeight)`.
23+
*/
24+
maxHeight: number;
25+
}
26+
27+
/** @remarks \@since 5.0.0 */
28+
export interface VerticalDividerHeight<E extends HTMLElement> {
29+
ref: RefCallback<E>;
30+
style: CSSProperties | undefined;
31+
}
32+
33+
/**
34+
* This is a small hook that is used to automatically create a vertical divider
35+
* based on the computed height of its parent element.
36+
*
37+
* @param maxHeight - The max height for the vertical divider. When the value is
38+
* between 0 and 1, it will be used as a percentage. Otherwise the smaller value
39+
* of parent element height and this will be used.
40+
* @remarks \@since 5.0.0 The hook accepts an object instead of using multiple
41+
* params and uses a generic for the HTMLElement type.
42+
*/
43+
export function useVerticalDividerHeight<E extends HTMLElement>({
44+
ref,
45+
style,
46+
maxHeight,
47+
}: VerticalDividerHookOptions<E>): VerticalDividerHeight<E> {
48+
const [height, setHeight] = useState<number | undefined>(undefined);
49+
const refCallback = useCallback(
50+
(instance: E | null) => {
51+
applyRef(instance, ref);
52+
if (!instance || !instance.parentElement || maxHeight === 0) {
53+
return;
54+
}
55+
56+
const height = instance.parentElement.offsetHeight;
57+
if (maxHeight <= 1) {
58+
setHeight(height * maxHeight);
59+
} else {
60+
setHeight(Math.min(height, maxHeight));
61+
}
62+
},
63+
[maxHeight, ref]
64+
);
65+
66+
return {
67+
ref: refCallback,
68+
style: maxHeight <= 0 ? style : { ...style, height },
69+
};
70+
}

0 commit comments

Comments
 (0)