feat(color): add hex code display and click-to-copy functionality
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
Enhances SpoolIcon component with optional showHex prop: - Displays hex color code next to swatch when enabled - Click to copy hex code to clipboard - Shows tooltip with copy hint - Supports both single and multi-color filaments Enabled on detail pages: - Filament show page - Spool show page Added translation keys for copy feedback messages. Closes #33 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,11 @@
|
|||||||
"validationError": "Validation error: {{error}}"
|
"validationError": "Validation error: {{error}}"
|
||||||
},
|
},
|
||||||
"kofi": "Tip me on Ko-fi",
|
"kofi": "Tip me on Ko-fi",
|
||||||
|
"color": {
|
||||||
|
"click_to_copy": "Click to copy",
|
||||||
|
"copied": "Copied {{color}} to clipboard",
|
||||||
|
"copy_failed": "Failed to copy to clipboard"
|
||||||
|
},
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
|
import { CopyOutlined } from "@ant-design/icons";
|
||||||
|
import { message, Tooltip } from "antd";
|
||||||
|
import { useTranslate } from "@refinedev/core";
|
||||||
import "./spoolIcon.css";
|
import "./spoolIcon.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
color: string | { colors: string[]; vertical: boolean };
|
color: string | { colors: string[]; vertical: boolean };
|
||||||
size?: "small" | "large";
|
size?: "small" | "large";
|
||||||
no_margin? : boolean
|
no_margin?: boolean;
|
||||||
|
showHex?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SpoolIcon(props: Readonly<Props>) {
|
export default function SpoolIcon(props: Readonly<Props>) {
|
||||||
|
const t = useTranslate();
|
||||||
let dirClass = "vertical";
|
let dirClass = "vertical";
|
||||||
let cols = [];
|
let cols: string[] = [];
|
||||||
let size = props.size ? props.size : "small";
|
const size = props.size ? props.size : "small";
|
||||||
let no_margin = props.no_margin ? "no-margin" : "";
|
const no_margin = props.no_margin ? "no-margin" : "";
|
||||||
|
|
||||||
if (typeof props.color === "string") {
|
if (typeof props.color === "string") {
|
||||||
cols = [props.color];
|
cols = [props.color];
|
||||||
@@ -19,16 +24,49 @@ export default function SpoolIcon(props: Readonly<Props>) {
|
|||||||
cols = props.color.colors;
|
cols = props.color.colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// Normalize hex codes (remove # if present, uppercase)
|
||||||
|
const normalizedCols = cols.map((col) => col.replace("#", "").toUpperCase());
|
||||||
|
const hexDisplay = normalizedCols.length === 1 ? `#${normalizedCols[0]}` : normalizedCols.map((c) => `#${c}`).join(", ");
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(hexDisplay);
|
||||||
|
message.success(t("color.copied", { color: hexDisplay }));
|
||||||
|
} catch {
|
||||||
|
message.error(t("color.copy_failed"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const swatchContent = (
|
||||||
<div className={"spool-icon " + dirClass + " " + size + " " + no_margin}>
|
<div className={"spool-icon " + dirClass + " " + size + " " + no_margin}>
|
||||||
{cols.map((col) => (
|
{normalizedCols.map((col) => (
|
||||||
<div
|
<div
|
||||||
key={col}
|
key={col}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "#" + col.replace("#", ""),
|
backgroundColor: "#" + col,
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (props.showHex) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={`${t("color.click_to_copy")}: ${hexDisplay}`}>
|
||||||
|
<div
|
||||||
|
className="spool-icon-with-hex"
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
style={{ cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 8 }}
|
||||||
|
>
|
||||||
|
{swatchContent}
|
||||||
|
<span className="hex-code" style={{ fontFamily: "monospace", fontSize: "0.85em", opacity: 0.8 }}>
|
||||||
|
{hexDisplay}
|
||||||
|
<CopyOutlined style={{ marginLeft: 4, fontSize: "0.9em" }} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return swatchContent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,8 +88,7 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
|
|||||||
<Title level={5}>{t("filament.fields.name")}</Title>
|
<Title level={5}>{t("filament.fields.name")}</Title>
|
||||||
<TextField value={record?.name} />
|
<TextField value={record?.name} />
|
||||||
<Title level={5}>{t("filament.fields.color_hex")}</Title>
|
<Title level={5}>{t("filament.fields.color_hex")}</Title>
|
||||||
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin />}
|
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin showHex />}
|
||||||
{record?.color_hex && <TextField value={`#${record?.color_hex}`} />}
|
|
||||||
<Title level={5}>{t("filament.fields.material")}</Title>
|
<Title level={5}>{t("filament.fields.material")}</Title>
|
||||||
<TextField value={record?.material} />
|
<TextField value={record?.material} />
|
||||||
<Title level={5}>{t("filament.fields.price")}</Title>
|
<Title level={5}>{t("filament.fields.price")}</Title>
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
|
|||||||
<Title level={5}>{t("spool.fields.id")}</Title>
|
<Title level={5}>{t("spool.fields.id")}</Title>
|
||||||
<NumberField value={record?.id ?? ""} />
|
<NumberField value={record?.id ?? ""} />
|
||||||
<Title level={5}>{t("spool.fields.filament")}</Title>
|
<Title level={5}>{t("spool.fields.filament")}</Title>
|
||||||
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin />}
|
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin showHex />}
|
||||||
<TextField value={record ? filamentURL(record?.filament) : ""} />
|
<TextField value={record ? filamentURL(record?.filament) : ""} />
|
||||||
<Title level={5}>{t("spool.fields.price")}</Title>
|
<Title level={5}>{t("spool.fields.price")}</Title>
|
||||||
<TextField value={spoolPrice(record)} />
|
<TextField value={spoolPrice(record)} />
|
||||||
|
|||||||
Reference in New Issue
Block a user