diff --git a/packages/styles/src/molecules/cart-item.css b/packages/styles/src/molecules/cart-item.css new file mode 100644 index 0000000000..ac096e5433 --- /dev/null +++ b/packages/styles/src/molecules/cart-item.css @@ -0,0 +1,54 @@ +[data-fs-cart-item] { + padding: 16px; + color: #171a1c; + background-color: #ffffff; + border: 2px solid #e3e6e8; + border-radius: 4px; +} + +[data-fs-cart-item-content] { + display: grid; + grid-template-columns: 72px repeat(4, 1fr); + column-gap: 8px; + align-items: center; +} + +[data-fs-cart-item-summary] { + flex-direction: column; + grid-column: 2 / span 4; +} + +[data-fs-cart-item-title] { + margin-bottom: 0; + line-height: 1.2; + text-decoration: none; + outline: none; +} + +[data-fs-cart-item-prices] { + display: flex; + align-items: baseline; +} + +[data-fs-cart-item-prices] [data-store-price] + [data-store-price] { + margin-left: 16px; +} + +[data-fs-cart-item-prices] [data-store-price][data-variant="listing"] { + font-size: 0.875rem; + color: #5d666f; +} + +[data-fs-cart-item-prices] [data-store-price][data-variant="spot"] { + font-size: 1.25rem; +} + +[data-fs-cart-item-actions] [data-store-quantity-selector] [data-store-icon] { + color: #00419e; +} + +[data-fs-cart-item-actions] { + display: flex; + justify-content: space-between; + margin-top: 16px; +} diff --git a/packages/styles/src/molecules/index.css b/packages/styles/src/molecules/index.css index 99557afc19..2fb42743ea 100644 --- a/packages/styles/src/molecules/index.css +++ b/packages/styles/src/molecules/index.css @@ -6,6 +6,7 @@ @import './breadcrumb.css'; @import './card.css'; @import './carousel.css'; +@import './cart-item.css'; @import './dropdown.css'; @import './form.css'; @import './gift.css'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 0f1e304019..4699d06652 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -99,6 +99,25 @@ export type { CardActionsProps, } from './molecules/Card' +export { + default as CartItem, + CartItemActions, + CartItemContent, + CartItemImage, + CartItemPrices, + CartItemSummary, + CartItemTitle, +} from './molecules/CartItem' +export type { + CartItemProps, + CartItemActionsProps, + CartItemContentProps, + CartItemImageProps, + CartItemPricesProps, + CartItemSummaryProps, + CartItemTitleProps, +} from './molecules/CartItem' + export { default as Bullets } from './molecules/Bullets' export type { BulletsProps } from './molecules/Bullets' diff --git a/packages/ui/src/molecules/CartItem/CartItem.test.tsx b/packages/ui/src/molecules/CartItem/CartItem.test.tsx new file mode 100644 index 0000000000..48bcf21599 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItem.test.tsx @@ -0,0 +1,79 @@ +import { render } from '@testing-library/react' +import { axe } from 'jest-axe' +import React from 'react' + +import CartItem, { + CartItemActions, + CartItemContent, + CartItemImage, + CartItemPrices, + CartItemSummary, + CartItemTitle, +} from './' + +const product = { + name: 'Apple Magic Mouse', + imageUrl: + 'https://assets.vtex.app/unsafe/216x216/center/middle/https%3A%2F%2Fstoreframework.vtexassets.com%2Farquivos%2Fids%2F190902%2Funsplash-magic-mouse.jpg%3Fv%3D637800136963870000', + price: { + listing: 999, + spot: 950, + }, +} + +const CartItemTest = () => { + return ( + + + + {product.name} + + + + {product.name} + + + {product.price.listing} + {product.price.spot} + + + + + + + + Quantity Selector + + + ) +} + +describe('`CartItem`', () => { + describe('Data attributes', () => { + it(`should have the correct format`, () => { + const testIdToDataAttributeMap = { + 'store-cart-item': 'data-fs-cart-item', + 'store-cart-item-content': 'data-fs-cart-item-content', + 'store-cart-item-image': 'data-fs-cart-item-image', + 'store-cart-item-summary': 'data-fs-cart-item-summary', + 'store-cart-item-title': 'data-fs-cart-item-title', + 'store-cart-item-prices': 'data-fs-cart-item-prices', + 'store-cart-item-actions': 'data-fs-cart-item-actions', + } + const { getByTestId } = render() + + for (const [testId, dataAttribute] of Object.entries(testIdToDataAttributeMap)) { + expect(getByTestId(testId)).toHaveAttribute(dataAttribute) + } + }) + }) + + describe('Accessibility', () => { + it('should have no violations', async () => { + const { getByTestId } = render() + + expect(await axe(getByTestId('store-cart-item'))).toHaveNoViolations() + expect(await axe(getByTestId('store-cart-item'))).toHaveNoIncompletes() + }) + }) +}) diff --git a/packages/ui/src/molecules/CartItem/CartItem.tsx b/packages/ui/src/molecules/CartItem/CartItem.tsx new file mode 100644 index 0000000000..31d90fc0c7 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItem.tsx @@ -0,0 +1,22 @@ +import React, { forwardRef } from 'react' +import type { HTMLAttributes } from 'react' + +export interface CartItemProps extends HTMLAttributes { + /** + * ID to find this component in testing tools (e.g.: Cypress, Testing Library, and Jest). + */ + testId?: string +} + +const CartItem = forwardRef(function CartItem( + { testId = 'store-cart-item', children, ...otherProps }, + ref +) { + return ( +
+ {children} +
+ ) +}) + +export default CartItem diff --git a/packages/ui/src/molecules/CartItem/CartItemActions.tsx b/packages/ui/src/molecules/CartItem/CartItemActions.tsx new file mode 100644 index 0000000000..13c099fb66 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItemActions.tsx @@ -0,0 +1,24 @@ +import React, { forwardRef } from 'react' +import type { HTMLAttributes } from 'react' + +export interface CartItemActionsProps extends HTMLAttributes { + /** + * ID to find this component in testing tools (e.g.: Cypress, Testing Library, and Jest). + */ + testId?: string +} + +const CartItemActions = forwardRef( + function CartItemActions( + { testId = 'store-cart-item-actions', children, ...otherProps }, + ref + ) { + return ( +
+ {children} +
+ ) + } +) + +export default CartItemActions diff --git a/packages/ui/src/molecules/CartItem/CartItemContent.tsx b/packages/ui/src/molecules/CartItem/CartItemContent.tsx new file mode 100644 index 0000000000..cbb4ad5141 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItemContent.tsx @@ -0,0 +1,24 @@ +import type { HTMLAttributes } from 'react' +import React, { forwardRef } from 'react' + +export interface CartItemContentProps extends HTMLAttributes { + /** + * ID to find this component in testing tools (e.g.: Cypress, Testing Library, and Jest). + */ + testId?: string +} + +const CartItemContent = forwardRef( + function CartItemContent( + { testId = 'store-cart-item-content', children, ...otherProps }, + ref + ) { + return ( +
+ {children} +
+ ) + } +) + +export default CartItemContent diff --git a/packages/ui/src/molecules/CartItem/CartItemImage.tsx b/packages/ui/src/molecules/CartItem/CartItemImage.tsx new file mode 100644 index 0000000000..ea94062457 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItemImage.tsx @@ -0,0 +1,22 @@ +import type { HTMLAttributes } from 'react' +import React, { forwardRef } from 'react' + +export interface CartItemImageProps extends HTMLAttributes { + /** + * ID to find this component in testing tools (e.g.: Cypress, Testing Library, and Jest). + */ + testId?: string +} + +const CartItemImage = forwardRef(function CartItemImage( + { testId = 'store-cart-item-image', children, ...otherProps }, + ref +) { + return ( +
+ {children} +
+ ) +}) + +export default CartItemImage diff --git a/packages/ui/src/molecules/CartItem/CartItemPrices.tsx b/packages/ui/src/molecules/CartItem/CartItemPrices.tsx new file mode 100644 index 0000000000..a2e9271298 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItemPrices.tsx @@ -0,0 +1,24 @@ +import React, { forwardRef } from 'react' +import type { HTMLAttributes } from 'react' + +export interface CartItemPricesProps extends HTMLAttributes { + /** + * ID to find this component in testing tools (e.g.: Cypress, Testing Library, and Jest). + */ + testId?: string +} + +const CartItemPrices = forwardRef( + function CartItemPrices( + { testId = 'store-cart-item-prices', children, ...otherProps }, + ref + ) { + return ( + + {children} + + ) + } +) + +export default CartItemPrices diff --git a/packages/ui/src/molecules/CartItem/CartItemSummary.tsx b/packages/ui/src/molecules/CartItem/CartItemSummary.tsx new file mode 100644 index 0000000000..6da5b01f28 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItemSummary.tsx @@ -0,0 +1,24 @@ +import React, { forwardRef } from 'react' +import type { HTMLAttributes } from 'react' + +export interface CartItemSummaryProps extends HTMLAttributes { + /** + * ID to find this component in testing tools (e.g.: Cypress, Testing Library, and Jest). + */ + testId?: string +} + +const CartItemSummary = forwardRef( + function CartItemSummary( + { testId = 'store-cart-item-summary', children, ...otherProps }, + ref + ) { + return ( +
+ {children} +
+ ) + } +) + +export default CartItemSummary diff --git a/packages/ui/src/molecules/CartItem/CartItemTitle.tsx b/packages/ui/src/molecules/CartItem/CartItemTitle.tsx new file mode 100644 index 0000000000..2e7a65e870 --- /dev/null +++ b/packages/ui/src/molecules/CartItem/CartItemTitle.tsx @@ -0,0 +1,22 @@ +import type { HTMLAttributes } from 'react' +import React, { forwardRef } from 'react' + +export interface CartItemTitleProps extends HTMLAttributes { + /** + * ID to find this component in testing tools (e.g.: Cypress, Testing Library, and Jest). + */ + testId?: string +} + +const CartItemTitle = forwardRef(function CartItemTitle( + { testId = 'store-cart-item-title', children, ...otherProps }, + ref +) { + return ( +
+ {children} +
+ ) +}) + +export default CartItemTitle diff --git a/packages/ui/src/molecules/CartItem/index.tsx b/packages/ui/src/molecules/CartItem/index.tsx new file mode 100644 index 0000000000..8bfcf4d10b --- /dev/null +++ b/packages/ui/src/molecules/CartItem/index.tsx @@ -0,0 +1,20 @@ +export { default } from './CartItem' +export type { CartItemProps } from './CartItem' + +export { default as CartItemActions } from './CartItemActions' +export type { CartItemActionsProps } from './CartItemActions' + +export { default as CartItemContent } from './CartItemContent' +export type { CartItemContentProps } from './CartItemContent' + +export { default as CartItemImage } from './CartItemImage' +export type { CartItemImageProps } from './CartItemImage' + +export { default as CartItemPrices } from './CartItemPrices' +export type { CartItemPricesProps } from './CartItemPrices' + +export { default as CartItemSummary } from './CartItemSummary' +export type { CartItemSummaryProps } from './CartItemSummary' + +export { default as CartItemTitle } from './CartItemTitle' +export type { CartItemTitleProps } from './CartItemTitle' diff --git a/packages/ui/src/molecules/CartItem/stories/CartItem.mdx b/packages/ui/src/molecules/CartItem/stories/CartItem.mdx new file mode 100644 index 0000000000..ea383f657f --- /dev/null +++ b/packages/ui/src/molecules/CartItem/stories/CartItem.mdx @@ -0,0 +1,79 @@ +import { ArgsTable, Canvas, Props, Story } from '@storybook/addon-docs' + +import CartItem, { + CartItemActions, + CartItemContent, + CartItemImage, + CartItemPrices, + CartItemSummary, + CartItemTitle, +} from '../' + +# Cart Item + + + + + +## Components + +The `CartItem` uses the [Compound Component](https://kentcdodds.com/blog/compound-components-with-react-hooks) pattern, its components are: + +- `CartItem`: the wrapper component; +- `CartItemContent`: the wrapper component for the image and summary; +- `CartItemImage`: the wrapper component for the content's image; +- `CartItemSummary`: the wrapper component for the title and prices; +- `CartItemTitle`: the wrapper component for the title; +- `CartItemPrices`: the wrapper component for the prices; +- `CartItemActions`: the wrapper component for the remove from cart button and quantity selector; + +## Props + +All CartItem-related components support all attributes also supported by the `
` tag. + +### `CartItem` + + + +### `CartItemContent` + + + +### `CartItemImage` + + + +### `CartItemSummary` + + + +### `CartItemTitle` + + + +### `CartItemPrices` + + + +### `CartItemActions` + + + +## CSS Selectors + +```css +[data-fs-cart-item] { +} +[data-fs-cart-item-content] { +} +[data-fs-cart-item-image] { +} +[data-fs-cart-item-summary] { +} +[data-fs-cart-item-title] { +} +[data-fs-cart-item-prices] { +} +[data-fs-cart-item-actions] { +} +``` diff --git a/packages/ui/src/molecules/CartItem/stories/CartItem.stories.tsx b/packages/ui/src/molecules/CartItem/stories/CartItem.stories.tsx new file mode 100644 index 0000000000..5336ed0c3a --- /dev/null +++ b/packages/ui/src/molecules/CartItem/stories/CartItem.stories.tsx @@ -0,0 +1,70 @@ +import React from 'react' + +import CartItem, { + CartItemActions, + CartItemContent, + CartItemImage, + CartItemPrices, + CartItemSummary, + CartItemTitle, +} from '../' +import { Button, Price, QuantitySelector } from '../../../' +import mdx from './CartItem.mdx' + +import type { Meta, Story } from '@storybook/react' +import type { CartItemProps } from '../' + +const product = { + name: 'Apple Magic Mouse', + imageUrl: + 'https://assets.vtex.app/unsafe/216x216/center/middle/https%3A%2F%2Fstoreframework.vtexassets.com%2Farquivos%2Fids%2F190902%2Funsplash-magic-mouse.jpg%3Fv%3D637800136963870000', + price: { + listing: 999, + spot: 950, + }, +} + +const CartItemTemplate: Story = ({ testId }) => { + return ( + + + + {product.name} + + + + {product.name} + + + + + + + + + + + + - }} + rightButtonProps={{ icon: + }} + inputProps={{ readOnly: true }} + /> + + + ) +} + +export const Default = CartItemTemplate.bind({}) + +Default.storyName = 'CartItem' + +export default { + title: 'Molecules/CartItem', + parameters: { + docs: { + page: mdx, + }, + }, +} as Meta diff --git a/packages/ui/src/molecules/QuantitySelector/stories/QuantitySelector.mdx b/packages/ui/src/molecules/QuantitySelector/stories/QuantitySelector.mdx index fc7cefb7cb..0dc1fb2112 100644 --- a/packages/ui/src/molecules/QuantitySelector/stories/QuantitySelector.mdx +++ b/packages/ui/src/molecules/QuantitySelector/stories/QuantitySelector.mdx @@ -7,7 +7,7 @@ import QuantitySelector from '../QuantitySelector' ## Default Style - + # Props