Replace project letter icons with color-mapped Lucide icons across all pages
- Map each tablo color to a dedicated Lucide icon (Zap, Leaf, Gem, Flame, etc.) - Apply contrast-aware icon color: dark gray for light backgrounds (yellow, cyan), white for all others - NavigationBar: always use gray icon for sidebar consistency; active nav item uses light purple background (#804EEC tint) - Apply icons to NavigationBar, tablos, tablo-details, tablo (dashboard), and tasks pages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7acb79b3de
commit
bec48cfab6
5 changed files with 235 additions and 43 deletions
|
|
@ -17,12 +17,17 @@ import {
|
|||
CalendarCheckIcon,
|
||||
CalendarIcon,
|
||||
Circle,
|
||||
Compass,
|
||||
ConstructionIcon,
|
||||
CreditCard,
|
||||
// FileTextIcon, // Notes feature temporarily hidden
|
||||
Flame,
|
||||
FolderIcon,
|
||||
Gem,
|
||||
Heart,
|
||||
Kanban,
|
||||
LayersIcon,
|
||||
Leaf,
|
||||
ListTodo,
|
||||
LogOutIcon,
|
||||
MessageCircleIcon,
|
||||
|
|
@ -33,6 +38,10 @@ import {
|
|||
SettingsIcon,
|
||||
Sparkles,
|
||||
SquareKanban,
|
||||
Star,
|
||||
Sun,
|
||||
Waves,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useTablosList } from "../hooks/tablos";
|
||||
import { useState } from "react";
|
||||
|
|
@ -69,7 +78,7 @@ function NavLink({ isActive, children }: NavLinkProps) {
|
|||
"*:data-[ui=notification-badge]:text-xs/6",
|
||||
"*:data-[ui=notification-badge]:font-semibold",
|
||||
isActive
|
||||
? "bg-navbar-darker font-semibold text-gray-900 dark:text-white *:data-[ui=notification-badge]:bg-transparent"
|
||||
? "bg-purple-100 dark:bg-purple-900/30 font-semibold text-[#804EEC] dark:text-purple-300 *:data-[ui=notification-badge]:bg-transparent"
|
||||
: ["font-medium", "text-gray-500 dark:text-gray-300/90 [&:not(:hover)>[data-ui=icon]]:bg-navbar-darker"]
|
||||
)}
|
||||
>
|
||||
|
|
@ -290,6 +299,26 @@ export const SideNavigation = ({ isMobileMenuOpen }: { isMobileMenuOpen: boolean
|
|||
);
|
||||
};
|
||||
|
||||
function getTabloIcon(color: string | null | undefined) {
|
||||
switch (color) {
|
||||
case "bg-blue-500": return Zap;
|
||||
case "bg-green-500": return Leaf;
|
||||
case "bg-purple-500": return Gem;
|
||||
case "bg-red-500": return Flame;
|
||||
case "bg-yellow-500": return Star;
|
||||
case "bg-indigo-500": return Compass;
|
||||
case "bg-pink-500": return Heart;
|
||||
case "bg-teal-500": return Waves;
|
||||
case "bg-orange-500": return Sun;
|
||||
case "bg-cyan-500": return Sparkles;
|
||||
default: return FolderIcon;
|
||||
}
|
||||
}
|
||||
|
||||
function getTabloIconColor(_color: string | null | undefined): string {
|
||||
return "text-gray-700 dark:text-white";
|
||||
}
|
||||
|
||||
function RecentProjectsSection() {
|
||||
const { t } = useTranslation("navigation");
|
||||
const location = useLocation();
|
||||
|
|
@ -309,6 +338,8 @@ function RecentProjectsSection() {
|
|||
<ul className="space-y-0.5">
|
||||
{recentTablos.map((tablo) => {
|
||||
const isActive = location.pathname === `/tablos/${tablo.id}`;
|
||||
const TabloIcon = getTabloIcon(tablo.color);
|
||||
const iconColor = getTabloIconColor(tablo.color);
|
||||
return (
|
||||
<li key={tablo.id}>
|
||||
<RouterLink
|
||||
|
|
@ -316,15 +347,15 @@ function RecentProjectsSection() {
|
|||
className={twMerge(
|
||||
"flex items-center gap-2.5 px-2 py-1.5 rounded-lg text-sm transition-colors",
|
||||
isActive
|
||||
? "bg-navbar-darker text-gray-900 dark:text-white font-semibold"
|
||||
? "bg-purple-100 dark:bg-purple-900/30 text-[#804EEC] dark:text-purple-300 font-semibold"
|
||||
: "text-gray-500 dark:text-gray-300/90 hover:bg-navbar-darker hover:text-gray-900 dark:hover:text-white"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className="w-6 h-6 rounded-full shrink-0 flex items-center justify-center text-xs font-bold text-gray-700 dark:text-white border border-gray-400 dark:border-gray-500/50"
|
||||
className="w-6 h-6 rounded-full shrink-0 flex items-center justify-center border border-gray-400 dark:border-gray-500/50"
|
||||
style={{ backgroundColor: tablo.color ?? "#6b7280" }}
|
||||
>
|
||||
{tablo.name.charAt(0).toUpperCase()}
|
||||
<TabloIcon className={twMerge("w-3.5 h-3.5", iconColor)} />
|
||||
</span>
|
||||
<span className="truncate flex-1">{tablo.name}</span>
|
||||
</RouterLink>
|
||||
|
|
@ -458,7 +489,7 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) {
|
|||
<TypographyLarge
|
||||
className={twMerge(
|
||||
"text-base transition-all duration-300 font-normal",
|
||||
isActive ? "text-gray-900 dark:text-white" : "text-gray-500 dark:text-gray-300/90",
|
||||
isActive ? "text-[#804EEC] dark:text-purple-300" : "text-gray-500 dark:text-gray-300/90",
|
||||
isCollapsed ? "opacity-0 w-0 hidden" : "opacity-100"
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -17,16 +17,26 @@ import {
|
|||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
CircleCheckIcon,
|
||||
Compass,
|
||||
EllipsisVerticalIcon,
|
||||
FileTextIcon,
|
||||
Flame,
|
||||
FolderIcon,
|
||||
Gem,
|
||||
Heart,
|
||||
KanbanIcon,
|
||||
LayoutDashboardIcon,
|
||||
Leaf,
|
||||
ListChecksIcon,
|
||||
MapIcon,
|
||||
MessageCircleIcon,
|
||||
PlusIcon,
|
||||
Sparkles,
|
||||
Star,
|
||||
Sun,
|
||||
UserPlusIcon,
|
||||
Waves,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
|
|
@ -47,6 +57,34 @@ import { useTabloFileNames } from "../hooks/tablo_data";
|
|||
import { useTablosList, useTabloMembers } from "../hooks/tablos";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
// ─── Icon helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
function getTabloIcon(color: string | null | undefined) {
|
||||
switch (color) {
|
||||
case "bg-blue-500": return Zap;
|
||||
case "bg-green-500": return Leaf;
|
||||
case "bg-purple-500": return Gem;
|
||||
case "bg-red-500": return Flame;
|
||||
case "bg-yellow-500": return Star;
|
||||
case "bg-indigo-500": return Compass;
|
||||
case "bg-pink-500": return Heart;
|
||||
case "bg-teal-500": return Waves;
|
||||
case "bg-orange-500": return Sun;
|
||||
case "bg-cyan-500": return Sparkles;
|
||||
default: return FolderIcon;
|
||||
}
|
||||
}
|
||||
|
||||
function getTabloIconColor(color: string | null | undefined): string {
|
||||
switch (color) {
|
||||
case "bg-yellow-500":
|
||||
case "bg-cyan-500":
|
||||
return "text-gray-700";
|
||||
default:
|
||||
return "text-white";
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Status helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
function getStatusConfig(status: string) {
|
||||
|
|
@ -196,6 +234,8 @@ export const TabloDetailsPage = () => {
|
|||
progress,
|
||||
} = getStatusConfig(tablo.status);
|
||||
const isAdmin = tablo.is_admin;
|
||||
const TabloIcon = getTabloIcon(tablo.color);
|
||||
const iconColor = getTabloIconColor(tablo.color);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -205,7 +245,7 @@ export const TabloDetailsPage = () => {
|
|||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className={cn(
|
||||
"w-12 h-12 rounded-lg flex items-center justify-center shrink-0 overflow-hidden text-white font-bold text-xl",
|
||||
"w-12 h-12 rounded-lg flex items-center justify-center shrink-0 overflow-hidden",
|
||||
!tablo.image && (tablo.color || "bg-gray-400"),
|
||||
)}
|
||||
>
|
||||
|
|
@ -216,7 +256,7 @@ export const TabloDetailsPage = () => {
|
|||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
tablo.name.charAt(0).toUpperCase()
|
||||
<TabloIcon className={cn("w-6 h-6", iconColor)} />
|
||||
)}
|
||||
</div>
|
||||
<h1 className="text-xl md:text-3xl font-bold text-foreground">
|
||||
|
|
|
|||
|
|
@ -20,12 +20,23 @@ import { Text } from "@xtablo/ui/components/typography";
|
|||
import {
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
Compass,
|
||||
Flame,
|
||||
FolderIcon,
|
||||
Gem,
|
||||
Heart,
|
||||
Leaf,
|
||||
ListTodo,
|
||||
MessageSquare,
|
||||
Plus,
|
||||
Shield,
|
||||
Sparkles,
|
||||
Star,
|
||||
Sun,
|
||||
Trash2,
|
||||
Users,
|
||||
Waves,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
|
@ -42,6 +53,32 @@ import { DashboardTaskList } from "src/components/DashboardTaskList";
|
|||
import { TaskModal } from "src/components/kanban/TaskModal";
|
||||
import { ProjectCardList } from "src/components/ProjectCardList";
|
||||
|
||||
function getTabloIcon(color: string | null | undefined) {
|
||||
switch (color) {
|
||||
case "bg-blue-500": return Zap;
|
||||
case "bg-green-500": return Leaf;
|
||||
case "bg-purple-500": return Gem;
|
||||
case "bg-red-500": return Flame;
|
||||
case "bg-yellow-500": return Star;
|
||||
case "bg-indigo-500": return Compass;
|
||||
case "bg-pink-500": return Heart;
|
||||
case "bg-teal-500": return Waves;
|
||||
case "bg-orange-500": return Sun;
|
||||
case "bg-cyan-500": return Sparkles;
|
||||
default: return FolderIcon;
|
||||
}
|
||||
}
|
||||
|
||||
function getTabloIconColor(color: string | null | undefined): string {
|
||||
switch (color) {
|
||||
case "bg-yellow-500":
|
||||
case "bg-cyan-500":
|
||||
return "text-gray-700";
|
||||
default:
|
||||
return "text-white";
|
||||
}
|
||||
}
|
||||
|
||||
export const TabloPage = () => {
|
||||
const { t } = useTranslation(["pages", "common"]);
|
||||
|
||||
|
|
@ -354,6 +391,8 @@ export const TabloPage = () => {
|
|||
|
||||
const renderTablo = (tablo: UserTablo) => {
|
||||
const isAdmin = tablo.is_admin;
|
||||
const TabloIcon = getTabloIcon(tablo.color);
|
||||
const iconColor = getTabloIconColor(tablo.color);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -392,9 +431,7 @@ export const TabloPage = () => {
|
|||
tablo.color || "bg-gray-400"
|
||||
} flex items-center justify-center`}
|
||||
>
|
||||
<h3 className="text-white font-bold text-xl text-center px-4">
|
||||
{tablo.name}
|
||||
</h3>
|
||||
<TabloIcon className={`w-12 h-12 ${iconColor}`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,23 @@ import type { UserTablo } from "@xtablo/shared/types/tablos.types";
|
|||
import { LoadingSpinner } from "@ui/components/LoadingSpinner";
|
||||
import {
|
||||
CalendarIcon,
|
||||
Compass,
|
||||
FilterIcon,
|
||||
Flame,
|
||||
FolderIcon,
|
||||
Gem,
|
||||
Grid3x3Icon,
|
||||
Heart,
|
||||
Leaf,
|
||||
ListIcon,
|
||||
PlusIcon,
|
||||
SearchIcon,
|
||||
Sparkles,
|
||||
Star,
|
||||
Sun,
|
||||
Trash2Icon,
|
||||
Waves,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
|
@ -17,6 +28,34 @@ import { CreateTabloModal } from "../components/CreateTabloModal";
|
|||
import { DeleteTabloModal } from "../components/DeleteTabloModal";
|
||||
import { useCreateTablo, useDeleteTablo, useTablosList } from "../hooks/tablos";
|
||||
|
||||
// ─── Icon helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
function getTabloIcon(color: string | null | undefined) {
|
||||
switch (color) {
|
||||
case "bg-blue-500": return Zap;
|
||||
case "bg-green-500": return Leaf;
|
||||
case "bg-purple-500": return Gem;
|
||||
case "bg-red-500": return Flame;
|
||||
case "bg-yellow-500": return Star;
|
||||
case "bg-indigo-500": return Compass;
|
||||
case "bg-pink-500": return Heart;
|
||||
case "bg-teal-500": return Waves;
|
||||
case "bg-orange-500": return Sun;
|
||||
case "bg-cyan-500": return Sparkles;
|
||||
default: return FolderIcon;
|
||||
}
|
||||
}
|
||||
|
||||
function getTabloIconColor(color: string | null | undefined): string {
|
||||
switch (color) {
|
||||
case "bg-yellow-500":
|
||||
case "bg-cyan-500":
|
||||
return "text-gray-700";
|
||||
default:
|
||||
return "text-white";
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Status helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
function getStatusConfig(status: string) {
|
||||
|
|
@ -63,6 +102,8 @@ function TabloCard({
|
|||
}) {
|
||||
const { t } = useTranslation("pages");
|
||||
const { label, badgeClass, progress } = getStatusConfig(tablo.status);
|
||||
const TabloIcon = getTabloIcon(tablo.color);
|
||||
const iconColor = getTabloIconColor(tablo.color);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -87,14 +128,14 @@ function TabloCard({
|
|||
<div className="flex items-center gap-3 mb-4">
|
||||
<div
|
||||
className={cn(
|
||||
"w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden text-white font-bold text-base",
|
||||
"w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden",
|
||||
!tablo.image && (tablo.color || "bg-gray-400")
|
||||
)}
|
||||
>
|
||||
{tablo.image ? (
|
||||
<img src={tablo.image} alt={tablo.name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
tablo.name.charAt(0).toUpperCase()
|
||||
<TabloIcon className={cn("w-4 h-4", iconColor)} />
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-gray-900 dark:text-gray-100 flex-1 line-clamp-2">
|
||||
|
|
@ -141,6 +182,8 @@ function TabloRow({
|
|||
onDelete: (id: string) => void;
|
||||
}) {
|
||||
const { label, badgeClass, progress } = getStatusConfig(tablo.status);
|
||||
const TabloIcon = getTabloIcon(tablo.color);
|
||||
const iconColor = getTabloIconColor(tablo.color);
|
||||
|
||||
return (
|
||||
<tr
|
||||
|
|
@ -151,14 +194,14 @@ function TabloRow({
|
|||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
"w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden text-white font-bold text-sm",
|
||||
"w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden",
|
||||
!tablo.image && (tablo.color || "bg-gray-400")
|
||||
)}
|
||||
>
|
||||
{tablo.image ? (
|
||||
<img src={tablo.image} alt={tablo.name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
tablo.name.charAt(0).toUpperCase()
|
||||
<TabloIcon className={cn("w-4 h-4", iconColor)} />
|
||||
)}
|
||||
</div>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100 truncate">{tablo.name}</span>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,14 @@ import {
|
|||
CalendarIcon,
|
||||
CircleCheckIcon,
|
||||
CircleIcon,
|
||||
Compass,
|
||||
EllipsisVerticalIcon,
|
||||
Flame,
|
||||
FolderIcon,
|
||||
Gem,
|
||||
Heart,
|
||||
KanbanIcon,
|
||||
Leaf,
|
||||
ListIcon,
|
||||
ListTodo,
|
||||
MessageSquareIcon,
|
||||
|
|
@ -23,7 +29,12 @@ import {
|
|||
PaperclipIcon,
|
||||
PlusIcon,
|
||||
Settings2Icon,
|
||||
Sparkles,
|
||||
Star,
|
||||
Sun,
|
||||
UserIcon,
|
||||
Waves,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
|
@ -55,6 +66,33 @@ const columnTitles = {
|
|||
done: "Terminé",
|
||||
};
|
||||
|
||||
function getTabloIcon(color: string | null | undefined) {
|
||||
switch (color) {
|
||||
case "bg-blue-500": return Zap;
|
||||
case "bg-green-500": return Leaf;
|
||||
case "bg-purple-500": return Gem;
|
||||
case "bg-red-500": return Flame;
|
||||
case "bg-yellow-500": return Star;
|
||||
case "bg-indigo-500": return Compass;
|
||||
case "bg-pink-500": return Heart;
|
||||
case "bg-teal-500": return Waves;
|
||||
case "bg-orange-500": return Sun;
|
||||
case "bg-cyan-500": return Sparkles;
|
||||
default: return FolderIcon;
|
||||
}
|
||||
}
|
||||
|
||||
// Light backgrounds need dark text; dark/saturated backgrounds use white
|
||||
function getTabloIconColor(color: string | null | undefined): string {
|
||||
switch (color) {
|
||||
case "bg-yellow-500":
|
||||
case "bg-cyan-500":
|
||||
return "text-gray-700";
|
||||
default:
|
||||
return "text-white";
|
||||
}
|
||||
}
|
||||
|
||||
export function TasksPage() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation(["pages", "common"]);
|
||||
|
|
@ -502,26 +540,25 @@ export function TasksPage() {
|
|||
*/}
|
||||
|
||||
{/* Tablo row */}
|
||||
{taskWithTablo.tablos && (
|
||||
<div className="flex items-center mb-3 border-b border-dashed border-[#D0D5DD] dark:border-gray-600 pb-3">
|
||||
<div
|
||||
className={twMerge(
|
||||
"w-5 h-5 rounded-[5px] p-1 mr-2 flex items-center justify-center shrink-0",
|
||||
taskWithTablo.tablos.color ||
|
||||
"bg-gray-400",
|
||||
)}
|
||||
>
|
||||
<span className="text-[8px] font-bold text-white leading-none">
|
||||
{taskWithTablo.tablos.name
|
||||
.charAt(0)
|
||||
.toUpperCase()}
|
||||
{taskWithTablo.tablos && (() => {
|
||||
const TabloIcon = getTabloIcon(taskWithTablo.tablos.color);
|
||||
const iconColor = getTabloIconColor(taskWithTablo.tablos.color);
|
||||
return (
|
||||
<div className="flex items-center mb-3 border-b border-dashed border-[#D0D5DD] dark:border-gray-600 pb-3">
|
||||
<div
|
||||
className={twMerge(
|
||||
"w-5 h-5 rounded-[5px] mr-2 flex items-center justify-center shrink-0",
|
||||
taskWithTablo.tablos.color || "bg-gray-400",
|
||||
)}
|
||||
>
|
||||
<TabloIcon className={twMerge("w-3 h-3", iconColor)} />
|
||||
</div>
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400 truncate">
|
||||
{taskWithTablo.tablos.name}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-600 dark:text-gray-400 truncate">
|
||||
{taskWithTablo.tablos.name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Footer: stats + assignee */}
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -664,17 +701,21 @@ export function TasksPage() {
|
|||
|
||||
{/* Project */}
|
||||
<td className="px-4 md:px-6 py-3">
|
||||
{taskWithTablo.tablos ? (
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div className={twMerge(
|
||||
"w-5 h-5 rounded-[4px] shrink-0 flex items-center justify-center text-[8px] font-bold text-white",
|
||||
taskWithTablo.tablos.color || "bg-gray-400"
|
||||
)}>
|
||||
{taskWithTablo.tablos.name.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<span className="truncate">{taskWithTablo.tablos.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
{taskWithTablo.tablos ? (() => {
|
||||
const TabloIcon = getTabloIcon(taskWithTablo.tablos.color);
|
||||
const iconColor = getTabloIconColor(taskWithTablo.tablos.color);
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div className={twMerge(
|
||||
"w-5 h-5 rounded-[4px] shrink-0 flex items-center justify-center",
|
||||
taskWithTablo.tablos.color || "bg-gray-400"
|
||||
)}>
|
||||
<TabloIcon className={twMerge("w-3 h-3", iconColor)} />
|
||||
</div>
|
||||
<span className="truncate">{taskWithTablo.tablos.name}</span>
|
||||
</div>
|
||||
);
|
||||
})() : (
|
||||
<span className="text-sm text-gray-400">—</span>
|
||||
)}
|
||||
</td>
|
||||
|
|
|
|||
Loading…
Reference in a new issue