+
@@ -55,6 +60,11 @@
}
"
>
+
{{ label.label }}
@@ -127,14 +137,14 @@
return filtered;
});
- const createAndAdd = async (name: string) => {
+ const createAndAdd = async (name: string, color = "") => {
if (name.length > 50) {
toast.error(t("components.label.create_modal.toast.label_name_too_long"));
return;
}
const { error, data } = await api.labels.create({
name,
- color: "", // Future!
+ color,
description: "",
});
diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts
index e16d753b..da315c83 100644
--- a/frontend/lib/api/types/data-contracts.ts
+++ b/frontend/lib/api/types/data-contracts.ts
@@ -638,6 +638,7 @@ export interface LabelCreate {
}
export interface LabelOut {
+ color: string;
createdAt: Date | string;
description: string;
id: string;
@@ -646,6 +647,7 @@ export interface LabelOut {
}
export interface LabelSummary {
+ color: string;
createdAt: Date | string;
description: string;
id: string;
diff --git a/frontend/lib/utils.ts b/frontend/lib/utils.ts
index 365058ce..13aac2ad 100644
--- a/frontend/lib/utils.ts
+++ b/frontend/lib/utils.ts
@@ -4,3 +4,35 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+
+/**
+ * Returns either '#000' or '#fff' depending on which has better contrast with the given background color.
+ * Accepts hex (#RRGGBB or #RGB) or rgb(a) strings.
+ */
+export function getContrastTextColor(bgColor: string): string {
+ let r = 0;
+ let g = 0;
+ let b = 0;
+ if (bgColor.startsWith("#")) {
+ let hex = bgColor.slice(1);
+ if (hex.length === 3) {
+ hex = hex
+ .split("")
+ .map(x => x + x)
+ .join("");
+ }
+ r = parseInt(hex.slice(0, 2), 16);
+ g = parseInt(hex.slice(2, 4), 16);
+ b = parseInt(hex.slice(4, 6), 16);
+ } else if (bgColor.startsWith("rgb")) {
+ const match = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
+ if (match) {
+ r = parseInt(match[1]);
+ g = parseInt(match[2]);
+ b = parseInt(match[3]);
+ }
+ }
+ // Calculate luminance
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+ return luminance > 0.5 ? "#000" : "#fff";
+}
diff --git a/frontend/locales/en.json b/frontend/locales/en.json
index aaa08b46..67ce4da5 100644
--- a/frontend/locales/en.json
+++ b/frontend/locales/en.json
@@ -24,6 +24,13 @@
"new_version_available_link": "Click here to view the release notes"
}
},
+ "color_selector": {
+ "color": "Color",
+ "clear": "Clear color",
+ "no_color": "No color",
+ "no_color_selected": "No color selected",
+ "randomize": "Randomize color"
+ },
"form": {
"password": {
"toggle_show": "Toggle Password Show"
@@ -144,7 +151,8 @@
"create_failed": "Couldn't create label",
"create_success": "Label created",
"label_name_too_long": "Label name must not be longer than 50 characters"
- }
+ },
+ "label_color": "Label Color"
},
"selector": {
"select_labels": "Select Labels"
diff --git a/frontend/pages/label/[id].vue b/frontend/pages/label/[id].vue
index bffa698d..d60aaacb 100644
--- a/frontend/pages/label/[id].vue
+++ b/frontend/pages/label/[id].vue
@@ -10,6 +10,8 @@
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
+ import ColorSelector from "@/components/Form/ColorSelector.vue";
+ import { getContrastTextColor } from "~/lib/utils";
definePageMeta({
middleware: ["auth"],
@@ -128,7 +130,12 @@
:label="$t('components.label.create_modal.label_description')"
:max-length="255"
/>
-
+
@@ -144,7 +151,12 @@