From 5d781e98088f014b07248f19101a352e8052e2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C4=8Curda?= Date: Mon, 23 Dec 2024 13:14:06 +0100 Subject: [PATCH] Feat(web-react): Additional parameter for styleProps and Box refactor --- packages/web-react/src/components/Box/Box.tsx | 3 +- .../Box/__tests__/useBoxStyleProps.test.ts | 72 ------------- .../src/components/Box/useBoxStyleProps.ts | 48 +-------- packages/web-react/src/constants/style.ts | 10 ++ .../src/hooks/__tests__/styleProps.test.ts | 42 ++++++++ .../hooks/__tests__/useStyleUtilities.test.ts | 101 ++++++++++++++++++ packages/web-react/src/hooks/styleProps.ts | 7 +- .../web-react/src/hooks/useStyleUtilities.ts | 10 +- 8 files changed, 171 insertions(+), 122 deletions(-) diff --git a/packages/web-react/src/components/Box/Box.tsx b/packages/web-react/src/components/Box/Box.tsx index 54a1b75fd1..e945d1a4ae 100644 --- a/packages/web-react/src/components/Box/Box.tsx +++ b/packages/web-react/src/components/Box/Box.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames'; import React, { ElementType } from 'react'; +import { PaddingStyleProps } from '../../constants'; import { useStyleProps } from '../../hooks'; import { SpiritBoxProps } from '../../types'; import { useBoxStyleProps } from './useBoxStyleProps'; @@ -15,7 +16,7 @@ const Box = (props: SpiritBoxProps) => { const { elementType: ElementTag = 'div', children, ...restProps } = propsWithDefaults; const { classProps, props: modifiedProps, styleProps: boxStyle } = useBoxStyleProps(restProps); - const { styleProps, props: otherProps } = useStyleProps(modifiedProps); + const { styleProps, props: otherProps } = useStyleProps(modifiedProps, PaddingStyleProps); const boxStyleProps = { style: { diff --git a/packages/web-react/src/components/Box/__tests__/useBoxStyleProps.test.ts b/packages/web-react/src/components/Box/__tests__/useBoxStyleProps.test.ts index 29e7805391..a285622989 100644 --- a/packages/web-react/src/components/Box/__tests__/useBoxStyleProps.test.ts +++ b/packages/web-react/src/components/Box/__tests__/useBoxStyleProps.test.ts @@ -19,78 +19,6 @@ describe('useBoxStyleProps', () => { expect(result.current.classProps).toBe('bg-secondary'); }); - it('should return padding classProps', () => { - const props: SpiritBoxProps = { - padding: 'space-400', - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('p-400'); - }); - - it('should return paddingX classProps', () => { - const props: SpiritBoxProps = { - paddingX: 'space-400', - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('px-400'); - }); - - it('should return paddingY classProps', () => { - const props: SpiritBoxProps = { - paddingY: 'space-400', - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('py-400'); - }); - - it('should return paddingTop classProps', () => { - const props: SpiritBoxProps = { - paddingTop: 'space-400', - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('pt-400'); - }); - - it('should return paddingBottom classProps', () => { - const props: SpiritBoxProps = { - paddingBottom: 'space-400', - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('pb-400'); - }); - - it('should return paddingLeft classProps', () => { - const props: SpiritBoxProps = { - paddingLeft: 'space-400', - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('pl-400'); - }); - - it('should return paddingRight classProps', () => { - const props: SpiritBoxProps = { - paddingRight: 'space-400', - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('pr-400'); - }); - - it('should return responsive padding classProps', () => { - const props: SpiritBoxProps = { - padding: { mobile: 'space-400', tablet: 'space-500', desktop: 'space-600' }, - }; - const { result } = renderHook(() => useBoxStyleProps(props)); - - expect(result.current.classProps).toBe('p-400 p-tablet-500 p-desktop-600'); - }); - it('should return border radius classProps', () => { const props: SpiritBoxProps = { borderRadius: '200', diff --git a/packages/web-react/src/components/Box/useBoxStyleProps.ts b/packages/web-react/src/components/Box/useBoxStyleProps.ts index 4aebde68d0..66f2a59ad7 100644 --- a/packages/web-react/src/components/Box/useBoxStyleProps.ts +++ b/packages/web-react/src/components/Box/useBoxStyleProps.ts @@ -1,7 +1,7 @@ import classNames from 'classnames'; import { CSSProperties, ElementType } from 'react'; import { BorderColors } from '../../constants'; -import { BreakpointToken, SpaceToken, SpiritBoxProps } from '../../types'; +import { SpiritBoxProps } from '../../types'; interface BoxCSSProperties extends CSSProperties { [key: string]: string | undefined | number; @@ -16,42 +16,10 @@ export interface UseBoxStyleProps { styleProps: BoxCSSProperties; } -export const generateResponsiveUtilityClasses = ( - prefix: string, - propValue: SpaceToken | Partial> | undefined, -): string[] => { - if (propValue && typeof propValue === 'object') { - return Object.entries(propValue).map(([breakpoint, value]) => { - const classValue = value?.replace('space-', ''); - - return breakpoint === 'mobile' ? `${prefix}-${classValue}` : `${prefix}-${breakpoint}-${classValue}`; - }); - } - - if (propValue && typeof propValue !== 'object') { - return [`${prefix}-${propValue.replace('space-', '')}`]; - } - - return []; -}; - export const useBoxStyleProps = ( props: Partial>, ): UseBoxStyleProps>> => { - const { - backgroundColor, - borderColor, - borderRadius, - borderWidth, - padding, - paddingBottom, - paddingLeft, - paddingRight, - paddingTop, - paddingX, - paddingY, - ...restProps - } = props || {}; + const { backgroundColor, borderColor, borderRadius, borderWidth, ...restProps } = props || {}; const boxStyle: BoxCSSProperties = {}; const boxBackgroundColor = backgroundColor ? `bg-${backgroundColor}` : ''; @@ -67,17 +35,7 @@ export const useBoxStyleProps = ( } } - const paddingClasses = [ - ...generateResponsiveUtilityClasses('p', padding), - ...generateResponsiveUtilityClasses('pb', paddingBottom), - ...generateResponsiveUtilityClasses('pl', paddingLeft), - ...generateResponsiveUtilityClasses('pr', paddingRight), - ...generateResponsiveUtilityClasses('pt', paddingTop), - ...generateResponsiveUtilityClasses('px', paddingX), - ...generateResponsiveUtilityClasses('py', paddingY), - ]; - - const boxClasses = classNames(boxBackgroundColor, boxBorderColor, boxBorderRadius, boxBorderWidth, ...paddingClasses); + const boxClasses = classNames(boxBackgroundColor, boxBorderColor, boxBorderRadius, boxBorderWidth); return { classProps: boxClasses, diff --git a/packages/web-react/src/constants/style.ts b/packages/web-react/src/constants/style.ts index 36888f0d48..9477e49e28 100644 --- a/packages/web-react/src/constants/style.ts +++ b/packages/web-react/src/constants/style.ts @@ -7,3 +7,13 @@ export const SpacingStyleProp = { marginX: 'mx', marginY: 'my', } as const; + +export const PaddingStyleProps = { + padding: 'p', + paddingBottom: 'pb', + paddingLeft: 'pl', + paddingRight: 'pr', + paddingTop: 'pt', + paddingX: 'px', + paddingY: 'py', +} as const; diff --git a/packages/web-react/src/hooks/__tests__/styleProps.test.ts b/packages/web-react/src/hooks/__tests__/styleProps.test.ts index cafb96fb93..bc5e28190e 100644 --- a/packages/web-react/src/hooks/__tests__/styleProps.test.ts +++ b/packages/web-react/src/hooks/__tests__/styleProps.test.ts @@ -131,4 +131,46 @@ describe('styleProps', () => { expect(result.current.styleProps).toEqual(expected); }); }); + + it('should process style props with additional utilities', () => { + const mockProps = { + margin: 'space-100', + marginX: 'space-200', + marginY: 'space-400', + padding: 'space-500', + paddingX: 'space-600', + paddingY: 'space-700', + }; + const additionalUtilities = { + padding: 'p', + paddingX: 'px', + paddingY: 'py', + }; + + const { result } = renderHook(() => useStyleProps(mockProps as StyleProps, additionalUtilities)); + + expect(result.current.styleProps).toEqual({ + className: 'm-100 mx-200 my-400 p-500 px-600 py-700', + style: undefined, + }); + }); + + it('should process style props with responsive additional utilities', () => { + const mockProps = { + margin: 'space-100', + marginX: 'space-200', + marginY: 'space-400', + padding: { mobile: 'space-500', tablet: 'space-600', desktop: 'space-700' }, + }; + const additionalUtilities = { + padding: 'p', + }; + + const { result } = renderHook(() => useStyleProps(mockProps as StyleProps, additionalUtilities)); + + expect(result.current.styleProps).toEqual({ + className: 'm-100 mx-200 my-400 p-500 p-tablet-600 p-desktop-700', + style: undefined, + }); + }); }); diff --git a/packages/web-react/src/hooks/__tests__/useStyleUtilities.test.ts b/packages/web-react/src/hooks/__tests__/useStyleUtilities.test.ts index a283b428b2..c6a769c81f 100644 --- a/packages/web-react/src/hooks/__tests__/useStyleUtilities.test.ts +++ b/packages/web-react/src/hooks/__tests__/useStyleUtilities.test.ts @@ -40,4 +40,105 @@ describe('useStyleUtilities hook', () => { expect(result.current.styleUtilities).toEqual(['m-100', 'mx-200', 'mx-tablet-auto', 'mx-desktop-300']); expect(result.current.props).toEqual({}); }); + + it('should process style utilities correctly with responsive values', () => { + const mockProps = { + margin: { mobile: 'space-100', tablet: 'space-200', desktop: 'space-300' }, + marginX: { mobile: 'space-200', tablet: 'space-200', desktop: 'space-300' }, + }; + const mockPrefix = 'test-prefix'; + + const { result } = renderHook(() => useStyleUtilities(mockProps as StyleProps, mockPrefix)); + + expect(result.current.styleUtilities).toEqual([ + 'test-prefix-m-100', + 'test-prefix-m-tablet-200', + 'test-prefix-m-desktop-300', + 'test-prefix-mx-200', + 'test-prefix-mx-tablet-200', + 'test-prefix-mx-desktop-300', + ]); + expect(result.current.props).toEqual({}); + }); + + it('should process style utilities correctly with responsive values without prefix', () => { + const mockProps = { + margin: { mobile: 'space-100', tablet: 'space-200', desktop: 'space-300' }, + marginX: { mobile: 'space-200', tablet: 'space-200', desktop: 'space-300' }, + }; + + const { result } = renderHook(() => useStyleUtilities(mockProps as StyleProps)); + + expect(result.current.styleUtilities).toEqual([ + 'm-100', + 'm-tablet-200', + 'm-desktop-300', + 'mx-200', + 'mx-tablet-200', + 'mx-desktop-300', + ]); + expect(result.current.props).toEqual({}); + }); + + it('should process style utilities with additional spacing props', () => { + const mockProps = { + margin: 'space-100', + marginX: 'space-200', + marginY: 'space-400', + padding: 'space-500', + paddingX: 'space-600', + paddingY: 'space-700', + }; + const additionalSpacingProps = { + padding: 'p', + paddingX: 'px', + paddingY: 'py', + }; + + const { result } = renderHook(() => + useStyleUtilities(mockProps as StyleProps, 'test-prefix', additionalSpacingProps), + ); + + expect(result.current.styleUtilities).toEqual([ + 'test-prefix-m-100', + 'test-prefix-mx-200', + 'test-prefix-my-400', + 'test-prefix-p-500', + 'test-prefix-px-600', + 'test-prefix-py-700', + ]); + }); + + it('should process style utilities with responsive additional spacing props', () => { + const mockProps = { + margin: 'space-100', + marginX: 'space-200', + marginY: 'space-400', + padding: { mobile: 'space-500', tablet: 'space-600', desktop: 'space-700' }, + paddingX: { mobile: 'space-600', tablet: 'space-700', desktop: 'space-800' }, + paddingY: { mobile: 'space-700', tablet: 'space-800', desktop: 'space-900' }, + }; + const additionalSpacingProps = { + padding: 'p', + paddingX: 'px', + paddingY: 'py', + }; + + const { result } = renderHook(() => useStyleUtilities(mockProps as StyleProps, '', additionalSpacingProps)); + + expect(result.current.styleUtilities).toEqual([ + 'm-100', + 'mx-200', + 'my-400', + 'p-500', + 'p-tablet-600', + 'p-desktop-700', + 'px-600', + 'px-tablet-700', + 'px-desktop-800', + 'py-700', + 'py-tablet-800', + 'py-desktop-900', + ]); + }); }); diff --git a/packages/web-react/src/hooks/styleProps.ts b/packages/web-react/src/hooks/styleProps.ts index 5c533973f9..791f78f6f0 100644 --- a/packages/web-react/src/hooks/styleProps.ts +++ b/packages/web-react/src/hooks/styleProps.ts @@ -10,10 +10,13 @@ export type StylePropsResult = { props: HTMLAttributes; }; -export function useStyleProps(props: T): StylePropsResult { +export function useStyleProps( + props: T, + additionalUtilities?: Record, +): StylePropsResult { const classNamePrefix = useContext(ClassNamePrefixContext); const { UNSAFE_className, UNSAFE_style, ...otherProps } = props; - const { styleUtilities, props: modifiedProps } = useStyleUtilities(otherProps, classNamePrefix); + const { styleUtilities, props: modifiedProps } = useStyleUtilities(otherProps, classNamePrefix, additionalUtilities); const style: CSSProperties = { ...UNSAFE_style }; diff --git a/packages/web-react/src/hooks/useStyleUtilities.ts b/packages/web-react/src/hooks/useStyleUtilities.ts index c369e8b34d..cd4e9eabed 100644 --- a/packages/web-react/src/hooks/useStyleUtilities.ts +++ b/packages/web-react/src/hooks/useStyleUtilities.ts @@ -1,4 +1,4 @@ -import { SpacingStyleProp } from '../constants'; +import { SpacingStyleProp as DefaultSpacingStyleProp } from '../constants'; import { BREAKPOINT_MOBILE, BreakpointToken, @@ -37,7 +37,13 @@ const processBreakpointProperties = ( return accumulatedBreakpointUtilities; }, accumulatedUtilities); -export function useStyleUtilities(props: StyleProps, prefix: string | null | undefined = ''): StyleUtilitiesResult { +export function useStyleUtilities( + props: StyleProps, + prefix: string | null | undefined = '', + additionalSpacingProps: Record = {}, +): StyleUtilitiesResult { + const SpacingStyleProp = { ...DefaultSpacingStyleProp, ...additionalSpacingProps }; + const propEntries = Object.entries(props); const styleUtilities = propEntries.reduce((accumulatedUtilities: string[], [key, propValue]) => { if (Object.keys(SpacingStyleProp).includes(key)) {