mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-12 02:38:15 +00:00
chore: initial Untitled UI Vite scaffold with FontAwesome Pro
This commit is contained in:
415
src/components/base/badges/badges.tsx
Normal file
415
src/components/base/badges/badges.tsx
Normal file
@@ -0,0 +1,415 @@
|
||||
import type { MouseEventHandler, ReactNode } from "react";
|
||||
import { X as CloseX } from "@untitledui/icons";
|
||||
import { Dot } from "@/components/foundations/dot-icon";
|
||||
import { cx } from "@/utils/cx";
|
||||
import type { BadgeColors, BadgeTypeToColorMap, BadgeTypes, FlagTypes, IconComponentType, Sizes } from "./badge-types";
|
||||
import { badgeTypes } from "./badge-types";
|
||||
|
||||
export const filledColors: Record<BadgeColors, { root: string; addon: string; addonButton: string }> = {
|
||||
gray: {
|
||||
root: "bg-utility-gray-50 text-utility-gray-700 ring-utility-gray-200",
|
||||
addon: "text-utility-gray-500",
|
||||
addonButton: "hover:bg-utility-gray-100 text-utility-gray-400 hover:text-utility-gray-500",
|
||||
},
|
||||
brand: {
|
||||
root: "bg-utility-brand-50 text-utility-brand-700 ring-utility-brand-200",
|
||||
addon: "text-utility-brand-500",
|
||||
addonButton: "hover:bg-utility-brand-100 text-utility-brand-400 hover:text-utility-brand-500",
|
||||
},
|
||||
error: {
|
||||
root: "bg-utility-error-50 text-utility-error-700 ring-utility-error-200",
|
||||
addon: "text-utility-error-500",
|
||||
addonButton: "hover:bg-utility-error-100 text-utility-error-400 hover:text-utility-error-500",
|
||||
},
|
||||
warning: {
|
||||
root: "bg-utility-warning-50 text-utility-warning-700 ring-utility-warning-200",
|
||||
addon: "text-utility-warning-500",
|
||||
addonButton: "hover:bg-utility-warning-100 text-utility-warning-400 hover:text-utility-warning-500",
|
||||
},
|
||||
success: {
|
||||
root: "bg-utility-success-50 text-utility-success-700 ring-utility-success-200",
|
||||
addon: "text-utility-success-500",
|
||||
addonButton: "hover:bg-utility-success-100 text-utility-success-400 hover:text-utility-success-500",
|
||||
},
|
||||
"gray-blue": {
|
||||
root: "bg-utility-gray-blue-50 text-utility-gray-blue-700 ring-utility-gray-blue-200",
|
||||
addon: "text-utility-gray-blue-500",
|
||||
addonButton: "hover:bg-utility-gray-blue-100 text-utility-gray-blue-400 hover:text-utility-gray-blue-500",
|
||||
},
|
||||
"blue-light": {
|
||||
root: "bg-utility-blue-light-50 text-utility-blue-light-700 ring-utility-blue-light-200",
|
||||
addon: "text-utility-blue-light-500",
|
||||
addonButton: "hover:bg-utility-blue-light-100 text-utility-blue-light-400 hover:text-utility-blue-light-500",
|
||||
},
|
||||
blue: {
|
||||
root: "bg-utility-blue-50 text-utility-blue-700 ring-utility-blue-200",
|
||||
addon: "text-utility-blue-500",
|
||||
addonButton: "hover:bg-utility-blue-100 text-utility-blue-400 hover:text-utility-blue-500",
|
||||
},
|
||||
indigo: {
|
||||
root: "bg-utility-indigo-50 text-utility-indigo-700 ring-utility-indigo-200",
|
||||
addon: "text-utility-indigo-500",
|
||||
addonButton: "hover:bg-utility-indigo-100 text-utility-indigo-400 hover:text-utility-indigo-500",
|
||||
},
|
||||
purple: {
|
||||
root: "bg-utility-purple-50 text-utility-purple-700 ring-utility-purple-200",
|
||||
addon: "text-utility-purple-500",
|
||||
addonButton: "hover:bg-utility-purple-100 text-utility-purple-400 hover:text-utility-purple-500",
|
||||
},
|
||||
pink: {
|
||||
root: "bg-utility-pink-50 text-utility-pink-700 ring-utility-pink-200",
|
||||
addon: "text-utility-pink-500",
|
||||
addonButton: "hover:bg-utility-pink-100 text-utility-pink-400 hover:text-utility-pink-500",
|
||||
},
|
||||
orange: {
|
||||
root: "bg-utility-orange-50 text-utility-orange-700 ring-utility-orange-200",
|
||||
addon: "text-utility-orange-500",
|
||||
addonButton: "hover:bg-utility-orange-100 text-utility-orange-400 hover:text-utility-orange-500",
|
||||
},
|
||||
};
|
||||
|
||||
const addonOnlyColors = Object.fromEntries(Object.entries(filledColors).map(([key, value]) => [key, { root: "", addon: value.addon }])) as Record<
|
||||
BadgeColors,
|
||||
{ root: string; addon: string }
|
||||
>;
|
||||
|
||||
const withPillTypes = {
|
||||
[badgeTypes.pillColor]: {
|
||||
common: "size-max flex items-center whitespace-nowrap rounded-full ring-1 ring-inset",
|
||||
styles: filledColors,
|
||||
},
|
||||
[badgeTypes.badgeColor]: {
|
||||
common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset",
|
||||
styles: filledColors,
|
||||
},
|
||||
[badgeTypes.badgeModern]: {
|
||||
common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset shadow-xs",
|
||||
styles: {
|
||||
gray: {
|
||||
root: "bg-primary text-secondary ring-primary",
|
||||
addon: "text-gray-500",
|
||||
addonButton: "hover:bg-utility-gray-100 text-utility-gray-400 hover:text-utility-gray-500",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const withBadgeTypes = {
|
||||
[badgeTypes.pillColor]: {
|
||||
common: "size-max flex items-center whitespace-nowrap rounded-full ring-1 ring-inset",
|
||||
styles: filledColors,
|
||||
},
|
||||
[badgeTypes.badgeColor]: {
|
||||
common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset",
|
||||
styles: filledColors,
|
||||
},
|
||||
[badgeTypes.badgeModern]: {
|
||||
common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset bg-primary text-secondary ring-primary shadow-xs",
|
||||
styles: addonOnlyColors,
|
||||
},
|
||||
};
|
||||
|
||||
export type BadgeColor<T extends BadgeTypes> = BadgeTypeToColorMap<typeof withPillTypes>[T];
|
||||
|
||||
interface BadgeProps<T extends BadgeTypes> {
|
||||
type?: T;
|
||||
size?: Sizes;
|
||||
color?: BadgeColor<T>;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Badge = <T extends BadgeTypes>(props: BadgeProps<T>) => {
|
||||
const { type = "pill-color", size = "md", color = "gray", children } = props;
|
||||
const colors = withPillTypes[type];
|
||||
|
||||
const pillSizes = {
|
||||
sm: "py-0.5 px-2 text-xs font-medium",
|
||||
md: "py-0.5 px-2.5 text-sm font-medium",
|
||||
lg: "py-1 px-3 text-sm font-medium",
|
||||
};
|
||||
const badgeSizes = {
|
||||
sm: "py-0.5 px-1.5 text-xs font-medium",
|
||||
md: "py-0.5 px-2 text-sm font-medium",
|
||||
lg: "py-1 px-2.5 text-sm font-medium rounded-lg",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
[badgeTypes.pillColor]: pillSizes,
|
||||
[badgeTypes.badgeColor]: badgeSizes,
|
||||
[badgeTypes.badgeModern]: badgeSizes,
|
||||
};
|
||||
|
||||
return <span className={cx(colors.common, sizes[type][size], colors.styles[color].root, props.className)}>{children}</span>;
|
||||
};
|
||||
|
||||
interface BadgeWithDotProps<T extends BadgeTypes> {
|
||||
type?: T;
|
||||
size?: Sizes;
|
||||
color?: BadgeTypeToColorMap<typeof withBadgeTypes>[T];
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const BadgeWithDot = <T extends BadgeTypes>(props: BadgeWithDotProps<T>) => {
|
||||
const { size = "md", color = "gray", type = "pill-color", className, children } = props;
|
||||
|
||||
const colors = withBadgeTypes[type];
|
||||
|
||||
const pillSizes = {
|
||||
sm: "gap-1 py-0.5 pl-1.5 pr-2 text-xs font-medium",
|
||||
md: "gap-1.5 py-0.5 pl-2 pr-2.5 text-sm font-medium",
|
||||
lg: "gap-1.5 py-1 pl-2.5 pr-3 text-sm font-medium",
|
||||
};
|
||||
|
||||
const badgeSizes = {
|
||||
sm: "gap-1 py-0.5 px-1.5 text-xs font-medium",
|
||||
md: "gap-1.5 py-0.5 px-2 text-sm font-medium",
|
||||
lg: "gap-1.5 py-1 px-2.5 text-sm font-medium rounded-lg",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
[badgeTypes.pillColor]: pillSizes,
|
||||
[badgeTypes.badgeColor]: badgeSizes,
|
||||
[badgeTypes.badgeModern]: badgeSizes,
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cx(colors.common, sizes[type][size], colors.styles[color].root, className)}>
|
||||
<Dot className={colors.styles[color].addon} size="sm" />
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
interface BadgeWithIconProps<T extends BadgeTypes> {
|
||||
type?: T;
|
||||
size?: Sizes;
|
||||
color?: BadgeTypeToColorMap<typeof withBadgeTypes>[T];
|
||||
iconLeading?: IconComponentType;
|
||||
iconTrailing?: IconComponentType;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const BadgeWithIcon = <T extends BadgeTypes>(props: BadgeWithIconProps<T>) => {
|
||||
const { size = "md", color = "gray", type = "pill-color", iconLeading: IconLeading, iconTrailing: IconTrailing, children, className } = props;
|
||||
|
||||
const colors = withBadgeTypes[type];
|
||||
|
||||
const icon = IconLeading ? "leading" : "trailing";
|
||||
|
||||
const pillSizes = {
|
||||
sm: {
|
||||
trailing: "gap-0.5 py-0.5 pl-2 pr-1.5 text-xs font-medium",
|
||||
leading: "gap-0.5 py-0.5 pr-2 pl-1.5 text-xs font-medium",
|
||||
},
|
||||
md: {
|
||||
trailing: "gap-1 py-0.5 pl-2.5 pr-2 text-sm font-medium",
|
||||
leading: "gap-1 py-0.5 pr-2.5 pl-2 text-sm font-medium",
|
||||
},
|
||||
lg: {
|
||||
trailing: "gap-1 py-1 pl-3 pr-2.5 text-sm font-medium",
|
||||
leading: "gap-1 py-1 pr-3 pl-2.5 text-sm font-medium",
|
||||
},
|
||||
};
|
||||
const badgeSizes = {
|
||||
sm: {
|
||||
trailing: "gap-0.5 py-0.5 pl-2 pr-1.5 text-xs font-medium",
|
||||
leading: "gap-0.5 py-0.5 pr-2 pl-1.5 text-xs font-medium",
|
||||
},
|
||||
md: {
|
||||
trailing: "gap-1 py-0.5 pl-2 pr-1.5 text-sm font-medium",
|
||||
leading: "gap-1 py-0.5 pr-2 pl-1.5 text-sm font-medium",
|
||||
},
|
||||
lg: {
|
||||
trailing: "gap-1 py-1 pl-2.5 pr-2 text-sm font-medium rounded-lg",
|
||||
leading: "gap-1 py-1 pr-2.5 pl-2 text-sm font-medium rounded-lg",
|
||||
},
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
[badgeTypes.pillColor]: pillSizes,
|
||||
[badgeTypes.badgeColor]: badgeSizes,
|
||||
[badgeTypes.badgeModern]: badgeSizes,
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cx(colors.common, sizes[type][size][icon], colors.styles[color].root, className)}>
|
||||
{IconLeading && <IconLeading className={cx(colors.styles[color].addon, "size-3 stroke-3")} />}
|
||||
{children}
|
||||
{IconTrailing && <IconTrailing className={cx(colors.styles[color].addon, "size-3 stroke-3")} />}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
interface BadgeWithFlagProps<T extends BadgeTypes> {
|
||||
type?: T;
|
||||
size?: Sizes;
|
||||
flag?: FlagTypes;
|
||||
color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const BadgeWithFlag = <T extends BadgeTypes>(props: BadgeWithFlagProps<T>) => {
|
||||
const { size = "md", color = "gray", flag = "AU", type = "pill-color", children } = props;
|
||||
|
||||
const colors = withPillTypes[type];
|
||||
|
||||
const pillSizes = {
|
||||
sm: "gap-1 py-0.5 pl-0.75 pr-2 text-xs font-medium",
|
||||
md: "gap-1.5 py-0.5 pl-1 pr-2.5 text-sm font-medium",
|
||||
lg: "gap-1.5 py-1 pl-1.5 pr-3 text-sm font-medium",
|
||||
};
|
||||
const badgeSizes = {
|
||||
sm: "gap-1 py-0.5 pl-1 pr-1.5 text-xs font-medium",
|
||||
md: "gap-1.5 py-0.5 pl-1.5 pr-2 text-sm font-medium",
|
||||
lg: "gap-1.5 py-1 pl-2 pr-2.5 text-sm font-medium rounded-lg",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
[badgeTypes.pillColor]: pillSizes,
|
||||
[badgeTypes.badgeColor]: badgeSizes,
|
||||
[badgeTypes.badgeModern]: badgeSizes,
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cx(colors.common, sizes[type][size], colors.styles[color].root)}>
|
||||
<img src={`https://www.untitledui.com/images/flags/${flag}.svg`} className="size-4 max-w-none rounded-full" alt={`${flag} flag`} />
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
interface BadgeWithImageProps<T extends BadgeTypes> {
|
||||
type?: T;
|
||||
size?: Sizes;
|
||||
imgSrc: string;
|
||||
color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const BadgeWithImage = <T extends BadgeTypes>(props: BadgeWithImageProps<T>) => {
|
||||
const { size = "md", color = "gray", type = "pill-color", imgSrc, children } = props;
|
||||
|
||||
const colors = withPillTypes[type];
|
||||
|
||||
const pillSizes = {
|
||||
sm: "gap-1 py-0.5 pl-0.75 pr-2 text-xs font-medium",
|
||||
md: "gap-1.5 py-0.5 pl-1 pr-2.5 text-sm font-medium",
|
||||
lg: "gap-1.5 py-1 pl-1.5 pr-3 text-sm font-medium",
|
||||
};
|
||||
const badgeSizes = {
|
||||
sm: "gap-1 py-0.5 pl-1 pr-1.5 text-xs font-medium",
|
||||
md: "gap-1.5 py-0.5 pl-1.5 pr-2 text-sm font-medium",
|
||||
lg: "gap-1.5 py-1 pl-2 pr-2.5 text-sm font-medium rounded-lg",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
[badgeTypes.pillColor]: pillSizes,
|
||||
[badgeTypes.badgeColor]: badgeSizes,
|
||||
[badgeTypes.badgeModern]: badgeSizes,
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cx(colors.common, sizes[type][size], colors.styles[color].root)}>
|
||||
<img src={imgSrc} className="size-4 max-w-none rounded-full" alt="Badge image" />
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
interface BadgeWithButtonProps<T extends BadgeTypes> {
|
||||
type?: T;
|
||||
size?: Sizes;
|
||||
icon?: IconComponentType;
|
||||
color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
|
||||
children: ReactNode;
|
||||
/**
|
||||
* The label for the button.
|
||||
*/
|
||||
buttonLabel?: string;
|
||||
/**
|
||||
* The click event handler for the button.
|
||||
*/
|
||||
onButtonClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export const BadgeWithButton = <T extends BadgeTypes>(props: BadgeWithButtonProps<T>) => {
|
||||
const { size = "md", color = "gray", type = "pill-color", icon: Icon = CloseX, buttonLabel, children } = props;
|
||||
|
||||
const colors = withPillTypes[type];
|
||||
|
||||
const pillSizes = {
|
||||
sm: "gap-0.5 py-0.5 pl-2 pr-0.75 text-xs font-medium",
|
||||
md: "gap-0.5 py-0.5 pl-2.5 pr-1 text-sm font-medium",
|
||||
lg: "gap-0.5 py-1 pl-3 pr-1.5 text-sm font-medium",
|
||||
};
|
||||
const badgeSizes = {
|
||||
sm: "gap-0.5 py-0.5 pl-1.5 pr-0.75 text-xs font-medium",
|
||||
md: "gap-0.5 py-0.5 pl-2 pr-1 text-sm font-medium",
|
||||
lg: "gap-0.5 py-1 pl-2.5 pr-1.5 text-sm font-medium rounded-lg",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
[badgeTypes.pillColor]: pillSizes,
|
||||
[badgeTypes.badgeColor]: badgeSizes,
|
||||
[badgeTypes.badgeModern]: badgeSizes,
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cx(colors.common, sizes[type][size], colors.styles[color].root)}>
|
||||
{children}
|
||||
<button
|
||||
type="button"
|
||||
aria-label={buttonLabel}
|
||||
onClick={props.onButtonClick}
|
||||
className={cx(
|
||||
"flex cursor-pointer items-center justify-center p-0.5 outline-focus-ring transition duration-100 ease-linear focus-visible:outline-2",
|
||||
colors.styles[color].addonButton,
|
||||
type === "pill-color" ? "rounded-full" : "rounded-[3px]",
|
||||
)}
|
||||
>
|
||||
<Icon className="size-3 stroke-[3px] transition-inherit-all" />
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
interface BadgeIconProps<T extends BadgeTypes> {
|
||||
type?: T;
|
||||
size?: Sizes;
|
||||
icon: IconComponentType;
|
||||
color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const BadgeIcon = <T extends BadgeTypes>(props: BadgeIconProps<T>) => {
|
||||
const { size = "md", color = "gray", type = "pill-color", icon: Icon } = props;
|
||||
|
||||
const colors = withPillTypes[type];
|
||||
|
||||
const pillSizes = {
|
||||
sm: "p-1.25",
|
||||
md: "p-1.5",
|
||||
lg: "p-2",
|
||||
};
|
||||
|
||||
const badgeSizes = {
|
||||
sm: "p-1.25",
|
||||
md: "p-1.5",
|
||||
lg: "p-2 rounded-lg",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
[badgeTypes.pillColor]: pillSizes,
|
||||
[badgeTypes.badgeColor]: badgeSizes,
|
||||
[badgeTypes.badgeModern]: badgeSizes,
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={cx(colors.common, sizes[type][size], colors.styles[color].root)}>
|
||||
<Icon className={cx("size-3 stroke-[3px]", colors.styles[color].addon)} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user