Skip to content

Commit

Permalink
create badge component (#2919)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nebby2105 authored Dec 4, 2024
1 parent 7fe550f commit dea0776
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/design-system/components/badge/Badge.scss
Original file line number Diff line number Diff line change
@@ -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));
}
52 changes: 52 additions & 0 deletions src/design-system/components/badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -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<MuiBadgeProps, 'variant'>

const Badge: React.FC<BadgeProps> = ({
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 (
<MuiBadge
badgeContent={badgeContent}
className={`s2s-badge-${color}`}
color={color}
max={maxContent}
overlap='circular'
showZero={isZeroShown}
variant={variant}
>
{children}
</MuiBadge>
)
}

export default Badge
193 changes: 193 additions & 0 deletions src/design-system/stories/Badge.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Badge> = {
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<typeof Badge>

export const FullBadge: Story = {
args: {
variant: 'lg',
color: 'primary',
isVisible: true,
badgeContent: 7,
maxContent: 10,
children: (
<IconButton>
<NotificationsRoundedIcon color='info' />
</IconButton>
)
},
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: (
<IconButton>
<NotificationsRoundedIcon color='info' />
</IconButton>
)
},
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.'
}
}
}
}
48 changes: 48 additions & 0 deletions tests/unit/design-system/components/Badge/Badge.spec.jsx
Original file line number Diff line number Diff line change
@@ -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(
<Badge variant='sm'>
<IconButton>
<NotificationsActiveRounded />
</IconButton>
</Badge>
)

const dotBadge = screen.getByText('', {
selector: '.MuiBadge-dot'
})

expect(dotBadge).toBeInTheDocument()
})

it('it should be rendered with correct badge content when "lg" is passed', () => {
render(
<Badge variant='lg' badgeContent={5}>
<IconButton>
<NotificationsActiveRounded />
</IconButton>
</Badge>
)

const badgeContent = screen.getByText('5')
expect(badgeContent).toBeInTheDocument
})

it('badge content should be hidden when "isVisible" is false', () => {
render(
<Badge variant='lg' badgeContent={5} isVisible={false}>
<IconButton>
<NotificationsActiveRounded />
</IconButton>
</Badge>
)

const badgeContent = screen.queryByText('5')
expect(badgeContent).not.toBeInTheDocument()
})
})

0 comments on commit dea0776

Please sign in to comment.