chore: unified status display system

This commit is contained in:
Aleix Soler 2025-07-16 18:36:01 +02:00
parent 04ff2d4a5c
commit 2d1ccd1127
12 changed files with 158 additions and 132 deletions

View file

@ -1,6 +1,6 @@
import React from "react";
import { NoteStatus } from "@/types/noteStatus";
import { StatusChip } from "../atoms/StatusChip";
import { StatusDisplay } from "../atoms/StatusDisplay";
import { SettingItem } from "../SettingsUI.tsx/SettingItem";
interface Props {
@ -25,9 +25,11 @@ export const CurrentStatusChips: React.FC<Props> = ({
}}
>
{currentStatuses.map((status) => (
<StatusChip
<StatusDisplay
key={status.name}
status={status}
variant="chip"
removable
onRemove={() => onRemoveStatus(status)}
/>
))}

View file

@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo } from "react";
import { useState, useCallback, useMemo } from "react";
import { FilterSection } from "./components/FilterSection";
import { TagSection } from "./components/TagSection";
import { LoadingSpinner } from "./components/LoadingSpinner";

View file

@ -1,5 +1,5 @@
import React, { useCallback } from "react";
import { StatusBadge } from "@/components/atoms/StatusBadge";
import { StatusDisplay } from "@/components/atoms/StatusDisplay";
import { CollapsibleCounter } from "@/components/atoms/CollapsibleCounter";
import { FileItem, StatusItem } from "../GroupedStatusView";
import { FileList } from "./FileList";
@ -43,10 +43,11 @@ export const StatusGroup = ({
return (
<div className="grouped-status-group">
<div className="grouped-status-group-header" onClick={handleToggle}>
<StatusBadge
<StatusDisplay
status={
{ ...status, icon: status.icon || "" } as NoteStatus
}
variant="badge"
/>
<div className="grouped-status-group-info">
<CollapsibleCounter

View file

@ -1,6 +1,6 @@
import React from "react";
import { StatusTemplate } from "@/types/pluginSettings";
import { TemplateStatusChip } from "./TemplateStatusChip";
import { StatusDisplay } from "../atoms/StatusDisplay";
interface TemplateItemProps {
template: StatusTemplate;
@ -29,7 +29,7 @@ export const TemplateItem: React.FC<TemplateItemProps> = ({
<div className="setting-item-description">{template.description}:</div>
<div className="template-statuses">
{template.statuses.map((status, index) => (
<TemplateStatusChip key={index} status={status} />
<StatusDisplay key={index} status={status} variant="template" />
))}
</div>
</div>

View file

@ -1,18 +0,0 @@
import { NoteStatus } from "@/types/noteStatus";
import React from "react";
interface StatusChipProps {
status: NoteStatus;
}
export const TemplateStatusChip: React.FC<StatusChipProps> = ({ status }) => (
<div className="template-status-chip">
<span
className="template-status-color-dot"
style={{ "--dot-color": status.color } as React.CSSProperties}
/>
<span>
{status.icon} {status.name}
</span>
</div>
);

View file

@ -3,7 +3,7 @@ import { NoteStatus } from "@/types/noteStatus";
import { StatusTemplate } from "@/types/pluginSettings";
import { GroupLabel } from "../atoms/GroupLabel";
import { CollapsibleCounter } from "../atoms/CollapsibleCounter";
import { StatusBadge } from "../atoms/StatusBadge";
import { StatusDisplay } from "../atoms/StatusDisplay";
import { useStatusBarContext } from "./StatusBarContext";
export interface StatusBarGroupProps {
@ -39,8 +39,9 @@ export const StatusBarGroup: FC<StatusBarGroupProps> = ({
}}
title={status.description}
>
<StatusBadge
<StatusDisplay
status={status}
variant="badge"
onClick={() => {
onStatusClick(status);
}}

View file

@ -1,6 +1,6 @@
import { TFile } from "obsidian";
import { NoteStatus } from "@/types/noteStatus";
import { StatusBadge } from "@/components/atoms/StatusBadge";
import { StatusDisplay } from "@/components/atoms/StatusDisplay";
import { GroupLabel } from "@/components/atoms/GroupLabel";
interface CurrentNoteInfo {
@ -50,9 +50,10 @@ export const CurrentNoteSection = ({
<div className="current-note-status-list">
{statuses.length > 0 ? (
statuses.map((status) => (
<StatusBadge
<StatusDisplay
key={status.name}
status={status}
variant="badge"
/>
))
) : (

View file

@ -1,6 +1,6 @@
import { useMemo } from "react";
import { BaseNoteStatusService } from "@/core/noteStatusService";
import { StatusBadge } from "@/components/atoms/StatusBadge";
import { StatusDisplay } from "@/components/atoms/StatusDisplay";
import { VaultStats } from "./useVaultStats";
interface StatusDistributionChartProps {
@ -50,7 +50,10 @@ export const StatusDistributionChart = ({
<div key={name} className="status-chart-item">
<div className="status-chart-info">
{status && (
<StatusBadge status={status} />
<StatusDisplay
status={status}
variant="badge"
/>
)}
<span className="status-chart-count">
{count} notes ({percentage}%)

View file

@ -1,6 +1,6 @@
import React from "react";
import { GroupedStatuses } from "@/types/noteStatus";
import { StatusBadge } from "../atoms/StatusBadge";
import { StatusDisplay } from "../atoms/StatusDisplay";
export interface Props {
statuses: GroupedStatuses;
@ -44,7 +44,10 @@ export const StatusFileInfoPopup: React.FC<Props> = ({ statuses }) => {
key={`${groupName}-${index}`}
className="status-item"
>
<StatusBadge status={status} />
<StatusDisplay
status={status}
variant="badge"
/>
{status.description && (
<div
className="status-description"

View file

@ -1,25 +0,0 @@
import { FC } from "react";
import { NoteStatus } from "@/types/noteStatus";
export interface StatusBadgeProps {
status: NoteStatus;
onClick?: () => void;
}
export const StatusBadge: FC<StatusBadgeProps> = ({ status, onClick }) => {
return (
<div
className="status-badge-container"
style={{
backgroundColor: `${status.color}15`,
border: `1px solid ${status.color}30`,
}}
onClick={onClick}
>
<div className="status-badge-item">
<span className="status-badge-icon">{status.icon}</span>
<span className="status-badge-text">{status.name}</span>
</div>
</div>
);
};

View file

@ -1,74 +0,0 @@
import { NoteStatus } from "@/types/noteStatus";
import { FC, memo, useState } from "react";
interface Props {
status: NoteStatus;
onRemove: () => void;
}
export const StatusChip: FC<Props> = memo(({ status, onRemove }) => {
const [isRemoving, setIsRemoving] = useState(false);
const handleRemove = (e: React.MouseEvent) => {
e.stopPropagation();
setIsRemoving(true);
setTimeout(() => {
onRemove();
}, 150);
};
return (
<div
className="note-status-chip"
title={
status.description
? `${status.name} - ${status.description}`
: status.name
}
style={{
display: "inline-flex",
alignItems: "center",
gap: "6px",
padding: "4px 8px",
background: "var(--interactive-accent)",
color: "var(--text-on-accent)",
borderRadius: "var(--radius-s)",
fontSize: "var(--font-ui-smaller)",
cursor: "pointer",
transition: "all 150ms ease",
opacity: isRemoving ? "0.5" : "1",
}}
>
<span className="note-status-chip-icon">{status.icon}</span>
<span className="note-status-chip-text">{status.name}</span>
<div
className="note-status-chip-remove"
onClick={handleRemove}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "16px",
height: "16px",
borderRadius: "50%",
background: "rgba(255, 255, 255, 0.2)",
cursor: "pointer",
}}
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</div>
</div>
);
});

View file

@ -0,0 +1,132 @@
import { NoteStatus } from "@/types/noteStatus";
import { FC, memo, useState } from "react";
export type StatusDisplayVariant = "chip" | "badge" | "template";
interface StatusDisplayProps {
status: NoteStatus;
variant: StatusDisplayVariant;
removable?: boolean;
onRemove?: () => void;
onClick?: () => void;
}
export const StatusDisplay: FC<StatusDisplayProps> = memo(
({ status, variant, removable = false, onRemove, onClick }) => {
const [isRemoving, setIsRemoving] = useState(false);
const handleRemove = (e: React.MouseEvent) => {
e.stopPropagation();
if (onRemove) {
setIsRemoving(true);
setTimeout(() => {
onRemove();
}, 150);
}
};
const handleClick = () => {
if (onClick) {
onClick();
}
};
if (variant === "chip") {
return (
<div
className="note-status-chip"
title={
status.description
? `${status.name} - ${status.description}`
: status.name
}
style={{
display: "inline-flex",
alignItems: "center",
gap: "6px",
padding: "4px 8px",
background: "var(--interactive-accent)",
color: "var(--text-on-accent)",
borderRadius: "var(--radius-s)",
fontSize: "var(--font-ui-smaller)",
cursor: onClick ? "pointer" : "default",
transition: "all 150ms ease",
opacity: isRemoving ? "0.5" : "1",
}}
onClick={handleClick}
>
<span className="note-status-chip-icon">{status.icon}</span>
<span className="note-status-chip-text">{status.name}</span>
{removable && (
<div
className="note-status-chip-remove"
onClick={handleRemove}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "16px",
height: "16px",
borderRadius: "50%",
background: "rgba(255, 255, 255, 0.2)",
cursor: "pointer",
}}
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</div>
)}
</div>
);
}
if (variant === "badge") {
return (
<div
className="status-badge-container"
style={{
backgroundColor: `${status.color}15`,
border: `1px solid ${status.color}30`,
}}
onClick={handleClick}
>
<div className="status-badge-item">
<span className="status-badge-icon">{status.icon}</span>
<span className="status-badge-text">{status.name}</span>
</div>
</div>
);
}
if (variant === "template") {
return (
<div className="template-status-chip" onClick={handleClick}>
<span
className="template-status-color-dot"
style={
{
"--dot-color": status.color,
} as React.CSSProperties
}
/>
<span>
{status.icon} {status.name}
</span>
</div>
);
}
return null;
},
);