mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 19:50:36 +03:00
implement OverlayScrollbarsComponent for react
This commit is contained in:
@@ -2,3 +2,9 @@ import matchers from '@testing-library/jest-dom/matchers';
|
|||||||
import { expect } from 'vitest';
|
import { expect } from 'vitest';
|
||||||
|
|
||||||
expect.extend(matchers);
|
expect.extend(matchers);
|
||||||
|
|
||||||
|
// remove jsdom warning for not implemented second argument for window.getComputedStyle
|
||||||
|
try {
|
||||||
|
const cmptdStyle = window.getComputedStyle;
|
||||||
|
window.getComputedStyle = (a) => cmptdStyle(a);
|
||||||
|
} catch {}
|
||||||
|
|||||||
@@ -1,24 +1,75 @@
|
|||||||
export interface OverlayScrollbarsComponentProps {
|
import { forwardRef, useEffect, useRef, useImperativeHandle } from 'react';
|
||||||
element?: string;
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||||
options?: {};
|
import type { PartialOptions, EventListeners } from 'overlayscrollbars';
|
||||||
events?: {};
|
import type { ComponentPropsWithoutRef, ElementRef, ForwardedRef } from 'react';
|
||||||
|
|
||||||
|
export type OverlayScrollbarsComponentProps<T extends keyof JSX.IntrinsicElements = 'div'> =
|
||||||
|
ComponentPropsWithoutRef<T> & {
|
||||||
|
element?: T;
|
||||||
|
options?: PartialOptions;
|
||||||
|
events?: EventListeners;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface OverlayScrollbarsComponentRef<T extends keyof JSX.IntrinsicElements = 'div'> {
|
||||||
|
osInstance(): OverlayScrollbars | null;
|
||||||
|
osTarget(): ElementRef<T> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OverlayScrollbarsComponent = (props: OverlayScrollbarsComponentProps) => {
|
const OverlayScrollbarsComponent = <T extends keyof JSX.IntrinsicElements>(
|
||||||
const { msg } = props;
|
props: OverlayScrollbarsComponentProps<T>,
|
||||||
return (
|
ref: ForwardedRef<OverlayScrollbarsComponentRef<T>>
|
||||||
<div className="App">
|
) => {
|
||||||
<header className="App-header">
|
const { element = 'div', options, events, ...other } = props;
|
||||||
<p>{msg}</p>
|
const Tag = element;
|
||||||
<a
|
|
||||||
className="App-link"
|
const osTargetRef = useRef<ElementRef<T>>(null);
|
||||||
href="https://reactjs.org"
|
const osInstanceRef = useRef<OverlayScrollbars | null>(null);
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
useEffect(() => {
|
||||||
>
|
const { current: target } = osTargetRef;
|
||||||
Learn React
|
if (target) {
|
||||||
</a>
|
const instance = OverlayScrollbars(target as any, options || {});
|
||||||
</header>
|
osInstanceRef.current = instance;
|
||||||
</div>
|
|
||||||
|
return () => instance.destroy();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { current: instance } = osInstanceRef;
|
||||||
|
if (OverlayScrollbars.valid(instance) && options) {
|
||||||
|
instance.options(options, true);
|
||||||
|
}
|
||||||
|
}, [options]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { current: instance } = osInstanceRef;
|
||||||
|
if (OverlayScrollbars.valid(instance) && events) {
|
||||||
|
return instance.on(events);
|
||||||
|
}
|
||||||
|
}, [events]);
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => {
|
||||||
|
return {
|
||||||
|
osInstance: () => osInstanceRef.current,
|
||||||
|
osTarget: () => osTargetRef.current,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return <Tag data-overlayscrollbars="" {...other} ref={osTargetRef} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const OverlayScrollbarsComponentForwardedRef = forwardRef(OverlayScrollbarsComponent) as <
|
||||||
|
T extends keyof JSX.IntrinsicElements
|
||||||
|
>(
|
||||||
|
props: OverlayScrollbarsComponentProps<T> & {
|
||||||
|
ref?: ForwardedRef<OverlayScrollbarsComponentRef<T>>;
|
||||||
|
}
|
||||||
|
) => ReturnType<typeof OverlayScrollbarsComponent>;
|
||||||
|
|
||||||
|
export { OverlayScrollbarsComponentForwardedRef as OverlayScrollbarsComponent };
|
||||||
|
|||||||
@@ -1,9 +1,91 @@
|
|||||||
import { test, expect } from 'vitest';
|
import { describe, test, expect } from 'vitest';
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||||
import { OverlayScrollbarsComponent } from '~/overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from '~/overlayscrollbars-react';
|
||||||
|
import type { OverlayScrollbarsComponentRef } from '~/overlayscrollbars-react';
|
||||||
|
import type { RefObject } from 'react';
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
describe('OverlayScrollbarsComponent', () => {
|
||||||
render(<OverlayScrollbarsComponent msg="hi" />);
|
describe('correct rendering', () => {
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
test('correct root element', () => {
|
||||||
expect(linkElement).toBeInTheDocument();
|
const elementA = 'code';
|
||||||
|
const elementB = 'span';
|
||||||
|
const { container, rerender } = render(<OverlayScrollbarsComponent />);
|
||||||
|
|
||||||
|
expect(container).not.toBeEmptyDOMElement();
|
||||||
|
expect(container.querySelector('div')).toBe(container.firstElementChild); // default is div
|
||||||
|
|
||||||
|
rerender(<OverlayScrollbarsComponent element={elementA} />);
|
||||||
|
expect(container.querySelector(elementA)).toBe(container.firstElementChild);
|
||||||
|
|
||||||
|
rerender(<OverlayScrollbarsComponent element={elementB} />);
|
||||||
|
expect(container.querySelector(elementB)).toBe(container.firstElementChild);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('children', () => {
|
||||||
|
render(
|
||||||
|
<OverlayScrollbarsComponent>
|
||||||
|
hello <span>react</span>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
);
|
||||||
|
expect(screen.getByText(/hello/)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/react/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('className', () => {
|
||||||
|
const { container, rerender } = render(
|
||||||
|
<OverlayScrollbarsComponent className="overlay scrollbars" />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.firstElementChild).toHaveClass('overlay', 'scrollbars');
|
||||||
|
|
||||||
|
rerender(<OverlayScrollbarsComponent className="overlay scrollbars react" />);
|
||||||
|
|
||||||
|
expect(container.firstElementChild).toHaveClass('overlay', 'scrollbars', 'react');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('style', () => {
|
||||||
|
const { container, rerender } = render(
|
||||||
|
<OverlayScrollbarsComponent style={{ width: '22px' }} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.firstElementChild).toHaveStyle({ width: '22px' });
|
||||||
|
|
||||||
|
rerender(<OverlayScrollbarsComponent style={{ height: '33px' }} />);
|
||||||
|
|
||||||
|
expect(container.firstElementChild).toHaveStyle({ height: '33px' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ref', () => {
|
||||||
|
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
|
||||||
|
const { container } = render(<OverlayScrollbarsComponent ref={ref} />);
|
||||||
|
|
||||||
|
const { osInstance, osTarget } = ref.current!;
|
||||||
|
expect(osInstance).toBeTypeOf('function');
|
||||||
|
expect(osTarget).toBeTypeOf('function');
|
||||||
|
expect(OverlayScrollbars.valid(osInstance())).toBe(true);
|
||||||
|
expect(osTarget()).toBe(container.firstElementChild);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('options', () => {
|
||||||
|
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
|
||||||
|
const { rerender } = render(
|
||||||
|
<OverlayScrollbarsComponent
|
||||||
|
options={{ paddingAbsolute: true, overflow: { y: 'hidden' } }}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const opts = ref.current!.osInstance()!.options();
|
||||||
|
expect(opts.paddingAbsolute).toBe(true);
|
||||||
|
expect(opts.overflow.y).toBe('hidden');
|
||||||
|
|
||||||
|
rerender(<OverlayScrollbarsComponent options={{ overflow: { x: 'hidden' } }} ref={ref} />);
|
||||||
|
|
||||||
|
const newOpts = ref.current!.osInstance()!.options()!;
|
||||||
|
expect(newOpts.paddingAbsolute).toBe(false); //switches back to default because its not specified in the new options
|
||||||
|
expect(newOpts.overflow.y).toBe('scroll'); //switches back to default because its not specified in the new options
|
||||||
|
expect(newOpts.overflow.x).toBe('hidden');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user