From 3ecb32b3499a5352a5a5e2ac3e31e79527e8295f Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Wed, 26 Oct 2022 18:59:55 +0200 Subject: [PATCH] implement OverlayScrollbarsComponent for react --- local/config/src/vitest.setup.js | 6 ++ .../src/OverlayScrollbarsComponent.tsx | 91 ++++++++++++++---- .../test/OverlayScrollbarsComponent.test.tsx | 92 ++++++++++++++++++- 3 files changed, 164 insertions(+), 25 deletions(-) diff --git a/local/config/src/vitest.setup.js b/local/config/src/vitest.setup.js index b210af5..c09a0d8 100644 --- a/local/config/src/vitest.setup.js +++ b/local/config/src/vitest.setup.js @@ -2,3 +2,9 @@ import matchers from '@testing-library/jest-dom/matchers'; import { expect } from 'vitest'; 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 {} diff --git a/packages/overlayscrollbars-react/src/OverlayScrollbarsComponent.tsx b/packages/overlayscrollbars-react/src/OverlayScrollbarsComponent.tsx index 1dc90cc..240d6b6 100644 --- a/packages/overlayscrollbars-react/src/OverlayScrollbarsComponent.tsx +++ b/packages/overlayscrollbars-react/src/OverlayScrollbarsComponent.tsx @@ -1,24 +1,75 @@ -export interface OverlayScrollbarsComponentProps { - element?: string; - options?: {}; - events?: {}; +import { forwardRef, useEffect, useRef, useImperativeHandle } from 'react'; +import { OverlayScrollbars } from 'overlayscrollbars'; +import type { PartialOptions, EventListeners } from 'overlayscrollbars'; +import type { ComponentPropsWithoutRef, ElementRef, ForwardedRef } from 'react'; + +export type OverlayScrollbarsComponentProps = + ComponentPropsWithoutRef & { + element?: T; + options?: PartialOptions; + events?: EventListeners; + }; + +export interface OverlayScrollbarsComponentRef { + osInstance(): OverlayScrollbars | null; + osTarget(): ElementRef | null; } -export const OverlayScrollbarsComponent = (props: OverlayScrollbarsComponentProps) => { - const { msg } = props; - return ( -
-
-

{msg}

- - Learn React - -
-
+const OverlayScrollbarsComponent = ( + props: OverlayScrollbarsComponentProps, + ref: ForwardedRef> +) => { + const { element = 'div', options, events, ...other } = props; + const Tag = element; + + const osTargetRef = useRef>(null); + const osInstanceRef = useRef(null); + + useEffect(() => { + const { current: target } = osTargetRef; + if (target) { + const instance = OverlayScrollbars(target as any, options || {}); + osInstanceRef.current = instance; + + 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 ; }; + +const OverlayScrollbarsComponentForwardedRef = forwardRef(OverlayScrollbarsComponent) as < + T extends keyof JSX.IntrinsicElements +>( + props: OverlayScrollbarsComponentProps & { + ref?: ForwardedRef>; + } +) => ReturnType; + +export { OverlayScrollbarsComponentForwardedRef as OverlayScrollbarsComponent }; diff --git a/packages/overlayscrollbars-react/test/OverlayScrollbarsComponent.test.tsx b/packages/overlayscrollbars-react/test/OverlayScrollbarsComponent.test.tsx index 6eef682..f943ac7 100644 --- a/packages/overlayscrollbars-react/test/OverlayScrollbarsComponent.test.tsx +++ b/packages/overlayscrollbars-react/test/OverlayScrollbarsComponent.test.tsx @@ -1,9 +1,91 @@ -import { test, expect } from 'vitest'; +import { describe, test, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; +import { OverlayScrollbars } from 'overlayscrollbars'; import { OverlayScrollbarsComponent } from '~/overlayscrollbars-react'; +import type { OverlayScrollbarsComponentRef } from '~/overlayscrollbars-react'; +import type { RefObject } from 'react'; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); +describe('OverlayScrollbarsComponent', () => { + describe('correct rendering', () => { + test('correct root element', () => { + const elementA = 'code'; + const elementB = 'span'; + const { container, rerender } = render(); + + expect(container).not.toBeEmptyDOMElement(); + expect(container.querySelector('div')).toBe(container.firstElementChild); // default is div + + rerender(); + expect(container.querySelector(elementA)).toBe(container.firstElementChild); + + rerender(); + expect(container.querySelector(elementB)).toBe(container.firstElementChild); + }); + + test('children', () => { + render( + + hello react + + ); + expect(screen.getByText(/hello/)).toBeInTheDocument(); + expect(screen.getByText(/react/)).toBeInTheDocument(); + }); + + test('className', () => { + const { container, rerender } = render( + + ); + + expect(container.firstElementChild).toHaveClass('overlay', 'scrollbars'); + + rerender(); + + expect(container.firstElementChild).toHaveClass('overlay', 'scrollbars', 'react'); + }); + + test('style', () => { + const { container, rerender } = render( + + ); + + expect(container.firstElementChild).toHaveStyle({ width: '22px' }); + + rerender(); + + expect(container.firstElementChild).toHaveStyle({ height: '33px' }); + }); + }); + + test('ref', () => { + const ref: RefObject = { current: null }; + const { container } = render(); + + 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 = { current: null }; + const { rerender } = render( + + ); + + const opts = ref.current!.osInstance()!.options(); + expect(opts.paddingAbsolute).toBe(true); + expect(opts.overflow.y).toBe('hidden'); + + rerender(); + + 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'); + }); });