diff --git a/playroom/snippets.tsx b/playroom/snippets.tsx index 7061ba1bc..214eed405 100644 --- a/playroom/snippets.tsx +++ b/playroom/snippets.tsx @@ -2900,8 +2900,8 @@ const alertSnippets = [ title: fruit, description: "Description", asset: ( - - + + ), }))} @@ -3326,8 +3326,8 @@ const gridSnippets = [ - + + } title="Title 1" @@ -3337,8 +3337,8 @@ const gridSnippets = [ - + + } title="Title 2" @@ -3348,8 +3348,8 @@ const gridSnippets = [ - + + } title="Title 3" @@ -3359,8 +3359,8 @@ const gridSnippets = [ - + + } title="Title 4" @@ -3640,12 +3640,90 @@ const timerSnippets: Array = [ }, ]; +const ratingSnippets: Array = [ + { + group: 'Rating', + name: 'InfoRating', + code: ` + + `, + }, + { + group: 'Rating', + name: 'Rating quantitative', + code: ` + + `, + }, + { + group: 'Rating', + name: 'Rating qualitative', + code: ` + + `, + }, +]; + export default [ ...buttonSnippets, ...formSnippets, ...feedbackSnippets, ...skeletonSnippets, ...timerSnippets, + ...ratingSnippets, {group: 'Feedbacks', name: 'Snackbar', code: ''}, ...layoutSnippets, { diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-custom-icons-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-custom-icons-1-snap.png new file mode 100644 index 000000000..7bf5174ef Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-custom-icons-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-inverse-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-inverse-1-snap.png new file mode 100644 index 000000000..d7806a29c Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-inverse-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-16-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-16-1-snap.png new file mode 100644 index 000000000..ac28fb674 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-16-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-24-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-24-1-snap.png new file mode 100644 index 000000000..2f2f7a5e3 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-24-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-48-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-48-1-snap.png new file mode 100644 index 000000000..bc6498cf3 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-size-48-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-0-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-0-1-snap.png new file mode 100644 index 000000000..ac28fb674 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-0-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-1-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-1-1-snap.png new file mode 100644 index 000000000..600a97884 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-1-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-2-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-2-1-snap.png new file mode 100644 index 000000000..d812baca9 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-2-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-3-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-3-1-snap.png new file mode 100644 index 000000000..d6e26dce9 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-3-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-1-snap.png new file mode 100644 index 000000000..2449ad5b6 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-5-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-5-1-snap.png new file mode 100644 index 000000000..2449ad5b6 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-5-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-51-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-51-1-snap.png new file mode 100644 index 000000000..32695fbf7 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-4-51-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-5-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-5-1-snap.png new file mode 100644 index 000000000..32695fbf7 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-value-5-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-0-74-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-0-74-1-snap.png new file mode 100644 index 000000000..237a38e93 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-0-74-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-0-75-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-0-75-1-snap.png new file mode 100644 index 000000000..600a97884 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-0-75-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-1-24-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-1-24-1-snap.png new file mode 100644 index 000000000..600a97884 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-1-24-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-4-25-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-4-25-1-snap.png new file mode 100644 index 000000000..bb651013a Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-4-25-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-4-5-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-4-5-1-snap.png new file mode 100644 index 000000000..bb651013a Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-info-rating-with-half-value-and-value-4-5-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-inverse-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-inverse-1-snap.png new file mode 100644 index 000000000..2f90e3c58 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-inverse-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-1-snap.png new file mode 100644 index 000000000..80e54e624 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-2-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-2-snap.png new file mode 100644 index 000000000..5c6741efc Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-2-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-3-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-3-snap.png new file mode 100644 index 000000000..d62a2bad0 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-3-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-4-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-4-snap.png new file mode 100644 index 000000000..4ce878fd7 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-4-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-5-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-5-snap.png new file mode 100644 index 000000000..c40ac5827 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-5-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-6-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-6-snap.png new file mode 100644 index 000000000..59fc23954 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-6-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-inverse-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-inverse-1-snap.png new file mode 100644 index 000000000..e0f5717cf Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-inverse-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-inverse-2-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-inverse-2-snap.png new file mode 100644 index 000000000..94a47c345 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-inverse-2-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-1-snap.png new file mode 100644 index 000000000..40de86daf Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-2-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-2-snap.png new file mode 100644 index 000000000..fa6d3c0c0 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-2-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-3-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-3-snap.png new file mode 100644 index 000000000..0aa64324b Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-3-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-4-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-4-snap.png new file mode 100644 index 000000000..803507fac Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-4-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-5-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-5-snap.png new file mode 100644 index 000000000..0c80624bd Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-qualitative-with-custom-icons-5-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-1-snap.png new file mode 100644 index 000000000..5ccceadf0 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-2-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-2-snap.png new file mode 100644 index 000000000..9ba3eaffb Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-2-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-with-custom-icons-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-with-custom-icons-1-snap.png new file mode 100644 index 000000000..8477eaf63 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-with-custom-icons-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-with-custom-icons-2-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-with-custom-icons-2-snap.png new file mode 100644 index 000000000..748cf099f Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-quantitative-with-custom-icons-2-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-24-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-24-1-snap.png new file mode 100644 index 000000000..403632147 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-24-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-48-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-48-1-snap.png new file mode 100644 index 000000000..0a6147fa8 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-48-1-snap.png differ diff --git a/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-8-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-8-1-snap.png new file mode 100644 index 000000000..0918b3c73 Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/rating-screenshot-test-tsx-rating-size-8-1-snap.png differ diff --git a/src/__screenshot_tests__/rating-screenshot-test.tsx b/src/__screenshot_tests__/rating-screenshot-test.tsx new file mode 100644 index 000000000..853c724ad --- /dev/null +++ b/src/__screenshot_tests__/rating-screenshot-test.tsx @@ -0,0 +1,179 @@ +import {openStoryPage, screen} from '../test-utils'; + +test.each([0, 1, 2, 3, 4, 5, 4.5, 4.51])('InfoRating - value = %s', async (value) => { + await openStoryPage({ + id: 'components-rating--info-rating-story', + device: 'MOBILE_IOS', + args: {value}, + }); + + const rating = await screen.findByTestId('info-rating'); + + expect(await rating.screenshot()).toMatchImageSnapshot(); +}); + +test.each([4.5, 0.74, 0.75, 1.24, 4.25])('InfoRating - with half value and value = %s', async (value) => { + await openStoryPage({ + id: 'components-rating--info-rating-story', + device: 'MOBILE_IOS', + args: {value, withHalfValue: true}, + }); + + const rating = await screen.findByTestId('info-rating'); + + expect(await rating.screenshot()).toMatchImageSnapshot(); +}); + +test.each([16, 24, 48])('InfoRating - size = %s', async (size) => { + await openStoryPage({ + id: 'components-rating--info-rating-story', + device: 'MOBILE_IOS', + args: {size}, + }); + + const rating = await screen.findByTestId('info-rating'); + + expect(await rating.screenshot()).toMatchImageSnapshot(); +}); + +test('InfoRating - inverse', async () => { + await openStoryPage({ + id: 'components-rating--info-rating-story', + device: 'MOBILE_IOS', + args: {inverse: true, value: 3}, + }); + + const rating = await screen.findByTestId('info-rating'); + + expect(await rating.screenshot()).toMatchImageSnapshot(); +}); + +test('InfoRating - custom icons', async () => { + await openStoryPage({ + id: 'components-rating--info-rating-story', + device: 'MOBILE_IOS', + args: {value: 3.5, customIcons: true, withHalfValue: true}, + }); + + const rating = await screen.findByTestId('info-rating'); + + expect(await rating.screenshot()).toMatchImageSnapshot(); +}); + +test('Rating - quantitative', async () => { + const page = await openStoryPage({ + id: 'components-rating--rating-story', + device: 'MOBILE_IOS', + args: {type: 'quantitative'}, + }); + + const ratingWrapper = await screen.findByTestId('rating-wrapper'); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); + + const thirdIcon = await screen.findByRole('radio', {name: '3 de 5'}); + await page.click(thirdIcon); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); +}); + +test('Rating - quantitative with custom icons', async () => { + const page = await openStoryPage({ + id: 'components-rating--rating-story', + device: 'MOBILE_IOS', + args: {type: 'quantitative', customIcons: true}, + }); + + const ratingWrapper = await screen.findByTestId('rating-wrapper'); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); + + const thirdIcon = await screen.findByRole('radio', {name: '3 de 5'}); + await page.click(thirdIcon); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); +}); + +test('Rating - qualitative', async () => { + const page = await openStoryPage({ + id: 'components-rating--rating-story', + device: 'MOBILE_IOS', + args: {type: 'qualitative'}, + }); + + const ratingWrapper = await screen.findByTestId('rating-wrapper'); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); + + const labels = ['muy malo', 'malo', 'regular', 'bueno', 'muy bueno']; + + for (const label of labels) { + const currentIcon = await screen.findByRole('radio', {name: label}); + await page.click(currentIcon); + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); + } +}); + +test('Rating - qualitative with custom icons', async () => { + const page = await openStoryPage({ + id: 'components-rating--rating-story', + device: 'MOBILE_IOS', + args: {type: 'qualitative', customIcons: true}, + }); + + const ratingWrapper = await screen.findByTestId('rating-wrapper'); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); + + const labels = ['no battery', 'low battery', 'mid battery', 'full battery']; + + for (const label of labels) { + const currentIcon = await screen.findByRole('radio', {name: label}); + await page.click(currentIcon); + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); + } +}); + +test('Rating - qualitative inverse', async () => { + const page = await openStoryPage({ + id: 'components-rating--rating-story', + device: 'MOBILE_IOS', + args: {type: 'qualitative', inverse: true}, + }); + + const ratingWrapper = await screen.findByTestId('rating-wrapper'); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); + + const thirdIcon = await screen.findByRole('radio', {name: 'regular'}); + await page.click(thirdIcon); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); +}); + +test.each([8, 24, 48])('Rating - size = %s', async (size) => { + await openStoryPage({ + id: 'components-rating--rating-story', + device: 'MOBILE_IOS', + args: {size}, + }); + + const ratingWrapper = await screen.findByTestId('rating-wrapper'); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); +}); + +test('Rating - inverse', async () => { + const page = await openStoryPage({ + id: 'components-rating--rating-story', + device: 'MOBILE_IOS', + args: {inverse: true}, + }); + + const ratingWrapper = await screen.findByTestId('rating-wrapper'); + + const thirdIcon = await screen.findByRole('radio', {name: '3 de 5'}); + await page.click(thirdIcon); + + expect(await ratingWrapper.screenshot()).toMatchImageSnapshot(); +}); diff --git a/src/__stories__/rating-story.tsx b/src/__stories__/rating-story.tsx new file mode 100644 index 000000000..b63a2fad9 --- /dev/null +++ b/src/__stories__/rating-story.tsx @@ -0,0 +1,183 @@ +import * as React from 'react'; +import { + Box, + IconBatteryChargingFilled, + IconBatteryChargingRegular, + IconBatteryFullFilled, + IconBatteryFullRegular, + IconBatteryLowFilled, + IconBatteryLowRegular, + IconBatteryMediumFilled, + IconBatteryMediumRegular, + IconCheckedFilled, + IconCheckedRegular, + InfoRating, + Rating, + ResponsiveLayout, +} from '..'; +import {vars} from '../skins/skin-contract.css'; + +export default { + title: 'Components/Rating', + parameters: {fullScreen: true}, +}; + +type RatingArgs = { + inverse: boolean; + count: number; + size: number; + type: 'quantitative' | 'qualitative'; + disabled: boolean; + customIcons: boolean; +}; + +export const RatingStory: StoryComponent = ({ + inverse, + count, + size, + type, + disabled, + customIcons, +}) => { + return ( + + +
+ +
+
+
+ ); +}; + +RatingStory.storyName = 'Rating'; + +RatingStory.args = { + type: 'quantitative', + count: 5, + size: 32, + disabled: false, + inverse: false, + customIcons: false, +}; + +RatingStory.argTypes = { + type: { + options: ['quantitative', 'qualitative'], + control: {type: 'select'}, + }, + count: { + control: {type: 'range', min: 1, max: 5}, + if: {arg: 'type', eq: 'quantitative'}, + }, + size: { + control: {type: 'range', min: 16, max: 64, step: 4}, + }, +}; + +type InfoRatingArgs = { + inverse: boolean; + count: number; + value: number; + size: number; + withHalfValue: boolean; + customIcons: boolean; +}; + +export const InfoRatingStory: StoryComponent = ({ + inverse, + count, + value, + size, + withHalfValue, + customIcons, +}) => { + return ( + + + + + + ); +}; + +InfoRatingStory.storyName = 'InfoRating'; + +InfoRatingStory.args = { + inverse: false, + withHalfValue: false, + count: 5, + size: 16, + value: 0, + customIcons: false, +}; + +InfoRatingStory.argTypes = { + count: { + control: {type: 'range', min: 1, max: 5}, + }, + size: { + control: {type: 'range', min: 16, max: 64, step: 4}, + }, +}; diff --git a/src/__tests__/rating-test.tsx b/src/__tests__/rating-test.tsx new file mode 100644 index 000000000..85a228184 --- /dev/null +++ b/src/__tests__/rating-test.tsx @@ -0,0 +1,217 @@ +import * as React from 'react'; +import {render, screen, within} from '@testing-library/react'; +import {Rating, InfoRating} from '../rating'; +import userEvent from '@testing-library/user-event'; +import ThemeContextProvider from '../theme-context-provider'; +import {makeTheme} from './test-utils'; +import IconStarRegular from '../generated/mistica-icons/icon-star-regular'; +import IconStarFilled from '../generated/mistica-icons/icon-star-filled'; +import IconLightningRegular from '../generated/mistica-icons/icon-lightning-regular'; +import IconLightningFilled from '../generated/mistica-icons/icon-lightning-filled'; +import {vars} from '../skins/skin-contract.css'; +import {Text2} from '../text'; + +test('InfoRating is accessible', async () => { + render( + + + + ); + + await screen.findByRole('img', {name: '3 de 5'}); +}); + +test('InfoRating is accessible with custom label', async () => { + render( + + + + ); + + await screen.findByRole('img', {name: 'info rating'}); +}); + +test('InfoRating is accessible with aria-labelledby', async () => { + render( + + + This is a label + + + + ); + + await screen.findByRole('img', {name: 'This is a label'}); +}); + +test('Rating quantitative is accessible', async () => { + render( + + + + ); + + const rating = await screen.findByRole('radiogroup', {name: 'rating'}); + const icons = within(rating).getAllByRole('radio'); + + // 5 icons by default + expect(icons).toHaveLength(5); + + // Initially no value is defined + icons.forEach((icon) => expect(icon).not.toBeChecked()); + + const firstIcon = await within(rating).findByRole('radio', {name: '1 de 5'}); + await userEvent.click(firstIcon); + expect(icons[0]).toBeChecked(); + + const thirdIcon = await within(rating).findByRole('radio', {name: '3 de 5'}); + await userEvent.click(thirdIcon); + expect(icons[0]).not.toBeChecked(); + expect(icons[2]).toBeChecked(); +}); + +test('Rating qualitative is accessible', async () => { + render( + + + + ); + + const rating = await screen.findByRole('radiogroup', {name: 'rating'}); + const icons = within(rating).getAllByRole('radio'); + + // 5 icons by default + expect(icons).toHaveLength(5); + + // Initially no value is defined + icons.forEach((icon) => expect(icon).not.toBeChecked()); + + const firstIcon = await within(rating).findByRole('radio', {name: 'muy malo'}); + await userEvent.click(firstIcon); + expect(icons[0]).toBeChecked(); + + const thirdIcon = await within(rating).findByRole('radio', {name: 'regular'}); + await userEvent.click(thirdIcon); + expect(icons[0]).not.toBeChecked(); + expect(icons[2]).toBeChecked(); +}); + +test('Rating is accessible with aria-labelledby', async () => { + render( + + + This is a label + + + + ); + + await screen.findByRole('radiogroup', {name: 'This is a label'}); +}); + +test('Rating with uncontrolled value', async () => { + render( + + + + ); + + const secondIcon = await screen.findByRole('radio', {name: '2 de 5'}); + expect(secondIcon).toBeChecked(); + + const thirdIcon = await screen.findByRole('radio', {name: '3 de 5'}); + await userEvent.click(thirdIcon); + expect(secondIcon).not.toBeChecked(); + expect(thirdIcon).toBeChecked(); +}); + +test('Rating with controlled value', async () => { + const ControlledRating = () => { + const [value, setValue] = React.useState(2); + return ; + }; + + render( + + + + ); + + const secondIcon = await screen.findByRole('radio', {name: '2 de 5'}); + expect(secondIcon).toBeChecked(); + + const thirdIcon = await screen.findByRole('radio', {name: '3 de 5'}); + await userEvent.click(thirdIcon); + expect(secondIcon).not.toBeChecked(); + expect(thirdIcon).toBeChecked(); +}); + +test('Rating quantitative with 3 icons and custom labels', async () => { + render( + + + + ); + + const rating = await screen.findByRole('radiogroup', {name: 'rating'}); + const icons = within(rating).getAllByRole('radio'); + + expect(icons).toHaveLength(3); + + // Initially no value is defined + icons.forEach((icon) => expect(icon).not.toBeChecked()); + + const secondIcon = await screen.findByRole('radio', {name: 'second'}); + await userEvent.click(secondIcon); + + expect(icons[1]).toBeChecked(); + + const thirdIcon = await screen.findByRole('radio', {name: 'third'}); + await userEvent.click(thirdIcon); + + expect(icons[1]).not.toBeChecked(); + expect(icons[2]).toBeChecked(); +}); + +test('Rating qualitative with 2 icons and custom labels', async () => { + render( + + + + ); + + const rating = await screen.findByRole('radiogroup', {name: 'rating'}); + const icons = within(rating).getAllByRole('radio'); + + expect(icons).toHaveLength(2); + + // Initially no value is defined + icons.forEach((icon) => expect(icon).not.toBeChecked()); + + const secondIcon = await screen.findByRole('radio', {name: 'second'}); + await userEvent.click(secondIcon); + + expect(icons[1]).toBeChecked(); + + const firstIcon = await screen.findByRole('radio', {name: 'first'}); + await userEvent.click(firstIcon); + + expect(icons[1]).not.toBeChecked(); + expect(icons[0]).toBeChecked(); +}); diff --git a/src/index.tsx b/src/index.tsx index 0198f7bd3..598a3c423 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -63,6 +63,7 @@ export {default as HorizontalScroll} from './horizontal-scroll'; export {default as HighlightedCard} from './highlighted-card'; export {default as Stepper} from './stepper'; export {ProgressBar, ProgressBarStepped} from './progress-bar'; +export {Rating, InfoRating} from './rating'; export {VerticalMosaic, HorizontalMosaic} from './mosaic'; export {Timer, TextTimer} from './timer'; export { diff --git a/src/inline.tsx b/src/inline.tsx index 02a398f01..a35bf3669 100644 --- a/src/inline.tsx +++ b/src/inline.tsx @@ -43,6 +43,7 @@ type Props = { children: React.ReactNode; className?: string; role?: string; + 'aria-label'?: string; 'aria-labelledby'?: string; fullWidth?: boolean; dataAttributes?: DataAttributes; @@ -55,6 +56,7 @@ const Inline = ({ children, role, alignItems = 'stretch', + 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, fullWidth, wrap, @@ -74,7 +76,8 @@ const Inline = ({ )} style={applyCssVars(calcInlineVars(space))} role={role} - aria-labelledby={ariaLabelledBy} + aria-label={ariaLabel} + aria-labelledby={ariaLabel ? undefined : ariaLabelledBy} {...getPrefixedDataAttributes(dataAttributes, 'Inline')} > {React.Children.map(children, (child) => diff --git a/src/radio-button.tsx b/src/radio-button.tsx index 6cee63826..d992c9723 100644 --- a/src/radio-button.tsx +++ b/src/radio-button.tsx @@ -153,6 +153,7 @@ const RadioButton = ({ type RadioGroupProps = { name: string; disabled?: boolean; + 'aria-label'?: string; 'aria-labelledby'?: string; children: React.ReactNode; value?: string; @@ -253,7 +254,8 @@ export const RadioGroup = (props: RadioGroupProps): JSX.Element => {
JSX.Element; + InactiveIcon: (props: IconProps) => JSX.Element; + color: string; +}; + +const DEFAULT_RATING_SIZE = 32; +const DEFAULT_INFO_RATING_SIZE = 16; +const DEFAULT_RATING_ICON_COUNT = 5; + +const DEFAULT_RATING_ICON: RatingIconProps = { + ActiveIcon: IconStarFilled, + InactiveIcon: IconStarRegular, + color: vars.colors.controlActivated, +}; + +const DEFAULT_INFO_RATING_ICON: RatingIconProps = { + ActiveIcon: IconStarFilled, + InactiveIcon: IconStarRegular, + color: vars.colors.warning, +}; + +const DEFAULT_QUALITATIVE_ICONS: Array = [ + { + ActiveIcon: IconFaceSadFilled, + InactiveIcon: IconFaceSadRegular, + color: vars.colors.errorHigh, + }, + { + ActiveIcon: IconFaceSlightlySadFilled, + InactiveIcon: IconFaceSlightlySadRegular, + color: vars.colors.error, + }, + { + ActiveIcon: IconFaceNeutralFilled, + InactiveIcon: IconFaceNeutralRegular, + color: vars.colors.warning, + }, + { + ActiveIcon: IconFaceHappyFilled, + InactiveIcon: IconFaceHappyRegular, + color: vars.colors.success, + }, + { + ActiveIcon: IconFaceSuperHappyFilled, + InactiveIcon: IconFaceSuperHappyRegular, + color: vars.colors.successHigh, + }, +]; + +interface BaseRatingProps { + size?: number; + dataAttributes?: DataAttributes; + valueLabels?: Array; + 'aria-label'?: string; + 'aria-labelledby'?: string; +} + +interface QuantitativeRatingProps extends BaseRatingProps { + type?: 'quantitative'; + icon?: RatingIconProps; + count?: number; +} + +interface QualitativeRatingProps extends BaseRatingProps { + type: 'qualitative'; + icons?: Array; +} + +type RatingProps = ExclusifyUnion & { + value?: number; + defaultValue?: number; + onChangeValue?: (value: number) => void; + disabled?: boolean; +}; + +type InfoRatingProps = Omit & { + value?: number; + withHalfValue?: boolean; +}; + +type InternalRatingProps = ExclusifyUnion & { + role: 'radiogroup' | 'img'; +}; + +const useRatingState = ({ + value, + defaultValue, + iconsCount, + onChangeValue, +}: { + value?: number; + defaultValue?: number; + iconsCount: number; + onChangeValue?: (value: number) => void; +}): [number, (value: number) => void] => { + const isControlledByParent = value !== undefined; + + const getValueInRange = React.useCallback( + (value?: number) => { + return value === undefined ? 0 : Math.max(0, Math.min(iconsCount, value)); + }, + [iconsCount] + ); + + const [currentValue, setCurrentValue] = React.useState(getValueInRange(defaultValue)); + + const updateValue = (newValue: number) => { + if (!isControlledByParent) { + setCurrentValue(newValue); + } + onChangeValue?.(newValue); + }; + + return [isControlledByParent ? getValueInRange(value) : currentValue, updateValue]; +}; + +const InternalRating = ({ + icons = DEFAULT_QUALITATIVE_ICONS, + count = DEFAULT_RATING_ICON_COUNT, + icon = DEFAULT_RATING_ICON, + size = DEFAULT_RATING_SIZE, + type = 'quantitative', + dataAttributes, + onChangeValue, + defaultValue, + value, + disabled, + role, + valueLabels, + withHalfValue, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, +}: InternalRatingProps) => { + const {texts, t} = useTheme(); + + const defaultQualitativeLabels = [ + texts.ratingVeryBadLabel || t(tokens.ratingVeryBadLabel), + texts.ratingBadLabel || t(tokens.ratingBadLabel), + texts.ratingRegularLabel || t(tokens.ratingRegularLabel), + texts.ratingGoodLabel || t(tokens.ratingGoodLabel), + texts.ratingVeryGoodLabel || t(tokens.ratingVeryGoodLabel), + ]; + + const defaultQuantitativeLabels = Array.from({length: count}, (_, index) => + (texts.ratingQuantitativeLabel || t(tokens.ratingQuantitativeLabel)) + .replace('1$s', String(index + 1)) + .replace('2$s', String(count)) + ); + + const iconList = type === 'qualitative' ? icons : Array.from({length: count}, () => icon); + const labelList = + valueLabels ?? + (type === 'qualitative' && isEqual(iconList, DEFAULT_QUALITATIVE_ICONS) + ? defaultQualitativeLabels + : defaultQuantitativeLabels); + + const isInteractive = role === 'radiogroup'; + + const iconSpacing = isInteractive ? 16 : size <= 16 ? 2 : size <= 24 ? 4 : 8; + const variant = useThemeVariant(); + const [hoveredIndex, setHoveredIndex] = React.useState(undefined); + const isTouchable = isTouchableDevice(); + + const [currentValue, setCurrentValue] = useRatingState({ + value, + defaultValue, + iconsCount: iconList.length, + onChangeValue, + }); + + const getIconType = (index: number) => { + if (hoveredIndex !== undefined && !disabled) { + return (type === 'qualitative' && index === hoveredIndex) || + (type === 'quantitative' && index <= hoveredIndex) + ? 'active' + : 'inactive'; + } + if (type === 'qualitative') { + return index === currentValue ? 'active' : 'inactive'; + } + + if (isInteractive) { + return index <= currentValue ? 'active' : 'inactive'; + } + + // Round the value to the closest integer (.5 is rounded down) + if (!withHalfValue) { + return index - 0.5 < currentValue ? 'active' : 'inactive'; + } + + // Fractional part of the value is in range [0.25, 0.75) + if (index - 0.75 <= currentValue && currentValue < index - 0.25) { + return 'half'; + } + + if (index - 0.25 <= currentValue) { + return 'active'; + } + + return 'inactive'; + }; + + const getIconElement = (icon: RatingIconProps, index: number) => { + const activeColor = variant === 'inverse' ? vars.colors.inverse : iconList[index].color; + const inactiveColor = + variant === 'inverse' + ? vars.colors.inverse + : isInteractive + ? vars.colors.control + : iconList[0].color; + + switch (getIconType(index + 1)) { + case 'active': + return ; + + case 'inactive': + return ; + + case 'half': + default: + return ( +
+
+ +
+
+ +
+
+ ); + } + }; + + const renderIcon = (icon: RatingIconProps, index: number) => { + const iconElement = getIconElement(icon, index); + + return !isInteractive ? ( + iconElement + ) : ( + ( +
{ + if (!isTouchable) { + setHoveredIndex(index + 1); + } + }} + onMouseLeave={() => { + if (!isTouchable) { + setHoveredIndex(undefined); + } + }} + style={applyCssVars({ + [styles.vars.iconSize]: `${size}px`, + })} + className={classNames(styles.touchable, { + [styles.disabled]: disabled, + [styles.firstIcon]: index === 0, + [styles.lastIcon]: index === iconList.length - 1, + })} + > +
{iconElement}
+
+ )} + /> + ); + }; + + return role === 'img' ? ( + + {iconList.map(renderIcon)} + + ) : ( + { + setCurrentValue(labelList.findIndex((value) => value === label) + 1); + }} + value={labelList[currentValue - 1]} + dataAttributes={dataAttributes} + > + {iconList.map(renderIcon)} + + ); +}; + +export const Rating = ({dataAttributes, ...props}: RatingProps): JSX.Element => ( + +); + +export const InfoRating = ({dataAttributes, icon, size, ...props}: InfoRatingProps): JSX.Element => ( + +); diff --git a/src/text-tokens.tsx b/src/text-tokens.tsx index cd04341a8..bdeaaed1d 100644 --- a/src/text-tokens.tsx +++ b/src/text-tokens.tsx @@ -62,6 +62,12 @@ export type Dictionary = { timerDisplayMinutesLabel: string; timerDisplaySecondsLabel: string; tableActionsHeaderLabel: string; + ratingVeryBadLabel: string; + ratingBadLabel: string; + ratingRegularLabel: string; + ratingGoodLabel: string; + ratingVeryGoodLabel: string; + ratingQuantitativeLabel: string; }; export type TextToken = Record; @@ -492,3 +498,45 @@ export const tableActionsHeaderLabel: TextToken = { de: 'Aktionen', pt: 'Ações', }; + +export const ratingVeryBadLabel: TextToken = { + es: 'muy malo', + en: 'very bad', + de: 'sehr schlecht', + pt: 'muito ruim', +}; + +export const ratingBadLabel: TextToken = { + es: 'malo', + en: 'bad', + de: 'schlecht', + pt: 'ruim', +}; + +export const ratingRegularLabel: TextToken = { + es: 'regular', + en: 'regular', + de: 'regular', + pt: 'regular', +}; + +export const ratingGoodLabel: TextToken = { + es: 'bueno', + en: 'good', + de: 'gut', + pt: 'bom', +}; + +export const ratingVeryGoodLabel: TextToken = { + es: 'muy bueno', + en: 'very good', + de: 'sehr gut', + pt: 'muito bom', +}; + +export const ratingQuantitativeLabel: TextToken = { + es: '1$s de 2$s', + en: '1$s out of 2$s', + de: '1$s von 2$s', + pt: '1$s de 2$s', +};