diff --git a/package.json b/package.json index 2beb194..a4485c6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,9 @@ "peerDependencies": { "react": "*" }, + "dependencies": { + "classnames": "^2.5.1" + }, "devDependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.0.0", @@ -52,8 +55,5 @@ "type": "git", "url": "git+https://github.com/rain-cafe/react-utils.git" }, - "license": "MIT", - "dependencies": { - "classnames": "^2.5.1" - } + "license": "MIT" } diff --git a/src/hooks/__tests__/use-cached-state.spec.tsx b/src/hooks/__tests__/use-cached-state.spec.tsx index 17b82c1..5f369a8 100644 --- a/src/hooks/__tests__/use-cached-state.spec.tsx +++ b/src/hooks/__tests__/use-cached-state.spec.tsx @@ -1,23 +1,19 @@ import { render } from '@testing-library/react'; -import { useCachedState, useReadOnlyCachedState } from '../use-cached-state'; +import { useCachedState, useClassNames, useReadOnlyCachedState } from '../use-cached-state'; import Chance from 'chance'; const chance = new Chance(); -type ExampleComponentProps = { - value: string; -}; - describe('Cached State Hooks', () => { describe('hook(useCachedState)', () => { - function ExampleComponent({ - value - }: ExampleComponentProps) { + type ExampleComponentProps = { + value: string; + }; + + function ExampleComponent({ value }: ExampleComponentProps) { const [internalValue] = useCachedState(() => value, [value]); - return ( -
{internalValue}
- ); + return
{internalValue}
; } it('should cache the value', () => { @@ -30,14 +26,14 @@ describe('Cached State Hooks', () => { }); describe('hook(useReadOnlyCachedState)', () => { - function ExampleComponent({ - value - }: ExampleComponentProps) { + type ExampleComponentProps = { + value: string; + }; + + function ExampleComponent({ value }: ExampleComponentProps) { const internalValue = useReadOnlyCachedState(() => value, [value]); - return ( -
{internalValue}
- ); + return
{internalValue}
; } it('should cache the value', () => { @@ -48,4 +44,28 @@ describe('Cached State Hooks', () => { expect(component.getByText(expectedValue)).toBeTruthy(); }); }); + + describe('hook(useClassNames)', () => { + type ExampleComponentProps = { + sticky?: boolean; + }; + + function ExampleComponent({ sticky }: ExampleComponentProps) { + const classes = useClassNames(['my-class', sticky && 'sticky']); + + return
; + } + + it('should support base classes', () => { + const component = render(); + + expect(component.getByTestId('example').className).toEqual('my-class'); + }); + + it('should support conditional classes', () => { + const component = render(); + + expect(component.getByTestId('example').className).toEqual('my-class sticky'); + }); + }); }); diff --git a/src/hooks/use-cached-state.ts b/src/hooks/use-cached-state.ts index d16f499..4f0885d 100644 --- a/src/hooks/use-cached-state.ts +++ b/src/hooks/use-cached-state.ts @@ -1,3 +1,4 @@ +import classNames, { ArgumentArray } from 'classnames'; import { DependencyList, Dispatch, SetStateAction, useEffect, useState } from 'react'; export function useCachedState( @@ -16,3 +17,9 @@ export function useReadOnlyCachedState(supplier: () => T, deps: DependencyLis return value; } + +export function useClassNames(deps: ArgumentArray): string { + return useReadOnlyCachedState(() => { + return classNames(deps); + }, deps); +}