mirror of
https://github.com/devonthesofa/obsidian-note-status.git
synced 2026-07-22 12:30:24 +00:00
chore: improve UI/UX CustomStatusItem component
This commit is contained in:
parent
b56b928f4d
commit
ae4463cc88
2 changed files with 205 additions and 33 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { NoteStatus } from "@/types/noteStatus";
|
||||
import { PluginSettings } from "@/types/pluginSettings";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Input } from "@/components/atoms/Input";
|
||||
|
||||
export type Props = {
|
||||
|
|
@ -21,35 +21,126 @@ export const CustomStatusItem: React.FC<Props> = ({
|
|||
onCustomStatusChange,
|
||||
onCustomStatusRemove,
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [focusedField, setFocusedField] = useState<string | null>(null);
|
||||
|
||||
const isValid = status.name.trim().length > 0;
|
||||
const hasIcon = status.icon.trim().length > 0;
|
||||
|
||||
return (
|
||||
<div className="custom-status-card">
|
||||
<div
|
||||
className="custom-status-card"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{
|
||||
background: "var(--background-secondary)",
|
||||
border: `1px solid ${isValid ? "var(--background-modifier-border)" : "var(--background-modifier-error)"}`,
|
||||
borderRadius: "var(--radius-m)",
|
||||
padding: "var(--size-4-3)",
|
||||
marginBottom: "var(--size-4-2)",
|
||||
transition: "all 0.2s ease",
|
||||
transform: isHovered ? "translateY(-1px)" : "translateY(0)",
|
||||
boxShadow: isHovered ? "0 4px 12px rgba(0,0,0,0.1)" : "none",
|
||||
}}
|
||||
>
|
||||
{/* Status Preview Bar */}
|
||||
<div
|
||||
className="custom-status-preview"
|
||||
style={{ borderLeftColor: status.color || "var(--text-muted)" }}
|
||||
style={{
|
||||
height: "4px",
|
||||
background: status.color || "var(--text-muted)",
|
||||
borderRadius: "2px",
|
||||
marginBottom: "var(--size-4-2)",
|
||||
opacity: isValid ? 1 : 0.5,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "var(--size-4-2)",
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
variant="text"
|
||||
value={status.icon}
|
||||
onChange={(value) =>
|
||||
onCustomStatusChange(index, "icon", value || "❓")
|
||||
}
|
||||
placeholder="🔥"
|
||||
className="custom-status-icon-input"
|
||||
/>
|
||||
<div className="custom-status-text-inputs">
|
||||
{/* Icon Input with Preview Circle */}
|
||||
<div style={{ position: "relative" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "48px",
|
||||
height: "48px",
|
||||
borderRadius: "50%",
|
||||
background: status.color
|
||||
? `${status.color}20`
|
||||
: "var(--background-modifier-border)",
|
||||
border: `2px solid ${status.color || "var(--background-modifier-border)"}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "20px",
|
||||
transition: "all 0.2s ease",
|
||||
transform:
|
||||
focusedField === "icon"
|
||||
? "scale(1.05)"
|
||||
: "scale(1)",
|
||||
}}
|
||||
>
|
||||
{hasIcon ? status.icon : "?"}
|
||||
</div>
|
||||
<Input
|
||||
variant="text"
|
||||
value={status.icon}
|
||||
onChange={(value) =>
|
||||
onCustomStatusChange(index, "icon", value || "")
|
||||
}
|
||||
placeholder="🔥"
|
||||
onFocus={() => setFocusedField("icon")}
|
||||
onBlur={() => setFocusedField(null)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "48px",
|
||||
height: "48px",
|
||||
opacity: 0,
|
||||
cursor: "pointer",
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Text Inputs */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "var(--size-2-2)",
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
variant="text"
|
||||
value={status.name}
|
||||
onChange={(value) =>
|
||||
onCustomStatusChange(
|
||||
index,
|
||||
"name",
|
||||
value || "unnamed",
|
||||
)
|
||||
onCustomStatusChange(index, "name", value || "")
|
||||
}
|
||||
placeholder="Status name"
|
||||
className="custom-status-name-input"
|
||||
style={{ color: status.color || "var(--text-normal)" }}
|
||||
onFocus={() => setFocusedField("name")}
|
||||
onBlur={() => setFocusedField(null)}
|
||||
style={{
|
||||
background: "var(--background-primary)",
|
||||
border: `1px solid ${
|
||||
focusedField === "name"
|
||||
? "var(--interactive-accent)"
|
||||
: !isValid
|
||||
? "var(--background-modifier-error)"
|
||||
: "var(--background-modifier-border)"
|
||||
}`,
|
||||
borderRadius: "var(--radius-s)",
|
||||
padding: "var(--size-2-2) var(--size-2-3)",
|
||||
fontSize: "var(--font-ui-medium)",
|
||||
fontWeight: "var(--font-semibold)",
|
||||
color: status.color || "var(--text-normal)",
|
||||
transition: "border-color 0.2s ease",
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
variant="text"
|
||||
|
|
@ -58,27 +149,106 @@ export const CustomStatusItem: React.FC<Props> = ({
|
|||
onCustomStatusChange(index, "description", value)
|
||||
}
|
||||
placeholder="Description (optional)"
|
||||
className="custom-status-description-input"
|
||||
onFocus={() => setFocusedField("description")}
|
||||
onBlur={() => setFocusedField(null)}
|
||||
style={{
|
||||
background: "var(--background-primary)",
|
||||
border: `1px solid ${
|
||||
focusedField === "description"
|
||||
? "var(--interactive-accent)"
|
||||
: "var(--background-modifier-border)"
|
||||
}`,
|
||||
borderRadius: "var(--radius-s)",
|
||||
padding: "var(--size-2-1) var(--size-2-3)",
|
||||
fontSize: "var(--font-ui-small)",
|
||||
color: "var(--text-muted)",
|
||||
transition: "border-color 0.2s ease",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="custom-status-controls">
|
||||
<Input
|
||||
variant="color"
|
||||
value={status.color || "#ffffff"}
|
||||
onChange={(value) =>
|
||||
onCustomStatusChange(index, "color", value)
|
||||
}
|
||||
className="custom-status-color-input"
|
||||
/>
|
||||
|
||||
{/* Controls */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "var(--size-2-2)",
|
||||
}}
|
||||
>
|
||||
{/* Color Picker */}
|
||||
<div style={{ position: "relative" }}>
|
||||
<Input
|
||||
variant="color"
|
||||
value={status.color || "#ffffff"}
|
||||
onChange={(value) =>
|
||||
onCustomStatusChange(index, "color", value)
|
||||
}
|
||||
onFocus={() => setFocusedField("color")}
|
||||
onBlur={() => setFocusedField(null)}
|
||||
style={{
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
borderRadius: "var(--radius-s)",
|
||||
border: `2px solid ${
|
||||
focusedField === "color"
|
||||
? "var(--interactive-accent)"
|
||||
: "var(--background-modifier-border)"
|
||||
}`,
|
||||
cursor: "pointer",
|
||||
transition: "border-color 0.2s ease",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Remove Button */}
|
||||
<button
|
||||
className="custom-status-remove-btn clickable-icon mod-warning"
|
||||
onClick={() => onCustomStatusRemove(index)}
|
||||
onMouseEnter={(e) =>
|
||||
(e.currentTarget.style.transform = "scale(1.05)")
|
||||
}
|
||||
onMouseLeave={(e) =>
|
||||
(e.currentTarget.style.transform = "scale(1)")
|
||||
}
|
||||
aria-label="Remove status"
|
||||
style={{
|
||||
width: "36px",
|
||||
height: "36px",
|
||||
borderRadius: "var(--radius-s)",
|
||||
border: "1px solid var(--background-modifier-border)",
|
||||
background: isHovered
|
||||
? "var(--background-modifier-error)"
|
||||
: "var(--background-primary)",
|
||||
color: isHovered
|
||||
? "var(--text-on-accent)"
|
||||
: "var(--text-muted)",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "16px",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
🗑️
|
||||
{isHovered ? "×" : "🗑️"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validation Message */}
|
||||
{!isValid && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "var(--size-2-2)",
|
||||
padding: "var(--size-2-1) var(--size-2-3)",
|
||||
background: "var(--background-modifier-error-hover)",
|
||||
borderRadius: "var(--radius-s)",
|
||||
fontSize: "var(--font-ui-smaller)",
|
||||
color: "var(--text-error)",
|
||||
}}
|
||||
>
|
||||
Status name is required
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@
|
|||
|
||||
.custom-status-text-inputs {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.custom-status-name-input {
|
||||
|
|
|
|||
Loading…
Reference in a new issue