mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
adjusted
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: OverflowBadges.jsx
|
||||
* Type of Program: Generic Component
|
||||
* Description: Renders up to `max` badges inline. Remaining items collapse into a
|
||||
* "+N" overflow button that opens a dialog listing all items.
|
||||
*
|
||||
* HOW TO USE:
|
||||
* <OverflowBadges
|
||||
* items={groups} // array of objects (or primitives)
|
||||
* labelKey="group_code" // key used for the badge label
|
||||
* dialogTitleKey="name" // key used for the left side in the dialog row (optional)
|
||||
* keyKey="group_id" // key used as React key
|
||||
* dialogTitle="All groups" // dialog heading (optional)
|
||||
* max={2} // how many badges to show before collapsing (default: 2)
|
||||
* badgeVariant="outline" // shadcn Badge variant (default: "outline")
|
||||
* badgeClassName="font-mono text-xs" // extra classes on each badge (optional)
|
||||
* />
|
||||
*
|
||||
* // Primitive arrays (no keys needed):
|
||||
* <OverflowBadges items={["SALES-A1", "HR-B2", "IT-C3"]} max={2} />
|
||||
***********************************************************************************************************************************************************************/
|
||||
import { useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
/**
|
||||
* @param {object} props
|
||||
* @param {Array} props.items Array of objects or primitives to render.
|
||||
* @param {string} [props.labelKey] Key on each item to use as the badge label. Omit for primitive arrays.
|
||||
* @param {string} [props.dialogTitleKey] Key on each item to show as the row title in the dialog. Falls back to labelKey.
|
||||
* @param {string} [props.keyKey] Key on each item to use as the React key. Falls back to index.
|
||||
* @param {string} [props.dialogTitle] Heading shown in the overflow dialog. Default: "All items".
|
||||
* @param {number} [props.max] Max badges shown inline before collapsing. Default: 2.
|
||||
* @param {string} [props.badgeVariant] shadcn Badge variant. Default: "outline".
|
||||
* @param {string} [props.badgeClassName] Extra className on each Badge.
|
||||
* @param {string} [props.emptyText] Text shown when items is empty. Default: "—".
|
||||
*/
|
||||
export function OverflowBadges({
|
||||
items = [],
|
||||
labelKey,
|
||||
dialogTitleKey,
|
||||
keyKey,
|
||||
dialogTitle = "All items",
|
||||
max = 1,
|
||||
badgeVariant = "outline",
|
||||
badgeClassName = "text-xs",
|
||||
emptyText = "—",
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!items.length)
|
||||
return <span className="text-muted-foreground text-xs">{emptyText}</span>;
|
||||
|
||||
const getLabel = (item) =>
|
||||
labelKey ? item[labelKey] : String(item);
|
||||
|
||||
const getTitle = (item) =>
|
||||
dialogTitleKey ? item[dialogTitleKey] : labelKey ? item[labelKey] : String(item);
|
||||
|
||||
const getKey = (item, i) =>
|
||||
keyKey ? item[keyKey] : i;
|
||||
|
||||
const visible = items.slice(0, max);
|
||||
const overflow = items.slice(max);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{visible.map((item, i) => (
|
||||
<Badge
|
||||
key={getKey(item, i)}
|
||||
variant={badgeVariant}
|
||||
className={badgeClassName}
|
||||
>
|
||||
{getLabel(item)}
|
||||
</Badge>
|
||||
))}
|
||||
|
||||
{overflow.length > 0 && (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setOpen(true); }}
|
||||
className="inline-flex items-center justify-center h-5 px-1.5 rounded-md border border-dashed text-xs text-muted-foreground hover:text-foreground hover:border-foreground transition-colors"
|
||||
>
|
||||
+{overflow.length}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="sm:max-w-[360px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-2 pt-1">
|
||||
{items.map((item, i) => {
|
||||
const label = getLabel(item);
|
||||
const title = getTitle(item);
|
||||
const isSame = label === title;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={getKey(item, i)}
|
||||
className="flex items-center justify-between rounded-lg border px-3 py-2"
|
||||
>
|
||||
<span className="text-sm font-medium">{title}</span>
|
||||
{!isSame && (
|
||||
<Badge variant={badgeVariant} className={badgeClassName}>
|
||||
{label}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user