mirror of
https://github.com/devonthesofa/obsidian-note-status.git
synced 2026-07-22 12:30:24 +00:00
chore: unified status display system
This commit is contained in:
parent
04ff2d4a5c
commit
2d1ccd1127
12 changed files with 158 additions and 132 deletions
|
|
@ -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)}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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}%)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
132
components/atoms/StatusDisplay.tsx
Normal file
132
components/atoms/StatusDisplay.tsx
Normal 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;
|
||||
},
|
||||
);
|
||||
Loading…
Reference in a new issue