diff --git a/.changeset/fresh-ties-create.md b/.changeset/fresh-ties-create.md new file mode 100644 index 0000000000..303b0d89be --- /dev/null +++ b/.changeset/fresh-ties-create.md @@ -0,0 +1,5 @@ +--- +'@skillsoft/gamut': patch +--- + +Updates Card to update border radius according to theme diff --git a/packages/gamut/src/Card/Card.test.tsx b/packages/gamut/src/Card/Card.test.tsx index de527188c8..9e1bdf1988 100644 --- a/packages/gamut/src/Card/Card.test.tsx +++ b/packages/gamut/src/Card/Card.test.tsx @@ -1,4 +1,10 @@ -import { theme } from '@skillsoft/gamut-styles'; +import { + coreTheme, + GamutProvider, + lxStudioTheme, + percipioTheme, + theme, +} from '@skillsoft/gamut-styles'; import { MockGamutProvider } from '@skillsoft/gamut-tests'; import { renderHook } from '@testing-library/react'; @@ -11,7 +17,7 @@ const renderElevation = (...args: Parameters) => describe('useCardElevation', () => { it('maps the theme elevation tokens onto the motion variants', () => { - const { animate, initial } = renderElevation('none'); + const { animate, initial } = renderElevation('none', 'md'); expect(initial).toMatchObject({ boxShadow: theme.elevation['rest-shadow'], @@ -24,7 +30,7 @@ describe('useCardElevation', () => { }); it('uses the mirrored hover tokens for patternRight shadows', () => { - const { animate } = renderElevation('patternRight'); + const { animate } = renderElevation('patternRight', 'md'); expect(animate).toMatchObject({ boxShadow: theme.elevation['hoverMirrored-shadow'], @@ -33,7 +39,7 @@ describe('useCardElevation', () => { }); it('applies outline shadows but shares the elevation transforms', () => { - const { animateOutline, initialOutline } = renderElevation('outline'); + const { animateOutline, initialOutline } = renderElevation('outline', 'md'); expect(initialOutline.boxShadow).not.toBe(theme.elevation['rest-shadow']); expect(initialOutline.transform).toBe(theme.elevation['rest-transform']); @@ -42,10 +48,34 @@ describe('useCardElevation', () => { }); it('passes borderRadius through to every variant', () => { - const variants = renderElevation('none', '8px'); + const variants = renderElevation('none', 'lg'); Object.values(variants).forEach((variant) => { - expect(variant.borderRadius).toBe('8px'); + expect(variant.borderRadius).toBe(theme.borderRadii.lg); + }); + }); + + it.each([ + ['coreTheme', coreTheme], + ['percipioTheme', percipioTheme], + ['lxStudioTheme', lxStudioTheme], + ])('resolves borderRadius from %s', (_name, activeTheme) => { + (['sm', 'md', 'lg'] as const).forEach((token) => { + const { result } = renderHook(() => useCardElevation('none', token), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current.initial.borderRadius).toBe( + activeTheme.borderRadii[token] + ); }); }); }); diff --git a/packages/gamut/src/Card/index.tsx b/packages/gamut/src/Card/index.tsx index ed9db004d9..1db707ad92 100644 --- a/packages/gamut/src/Card/index.tsx +++ b/packages/gamut/src/Card/index.tsx @@ -1,14 +1,11 @@ import { CheckerDense } from '@skillsoft/gamut-patterns'; import type { Colors } from '@skillsoft/gamut-styles'; -import { borderRadii } from '@skillsoft/gamut-styles'; import type * as React from 'react'; import { DynamicCardWrapper, MotionBox, StaticCardWrapper } from './elements'; import { patternFadeInOut, useCardElevation } from './styles'; import type { CardProps } from './types'; -type BorderRadiusToken = keyof typeof borderRadii; - export const Card: React.FC = ({ children, variant = 'default', @@ -20,10 +17,7 @@ export const Card: React.FC = ({ height = '100%', ...rest }) => { - const defaultBorderRadius: BorderRadiusToken = isInteractive ? 'md' : 'none'; - const trueBorderRadius = borderRadius ?? defaultBorderRadius; - const resolvedBorderRadius = - borderRadii[trueBorderRadius as BorderRadiusToken]; + const trueBorderRadius = borderRadius ?? (isInteractive ? 'md' : 'none'); const SelectedWrapper = variant === 'default' ? DynamicCardWrapper : StaticCardWrapper; @@ -31,7 +25,7 @@ export const Card: React.FC = ({ const hasPattern = shadow === 'patternLeft' || shadow === 'patternRight'; const isOutline = shadow === 'outline'; - const setHoverShadow = useCardElevation(shadow, resolvedBorderRadius); + const setHoverShadow = useCardElevation(shadow, trueBorderRadius); const initialVariant = isOutline ? 'initialOutline' : 'initial'; const animateVariant = isOutline ? 'animateOutline' : 'animate'; diff --git a/packages/gamut/src/Card/styles.tsx b/packages/gamut/src/Card/styles.tsx index c251251bbd..a7af55320b 100644 --- a/packages/gamut/src/Card/styles.tsx +++ b/packages/gamut/src/Card/styles.tsx @@ -4,6 +4,8 @@ import type { ElevationState } from '@skillsoft/gamut-styles'; import { theme, timingValues, variant } from '@skillsoft/gamut-styles'; import type { StyleProps } from '@skillsoft/variance'; +import type { CardWrapperProps } from './types'; + /** * The theme's elevation scale is flattened to dashcase tokens (`rest-shadow`, * `hoverMirrored-transform`, ...), so per-state groups don't exist on the @@ -91,34 +93,41 @@ export const patternFadeInOut = { export const useCardElevation = ( shadow: StyleProps['shadow'], - borderRadius?: string + borderRadius: CardWrapperProps['borderRadius'] ) => { - const { elevation } = useTheme(); + const { borderRadii, elevation } = useTheme(); const hoverState: ElevationState = shadow === 'patternRight' ? 'hoverMirrored' : 'hover'; + // framer-motion can only animate a concrete value, so responsive or + // function-valued radii are not resolved + const resolvedBorderRadius = + typeof borderRadius === 'string' + ? borderRadii[borderRadius as keyof Theme['borderRadii']] ?? borderRadius + : undefined; + return { initial: { ...getElevationStyles(elevation, 'rest'), - borderRadius, + borderRadius: resolvedBorderRadius, transition: REST_TRANSITION, }, animate: { ...getElevationStyles(elevation, hoverState), - borderRadius, + borderRadius: resolvedBorderRadius, transition: HOVER_TRANSITION, }, // Outline styles are specific to Codecademy and will not be included in a theme initialOutline: { ...getElevationStyles(elevation, 'rest'), boxShadow: `-${SHADOW_OFFSET_INITIAL}px ${SHADOW_OFFSET_INITIAL}px 0 0px ${theme.colors['background-current']}, -${SHADOW_OFFSET_INITIAL}px ${SHADOW_OFFSET_INITIAL}px 0 1px ${theme.colors['border-primary']}`, - borderRadius, + borderRadius: resolvedBorderRadius, transition: REST_TRANSITION, }, animateOutline: { ...getElevationStyles(elevation, 'hover'), boxShadow: `-${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 0px ${theme.colors['shadow-primary']}, -${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 1px ${theme.colors['shadow-primary']}`, - borderRadius, + borderRadius: resolvedBorderRadius, transition: HOVER_TRANSITION, }, };