refactor(ui): move AttentionRow to ui and reuse in Inbox
This commit is contained in:
1 parent
279af3b034
commit
c6935ff983
29 files changed
+615
-442
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import type { ElementType, ReactNode } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight } from "@phosphor-icons/react";
|
||||
|
||||
const ATTENTION_STAGGER_MAX = 8;
|
||||
const ATTENTION_STAGGER_STEP = 0.04;
|
||||
|
||||
export type AttentionTone = "warn" | "danger" | "info" | "neutral";
|
||||
|
||||
export interface AttentionRowProps {
|
||||
icon: ElementType;
|
||||
tone?: AttentionTone;
|
||||
title: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
onClick?: () => void;
|
||||
index?: number;
|
||||
rightMeta?: ReactNode;
|
||||
iconColor?: string;
|
||||
iconTileBg?: string;
|
||||
rowBgClass?: string;
|
||||
borderAccentClass?: string;
|
||||
showArrow?: boolean;
|
||||
iconWeight?: "fill" | "bold" | "regular";
|
||||
}
|
||||
|
||||
const toneIconClass: Record<AttentionTone, string> = {
|
||||
warn: "text-warning",
|
||||
danger: "text-destructive",
|
||||
info: "text-primary",
|
||||
neutral: "text-muted-foreground",
|
||||
};
|
||||
|
||||
const toneBgClass: Record<AttentionTone, string> = {
|
||||
warn: "bg-warning-muted",
|
||||
danger: "bg-destructive-muted",
|
||||
info: "bg-accent",
|
||||
neutral: "bg-muted",
|
||||
};
|
||||
|
||||
export function AttentionRow({
|
||||
icon: Icon,
|
||||
tone = "neutral",
|
||||
title,
|
||||
subtitle,
|
||||
onClick,
|
||||
index = 0,
|
||||
rightMeta,
|
||||
iconColor,
|
||||
iconTileBg,
|
||||
rowBgClass,
|
||||
borderAccentClass,
|
||||
showArrow = true,
|
||||
iconWeight,
|
||||
}: AttentionRowProps) {
|
||||
const iconClass = iconColor ? "" : toneIconClass[tone];
|
||||
const bgClass = rowBgClass ?? toneBgClass[tone];
|
||||
const delay = Math.min(index, ATTENTION_STAGGER_MAX) * ATTENTION_STAGGER_STEP;
|
||||
const borderClass = borderAccentClass ?? "border-[color:var(--destructive)] border-opacity-20 hover:border-opacity-40";
|
||||
const isButton = typeof onClick === "function";
|
||||
const commonClass = `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 flex w-full items-center gap-3 rounded-lg border ${borderClass} ${bgClass} px-4 py-3 text-left transition`;
|
||||
|
||||
const iconElement = iconTileBg ? (
|
||||
<div
|
||||
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"
|
||||
style={{ backgroundColor: iconTileBg }}
|
||||
>
|
||||
<Icon size={18} weight={iconWeight} className={iconClass} style={iconColor ? { color: iconColor } : undefined} />
|
||||
</div>
|
||||
) : (
|
||||
<Icon size={18} weight={iconWeight} className={`shrink-0 ${iconClass}`} style={iconColor ? { color: iconColor } : undefined} />
|
||||
);
|
||||
|
||||
const inner = (
|
||||
<>
|
||||
{iconElement}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-[13px] font-medium text-foreground">{title}</div>
|
||||
{subtitle ? <div className="mt-0.5 text-[11px] text-muted-foreground">{subtitle}</div> : null}
|
||||
</div>
|
||||
{rightMeta}
|
||||
{showArrow ? <ArrowRight size={14} className="shrink-0 text-muted-foreground" /> : null}
|
||||
</>
|
||||
);
|
||||
|
||||
if (isButton) {
|
||||
return (
|
||||
<motion.button
|
||||
initial={{ opacity: 0, y: 6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.22, delay, ease: [0.16, 1, 0.3, 1] }}
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={commonClass}
|
||||
>
|
||||
{inner}
|
||||
</motion.button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.22, delay, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={commonClass}
|
||||
>
|
||||
{inner}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -2,3 +2,5 @@ export { Badge, SectionButton, Panel, Field, Input, TextArea, Select, ActionButt
|
||||
export { StatusPill, InlineNotice, ConfirmDialog, EmptyState } from "./feedback";
|
||||
export { Modal, ListFrame, ListRow } from "./layout";
|
||||
export { useFocusTrap, useReducedMotion, ErrorBoundary } from "./hooks";
|
||||
export { AttentionRow } from "./AttentionRow";
|
||||
export type { AttentionRowProps, AttentionTone } from "./AttentionRow";
|
||||
Reference in new issue
Block a user