Skip to content

Commit

Permalink
Adds CartIcon component
Browse files Browse the repository at this point in the history
  • Loading branch information
rabidkitten committed May 8, 2023
1 parent 594d168 commit 3185bb6
Show file tree
Hide file tree
Showing 9 changed files with 2,095 additions and 130 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ cart is empty.

![Choking Hazard component](docs/components/cart-empty.png)

### Cart Icon

The Cart Icon component displays a shopping cart icon that can be clicked to
redirect the customer to their shopping cart.

![Cart Icon component](docs/components/cart-icon1.png)

The Cart Icon with a badge specifying the number of products in the shopping
cart:

![Cart Icon component with badge](docs/components/cart-icon2.png)

### Choking Hazard

The Choking Hazard component displays a warning on products that contain small
Expand Down
Binary file added docs/components/cart-icon1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/components/cart-icon2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2,004 changes: 1,875 additions & 129 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ditus/react-web-retail",
"version": "1.0.21",
"version": "1.0.22",
"description": "A set of reusable React web components based on Material UI for retail applications.",
"private": false,
"main": "dist/index.js",
Expand Down Expand Up @@ -35,6 +35,9 @@
"react-dom": "^18.2.0",
"react": "^18.2.0"
},
"dependencies": {
"react-ionicons": "^4.2.0"
},
"devDependencies": {
"@babel/core": "^7.21.4",
"@babel/preset-env": "^7.21.4",
Expand Down
86 changes: 86 additions & 0 deletions src/components/cart-icon/cart-icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// Copyright (c) DITUS INC. All rights reserved. See LICENSE file in the project
// root for details.
//
import React from 'react';
import PropTypes from 'prop-types';
import { Badge, IconButton } from '@mui/material';
import CartOutline from 'react-ionicons/lib/CartOutline';
import { useTranslationProps } from '@ditus/react-translation';

/**
* Represents the icon used to display the shopping cart.
* @param {*} props The properties of the component.
* @returns {HTMLElement} An HTML element representing the component.
*/
function CartIcon(props) {
const {
hidden,
count,
label,
onClick,
} = useTranslationProps({ name: 'CartIcon', props });

if (hidden) {
return null;
}

const handleClick = () => {
if (onClick) {
onClick();
}
};

// If the value is a string, showZero does not work in the badge component.
const numericCount = parseInt(count, 10);

return (
<IconButton
aria-label={label}
onClick={handleClick}
sx={{ fontSize: 0 }}
>
<Badge
badgeContent={numericCount}
color="primary"
showZero={false}
>
<CartOutline
height="30px"
width="30px"
/>
</Badge>
</IconButton>
);
}

export default CartIcon;

CartIcon.propTypes = {
/**
* Specifies the number of items in the cart.
*/
count: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

/**
* Specifies whether the component is visible or hidden.
*/
hidden: PropTypes.bool,

/**
* Specifies the label on the <button className=""></button>
*/
label: PropTypes.string,

/**
* Specifies the callback function to invoke when the button is clicked.
*/
onClick: PropTypes.func,
};

CartIcon.defaultProps = {
count: 0,
hidden: false,
label: 'Shopping Cart',
onClick: null,
};
88 changes: 88 additions & 0 deletions src/components/cart-icon/cart-icon.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @jest-environment jsdom
*/

//
// Copyright (c) DITUS INC. All rights reserved. See LICENSE file in the project
// root for details.
//
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CartIcon from './cart-icon';

describe('CartIcon', () => {
it('displays by default.', async () => {
const { container } = render(
<CartIcon />,
);

expect(container).not.toBeEmptyDOMElement();
});

it('does not display when hidden.', async () => {
const { container } = render(
<CartIcon hidden />,
);

expect(container).toBeEmptyDOMElement();
});

it('displays when not hidden.', async () => {
const { container } = render(
<CartIcon hidden={false} />,
);

expect(container).not.toBeEmptyDOMElement();
});

it('displays a button with a default label.', async () => {
const { container } = render(
<CartIcon />,
);

expect(container).not.toBeEmptyDOMElement();
expect(screen.queryByRole('button', { name: 'Shopping Cart' })).not.toBeNull();
});

it('displays a button with the the specified label.', async () => {
const { container } = render(
<CartIcon label="This is new" />,
);

expect(container).not.toBeEmptyDOMElement();
expect(screen.queryByRole('button', { name: 'This is new' })).not.toBeNull();
});

it('displays a badge when the shopping cart contains items.', async () => {
const { container } = render(
<CartIcon count={3} />,
);

expect(container).not.toBeEmptyDOMElement();
expect(screen.queryByText('3')).not.toBeNull();
});

it('can be clicked.', async () => {
const user = userEvent.setup();

const handleClick = jest.fn();

const { container, rerender } = render(
<CartIcon onClick={handleClick} />,
);

expect(container).not.toBeEmptyDOMElement();

await user.click(screen.getByRole('button'));
expect(handleClick).toBeCalled();

// The component should not produce an error if the button is clicked and no
// onClick callback is provided.
rerender(
<CartIcon />,
);

await user.click(screen.getByRole('button'));
});
});
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable import/prefer-default-export */
export { default as CartEmpty } from './cart-empty/cart-empty';
export { default as CartIcon } from './cart-icon/cart-icon';
export { default as ChokingHazard } from './choking-hazard/choking-hazard';
export { default as FeaturedProductAttributes } from './featured-product-attributes/featured-product-attributes';
export { default as FeaturedTaxonomies } from './featured-taxonomies/featured-taxonomies';
Expand Down
29 changes: 29 additions & 0 deletions src/stories/CartIcon.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable jsdoc/require-jsdoc */
/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import CartIcon from '../components/cart-icon/cart-icon';

export default {
title: 'Components/CartIcon',
component: CartIcon,
argTypes: {
},
};

const theme = createTheme({
});

function Template(args) {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<CartIcon {...args} />
</ThemeProvider>
);
}

export const Primary = Template.bind({});
Primary.args = {
};

0 comments on commit 3185bb6

Please sign in to comment.