From bd53ad39912d8973882dd8e6512acf0fda11f186 Mon Sep 17 00:00:00 2001 From: Jamie Henson Date: Thu, 5 Dec 2024 16:14:06 +0000 Subject: [PATCH] feat: add Badge component --- src/core/Badge.tsx | 153 ++++++++++++++++++ src/core/Badge/Badge.stories.tsx | 109 +++++++++++++ .../__snapshots__/Badge.stories.tsx.snap | 83 ++++++++++ 3 files changed, 345 insertions(+) create mode 100644 src/core/Badge.tsx create mode 100644 src/core/Badge/Badge.stories.tsx create mode 100644 src/core/Badge/__snapshots__/Badge.stories.tsx.snap diff --git a/src/core/Badge.tsx b/src/core/Badge.tsx new file mode 100644 index 000000000..1ffa17800 --- /dev/null +++ b/src/core/Badge.tsx @@ -0,0 +1,153 @@ +import React, { PropsWithChildren, useMemo } from "react"; +import { IconName, IconSize } from "./Icon/types"; +import Icon from "./Icon"; +import cn from "./utils/cn"; +import { ColorClassColorGroups } from "./styles/colors/types"; + +/** + * Props for the Badge component. + */ +interface BadgeProps { + /** + * The size of the badge. Can be one of "xs", "sm", "md", or "lg". + */ + size?: "xs" | "sm" | "md" | "lg"; + + /** + * The color of the badge. Can be a value from ColorClassColorGroups or "red". + */ + color?: ColorClassColorGroups | "red"; + + /** + * The name of the icon to be displayed before the children in the badge. + */ + iconBefore?: IconName; + + /** + * The name of the icon to be displayed after the children in the badge. + */ + iconAfter?: IconName; + + /** + * Additional CSS class names to apply to the badge. + */ + className?: string; + + /** + * Whether the badge is disabled. Defaults to false. + */ + disabled?: boolean; + + /** + * Whether the badge is focusable. Defaults to false. + */ + focusable?: boolean; + + /** + * Whether the badge is hoverable. Defaults to false. + */ + hoverable?: boolean; + + /** + * The size of the icons in the badge. Defaults to 12px. + */ + iconSize?: IconSize; + + /** + * Accessible label for the badge when interactive + */ + ariaLabel?: string; +} + +const Badge: React.FC> = ({ + size = "md", + color = "neutral", + iconBefore, + iconAfter, + className, + children, + disabled = false, + focusable = false, + hoverable = false, + iconSize = "12px", + ariaLabel, +}) => { + const sizeClass = useMemo(() => { + switch (size) { + case "xs": + return "px-8 py-0 text-[10px] leading-tight"; + case "sm": + return "px-8 py-2 text-[10px] leading-tight"; + case "md": + return "px-[10px] py-2 text-[11px] leading-normal"; + case "lg": + return "px-12 py-[3px] text-[12px] leading-normal"; + } + }, [size]); + + const childClass = useMemo(() => { + switch (size) { + case "xs": + case "sm": + return "leading-[18px]"; + case "md": + case "lg": + return "leading-[20px]"; + } + }, [size]); + + const colorClass = useMemo(() => { + switch (color) { + case "neutral": + return "text-neutral-900 dark:text-neutral-400"; + case "violet": + return "text-violet-400"; + case "orange": + return "text-orange-600"; + case "yellow": + return "text-yellow-600"; + case "green": + return "text-green-600"; + case "blue": + return "text-blue-600"; + case "pink": + return "text-pink-600"; + case "red": + return "text-orange-700"; + } + }, [color]); + + return ( +
+ {iconBefore ? ( + + ) : null} + + {children} + + {iconAfter ? ( + + ) : null} +
+ ); +}; + +export default Badge; diff --git a/src/core/Badge/Badge.stories.tsx b/src/core/Badge/Badge.stories.tsx new file mode 100644 index 000000000..d28ab1733 --- /dev/null +++ b/src/core/Badge/Badge.stories.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/react"; +import Badge from "../Badge"; + +export default { + title: "Components/Badge", + component: Badge, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + children: "BADGE", +}; +Default.parameters = { + docs: { + description: { + story: + "The default badge component. Can be customized with various props. Use the `children` prop to set the text.", + }, + }, +}; + +export const WithBeforeIcon = Template.bind({}); +WithBeforeIcon.args = { + children: "BADGE", + iconBefore: "icon-gui-check-lotus-circled", +}; +WithBeforeIcon.parameters = { + docs: { + description: { + story: + "Badge component with an icon displayed before the text. Use the `iconBefore` prop to set the icon.", + }, + }, +}; + +export const WithAfterIcon = Template.bind({}); +WithAfterIcon.args = { + children: "BADGE", + iconAfter: "icon-gui-chevron-down", +}; +WithAfterIcon.parameters = { + docs: { + description: { + story: + "Badge component with an icon displayed after the text. Use the `iconAfter` prop to set the icon.", + }, + }, +}; + +export const Hoverable = Template.bind({}); +Hoverable.args = { + children: "BADGE", + hoverable: true, +}; +Hoverable.parameters = { + docs: { + description: { + story: + "Badge component that changes appearance when hovered over. Use the `hoverable` prop to enable this feature.", + }, + }, +}; + +export const Focusable = Template.bind({}); +Focusable.args = { + children: "BADGE", + focusable: true, +}; +Focusable.parameters = { + docs: { + description: { + story: + "Badge component that can be focused using keyboard navigation. Use the `focusable` prop to enable this feature.", + }, + }, +}; + +export const WithLargeIconSize = Template.bind({}); +WithLargeIconSize.args = { + children: "BADGE", + iconBefore: "icon-gui-check-circled", + iconSize: "16px", +}; +WithLargeIconSize.parameters = { + docs: { + description: { + story: + "Badge component with a larger icon size. Use the `iconSize` prop to set the size of the icon.", + }, + }, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + children: "BADGE", + iconBefore: "icon-gui-check-circled", + disabled: true, +}; +Disabled.parameters = { + docs: { + description: { + story: + "Badge component that is disabled and not interactive. Use the `disabled` prop to disable the badge.", + }, + }, +}; diff --git a/src/core/Badge/__snapshots__/Badge.stories.tsx.snap b/src/core/Badge/__snapshots__/Badge.stories.tsx.snap new file mode 100644 index 000000000..a4f1d3a24 --- /dev/null +++ b/src/core/Badge/__snapshots__/Badge.stories.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Components/Badge Default smoke-test 1`] = ` +
+ + BADGE + +
+`; + +exports[`Components/Badge Disabled smoke-test 1`] = ` +
+ + + + + + BADGE + +
+`; + +exports[`Components/Badge Focusable smoke-test 1`] = ` +
+ + BADGE + +
+`; + +exports[`Components/Badge Hoverable smoke-test 1`] = ` +
+ + BADGE + +
+`; + +exports[`Components/Badge WithAfterIcon smoke-test 1`] = ` +
+ + BADGE + + + + + +
+`; + +exports[`Components/Badge WithBeforeIcon smoke-test 1`] = ` +
+ + + + + + BADGE + +
+`; + +exports[`Components/Badge WithLargeIconSize smoke-test 1`] = ` +
+ + + + + + BADGE + +
+`;