From dea0776e2be813b2d93f6cac92d507c0f3432a24 Mon Sep 17 00:00:00 2001 From: olena <154923065+nebby2105@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:24:58 +0200 Subject: [PATCH] create badge component (#2919) * add badge and stories * add tests and update stories * fixed tests description and comments suggestions * fixed variables names * changed badgeVariant to variant * add styles * fixed storybook description * fixed sb description * fixed props descriptions --- src/design-system/components/badge/Badge.scss | 20 ++ src/design-system/components/badge/Badge.tsx | 52 +++++ src/design-system/stories/Badge.stories.tsx | 193 ++++++++++++++++++ .../components/Badge/Badge.spec.jsx | 48 +++++ 4 files changed, 313 insertions(+) create mode 100644 src/design-system/components/badge/Badge.scss create mode 100644 src/design-system/components/badge/Badge.tsx create mode 100644 src/design-system/stories/Badge.stories.tsx create mode 100644 tests/unit/design-system/components/Badge/Badge.spec.jsx diff --git a/src/design-system/components/badge/Badge.scss b/src/design-system/components/badge/Badge.scss new file mode 100644 index 000000000..fc67ecb07 --- /dev/null +++ b/src/design-system/components/badge/Badge.scss @@ -0,0 +1,20 @@ +@use '~scss/utilities' as *; + +@mixin badge-style($bg-color, $color) { + & > .MuiBadge-dot { + background-color: $bg-color; + } + + & > .MuiBadge-standard { + background-color: $bg-color; + color: $color; + } +} + +.#{$prefix}badge-error { + @include badge-style(var(--#{$prefix}red-500), var(--#{$prefix}neutral-50)); +} + +.#{$prefix}badge-success { + @include badge-style(var(--#{$prefix}green-600), var(--#{$prefix}neutral-50)); +} diff --git a/src/design-system/components/badge/Badge.tsx b/src/design-system/components/badge/Badge.tsx new file mode 100644 index 000000000..3edf14afc --- /dev/null +++ b/src/design-system/components/badge/Badge.tsx @@ -0,0 +1,52 @@ +import { Badge as MuiBadge, BadgeProps as MuiBadgeProps } from '@mui/material' +import './Badge.scss' + +type BadgeColor = 'primary' | 'success' | 'error' + +type SmallBadgeProps = { + variant: 'sm' + color?: BadgeColor + isVisible?: boolean +} + +type LargeBadgeProps = { + variant: 'lg' + badgeContent: number + maxContent?: number + color?: BadgeColor + isVisible?: boolean + isZeroShown?: boolean +} + +type BadgeProps = (LargeBadgeProps | SmallBadgeProps) & + Omit + +const Badge: React.FC = ({ + children, + isVisible = true, + color = 'primary', + ...props +}) => { + const badgeContent = isVisible ? props.badgeContent : 0 + const variant = props.variant === 'sm' ? 'dot' : 'standard' + const maxContent = + props.variant === 'lg' ? (props.maxContent ?? 10) : undefined + const isZeroShown = + props.variant === 'lg' ? (props.isZeroShown ?? false) : undefined + + return ( + + {children} + + ) +} + +export default Badge diff --git a/src/design-system/stories/Badge.stories.tsx b/src/design-system/stories/Badge.stories.tsx new file mode 100644 index 000000000..b57c364cb --- /dev/null +++ b/src/design-system/stories/Badge.stories.tsx @@ -0,0 +1,193 @@ +import { IconButton } from '@mui/material' +import type { Meta, StoryObj } from '@storybook/react' +import Badge from '~scss-components/badge/Badge' +import NotificationsRoundedIcon from '@mui/icons-material/NotificationsRounded' + +const meta: Meta = { + title: 'Components/Badge', + component: Badge, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: ` +The \`Badge\` component is a versatile and reusable component designed to enhance your application's UI by displaying notifications, counts, or statuses. Built on top of Material-UI's Badge, this custom implementation ensures consistency with your design system while adding additional customization options. + +#### Key Features: + +- **Variants:** + - \`sm\`: Displays the badge as a small dot (ideal for simple status indicators). + - \`lg\`: Displays the badge with a numeric value. +- **Visibility Control:** Use the \`isVisible\` prop to toggle the visibility of the badge dynamically. When set to \`false\`, the badge content is hidden. +- **Content Customization:** + - \`badgeContent\`: Render a numeric content. + - \`maxContent\`: Specify the maximum value to display. If badgeContent exceeds this value, it displays \`maxContent+\`. +- **Color Options:** Supports predefined color options: \`primary\`, \`success\`, and \`error\`, aligning with the design system. +- **Children Support:** Seamlessly wrap any element (e.g., icons, text, buttons) with the badge, making it highly adaptable to your application's requirements. +` + } + } + }, + argTypes: { + variant: { + description: + 'Specifies the style of the badge. `sm` displays the badge as a small dot for status indicators, while `lg` displays a numeric value.', + control: { type: 'radio' }, + options: ['sm', 'lg'] + }, + color: { + description: + 'Defines the color of the badge, affecting its visual style. Can be used to align the badge with your application`s theme.', + control: { type: 'radio' }, + options: ['primary', 'success', 'error'], + defaultValue: 'primary' + }, + badgeContent: { + description: + 'The content to be displayed inside the badge. If the variant is set to "sm," this prop is ignored.', + control: { type: 'number' }, + defaultValue: 4 + }, + maxContent: { + description: + 'Controls the visibility of the badge. When set to `false`, the badge content is hidden.', + control: { type: 'number' }, + defaultValue: 10 + }, + isZeroShown: { + description: + 'determines whether the badge displays a `0` when the `variant` is set to `lg` and `badgeContent` is `0`', + control: { type: 'boolean' }, + defaultValue: false + }, + isVisible: { + description: + 'Determines whether the badge is visible. Set this to `false` to hide the badge, regardless of its content.', + control: { type: 'boolean' }, + defaultValue: true + }, + children: { + description: + 'The element to be wrapped by the badge. This is typically an icon, button, or other visual element.', + control: { type: 'text' } + } + }, + args: { + variant: 'lg', + color: 'primary', + badgeContent: 7, + maxContent: 10, + isZeroShown: false, + isVisible: true + } +} + +export default meta + +type Story = StoryObj + +export const FullBadge: Story = { + args: { + variant: 'lg', + color: 'primary', + isVisible: true, + badgeContent: 7, + maxContent: 10, + children: ( + + + + ) + }, + parameters: { + docs: { + description: { + story: + 'A large badge (`lg` variant) with numeric content and a maximum display value of 10.' + } + } + } +} + +export const SmallBadge: Story = { + args: { + variant: 'sm', + color: 'primary', + isVisible: true, + children: ( + + + + ) + }, + parameters: { + docs: { + description: { + story: + 'Simple configuration of the badge with a primary color, `sm` size displaying as small dot, and visibility enabled' + } + } + } +} + +export const SmallSuccessBadge: Story = { + args: { + ...SmallBadge.args, + color: 'success' + }, + parameters: { + docs: { + description: { + story: 'A small badge with success color' + } + } + } +} + +export const SmallErrorBadge: Story = { + args: { + ...SmallBadge.args, + color: 'error' + }, + parameters: { + docs: { + description: { + story: 'A small badge with error color' + } + } + } +} + +export const SuccessFullBadge: Story = { + args: { + ...FullBadge.args, + color: 'success', + maxContent: 99, + badgeContent: 102 + }, + parameters: { + docs: { + description: { + story: + 'A large badge (`lg` variant) with numeric content, success color, and a maximum display value of 99.' + } + } + } +} + +export const ErrorFullBadge: Story = { + args: { + ...FullBadge.args, + color: 'error', + maxContent: 10, + badgeContent: 7 + }, + parameters: { + docs: { + description: { + story: + 'A large badge (`lg` variant) with numeric content, error color, and a content of 7.' + } + } + } +} diff --git a/tests/unit/design-system/components/Badge/Badge.spec.jsx b/tests/unit/design-system/components/Badge/Badge.spec.jsx new file mode 100644 index 000000000..5d18e3d7a --- /dev/null +++ b/tests/unit/design-system/components/Badge/Badge.spec.jsx @@ -0,0 +1,48 @@ +import { NotificationsActiveRounded } from '@mui/icons-material' +import { IconButton } from '@mui/material' +import { screen, render } from '@testing-library/react' +import Badge from '~scss-components/badge/Badge' + +describe('Badge Component', () => { + it('it should be rendered with "dot" variant when "sm" is passed', () => { + render( + + + + + + ) + + const dotBadge = screen.getByText('', { + selector: '.MuiBadge-dot' + }) + + expect(dotBadge).toBeInTheDocument() + }) + + it('it should be rendered with correct badge content when "lg" is passed', () => { + render( + + + + + + ) + + const badgeContent = screen.getByText('5') + expect(badgeContent).toBeInTheDocument + }) + + it('badge content should be hidden when "isVisible" is false', () => { + render( + + + + + + ) + + const badgeContent = screen.queryByText('5') + expect(badgeContent).not.toBeInTheDocument() + }) +})