diff --git a/.changeset/smart-stingrays-sit.md b/.changeset/smart-stingrays-sit.md new file mode 100644 index 0000000000..0dc5c01f86 --- /dev/null +++ b/.changeset/smart-stingrays-sit.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/skeleton-loader': patch +'@twilio-paste/core': patch +--- + +[Skeleton-loader] Enable Compoonent to respect element customizations set on the customization provider. Component now enables setting an element name on the underlying HTML element and checks the emotion theme object to determine whether it should merge in custom styles to the ones set by the component author. diff --git a/.storybook/preview.js b/.storybook/preview.js index d7c3fb4eb9..6885adffe3 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -54,7 +54,7 @@ export const decorators = [ default: case 'default': return ( - + @@ -67,14 +67,14 @@ export const decorators = [ - + - + @@ -88,24 +88,24 @@ export const decorators = [ <> - + - + - + - + diff --git a/packages/paste-core/components/skeleton-loader/__tests__/index.spec.tsx b/packages/paste-core/components/skeleton-loader/__tests__/index.spec.tsx index 9a8d07aa4f..a9c5a5e434 100644 --- a/packages/paste-core/components/skeleton-loader/__tests__/index.spec.tsx +++ b/packages/paste-core/components/skeleton-loader/__tests__/index.spec.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import {matchers} from 'jest-emotion'; -import {render} from '@testing-library/react'; +import {render, screen} from '@testing-library/react'; +import {CustomizationProvider} from '@twilio-paste/customization'; // @ts-ignore typescript doesn't like js imports import axe from '../../../../../.jest/axe-helper'; import {SkeletonLoader} from '../src'; @@ -10,15 +11,15 @@ expect.extend(matchers); describe('SkeletonLoader', () => { it('should render', () => { - const {getByTestId} = render(); - expect(getByTestId('default-skeleton')).toBeDefined(); - expect(getByTestId('default-skeleton')).toHaveStyleRule('background-color', 'colorBackgroundStrong'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('border-radius', 'borderRadius20'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('height', 'sizeIcon20'); + render(); + expect(screen.getByTestId('default-skeleton')).toBeDefined(); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('background-color', 'colorBackgroundStrong'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('border-radius', 'borderRadius20'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('height', 'sizeIcon20'); }); it('should render layout prop styles', (): void => { - const {getByTestId} = render( + render( { size="size30" /> ); - expect(getByTestId('default-skeleton')).toHaveStyleRule('display', 'block'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('width', 'size30'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('min-width', 'size30'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('max-width', 'size30'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('height', 'size30'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('min-height', 'size30'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('max-height', 'size30'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('display', 'block'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('width', 'size30'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('min-width', 'size30'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('max-width', 'size30'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('height', 'size30'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('min-height', 'size30'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('max-height', 'size30'); }); it('should render size prop styles', (): void => { - const {getByTestId} = render(); - expect(getByTestId('default-skeleton')).toHaveStyleRule('height', 'size30'); - expect(getByTestId('default-skeleton')).toHaveStyleRule('width', 'size30'); + render(); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('height', 'size30'); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('width', 'size30'); }); it('should render border-radius prop styles', (): void => { - const {getByTestId} = render(); - expect(getByTestId('default-skeleton')).toHaveStyleRule('border-radius', 'borderRadiusCircle'); + render(); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('border-radius', 'borderRadiusCircle'); }); it('should render top left border-radius prop styles', (): void => { - const {getByTestId} = render(); - expect(getByTestId('default-skeleton')).toHaveStyleRule('border-top-left-radius', 'borderRadiusCircle'); + render(); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('border-top-left-radius', 'borderRadiusCircle'); }); it('should render top right border-radius prop styles', (): void => { - const {getByTestId} = render(); - expect(getByTestId('default-skeleton')).toHaveStyleRule('border-top-right-radius', 'borderRadiusCircle'); + render(); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('border-top-right-radius', 'borderRadiusCircle'); }); it('should render bottom left border-radius prop styles', (): void => { - const {getByTestId} = render(); - expect(getByTestId('default-skeleton')).toHaveStyleRule('border-bottom-left-radius', 'borderRadiusCircle'); + render(); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('border-bottom-left-radius', 'borderRadiusCircle'); }); it('should render bottom right border-radius prop styles', (): void => { - const {getByTestId} = render(); - expect(getByTestId('default-skeleton')).toHaveStyleRule('border-bottom-right-radius', 'borderRadiusCircle'); + render(); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('border-bottom-right-radius', 'borderRadiusCircle'); + }); + + describe('Customization', () => { + it('should have default DOM attribute present', () => { + render(); + expect(screen.getByTestId('default-skeleton').getAttribute('data-paste-element')).toEqual('SKELETON_LOADER'); + }); + + it('should be able to create custom element dom attribute', () => { + render(); + expect(screen.getByTestId('default-skeleton').getAttribute('data-paste-element')).toEqual('CUSTOM'); + }); + + it('should apply custom style to default element name', () => { + render( + + + + ); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('margin', '1.75rem'); + }); + + it('should apply custom style to a custom element name', () => { + render( + + + + ); + expect(screen.getByTestId('default-skeleton')).toHaveStyleRule('padding', '0.5rem'); + }); }); -}); -describe('Accessibility', () => { - it('Should have no accessibility violations', async () => { - const {container} = render(); - const results = await axe(container); - expect(results).toHaveNoViolations(); + describe('Accessibility', () => { + it('Should have no accessibility violations', async () => { + const {container} = render(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); }); }); diff --git a/packages/paste-core/components/skeleton-loader/src/index.tsx b/packages/paste-core/components/skeleton-loader/src/index.tsx index 99e5f08d84..170059fe36 100644 --- a/packages/paste-core/components/skeleton-loader/src/index.tsx +++ b/packages/paste-core/components/skeleton-loader/src/index.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import {useSpring, animated} from '@twilio-paste/animation-library'; import {css, styled} from '@twilio-paste/styling-library'; import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import type {BoxElementProps} from '@twilio-paste/box'; import type {LayoutProps, BorderRadiusProps} from '@twilio-paste/style-props'; const AnimatedSkeleton = animated(Box); @@ -14,6 +15,7 @@ const StyledAnimatedSkeleton = styled(AnimatedSkeleton)(() => export interface SkeletonLoaderProps extends React.HTMLAttributes, + Pick, Omit, BorderRadiusProps {} @@ -25,6 +27,7 @@ const SkeletonLoader = React.forwardRef( borderRadius = 'borderRadius20', borderTopLeftRadius, borderTopRightRadius, + element = 'SKELETON_LOADER', display, height = 'sizeIcon20', maxHeight, @@ -59,6 +62,7 @@ const SkeletonLoader = React.forwardRef( borderTopLeftRadius={borderTopLeftRadius} borderTopRightRadius={borderTopRightRadius} display={display} + element={element} height={height} maxHeight={maxHeight} maxWidth={maxWidth} diff --git a/packages/paste-core/components/skeleton-loader/stories/index.stories.tsx b/packages/paste-core/components/skeleton-loader/stories/index.stories.tsx index 0b5b012e84..7ce703707d 100644 --- a/packages/paste-core/components/skeleton-loader/stories/index.stories.tsx +++ b/packages/paste-core/components/skeleton-loader/stories/index.stories.tsx @@ -8,6 +8,8 @@ import {Heading} from '@twilio-paste/heading'; import {Paragraph} from '@twilio-paste/paragraph'; import {Stack} from '@twilio-paste/stack'; import {Table, THead, TBody, Tr, Td, Th} from '@twilio-paste/table'; +import {CustomizationProvider} from '@twilio-paste/customization'; +import {useTheme} from '@twilio-paste/theme'; import {Text} from '@twilio-paste/text'; import {CalendarIcon} from '@twilio-paste/icons/esm/CalendarIcon'; import type {SkeletonLoaderProps} from '../src'; @@ -324,3 +326,28 @@ export const TableLoading: React.FC = () => { ); }; + +export const CustomizedSkeletonLoader: React.FC = () => { + const activeTheme = useTheme(); + return ( + + + + Customized Skeleton + + + + Custom Skeleton + + + + + ); +};