feat: add board background patterns (dots, grid, gradient) with settings dropdown
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef, useEffect, useCallback } from "react";
|
import React, { useState, useRef, useEffect, useCallback } from "react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
@@ -30,6 +30,30 @@ function findColumnByCardId(board: Board, cardId: string) {
|
|||||||
return board.columns.find((col) => col.cardIds.includes(cardId));
|
return board.columns.find((col) => col.cardIds.includes(cardId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBoardBackground(board: Board): React.CSSProperties {
|
||||||
|
const bg = board.settings.background;
|
||||||
|
if (bg === "dots") {
|
||||||
|
return {
|
||||||
|
backgroundImage: `radial-gradient(circle, currentColor 1px, transparent 1px)`,
|
||||||
|
backgroundSize: "20px 20px",
|
||||||
|
color: "oklch(50% 0 0 / 5%)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (bg === "grid") {
|
||||||
|
return {
|
||||||
|
backgroundImage: `linear-gradient(currentColor 1px, transparent 1px), linear-gradient(90deg, currentColor 1px, transparent 1px)`,
|
||||||
|
backgroundSize: "24px 24px",
|
||||||
|
color: "oklch(50% 0 0 / 5%)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (bg === "gradient") {
|
||||||
|
return {
|
||||||
|
background: `linear-gradient(135deg, ${board.color}08, ${board.color}03, transparent)`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
export function BoardView() {
|
export function BoardView() {
|
||||||
const board = useBoardStore((s) => s.board);
|
const board = useBoardStore((s) => s.board);
|
||||||
const addColumn = useBoardStore((s) => s.addColumn);
|
const addColumn = useBoardStore((s) => s.addColumn);
|
||||||
@@ -296,7 +320,7 @@ export function BoardView() {
|
|||||||
items={columnIds}
|
items={columnIds}
|
||||||
strategy={horizontalListSortingStrategy}
|
strategy={horizontalListSortingStrategy}
|
||||||
>
|
>
|
||||||
<div className="flex h-full overflow-x-auto" style={{ gap: `calc(1.5rem * var(--density-factor))`, padding: `calc(1.5rem * var(--density-factor))` }}>
|
<div className="flex h-full overflow-x-auto" style={{ gap: `calc(1.5rem * var(--density-factor))`, padding: `calc(1.5rem * var(--density-factor))`, ...getBoardBackground(board) }}>
|
||||||
{board.columns.map((column) => (
|
{board.columns.map((column) => (
|
||||||
<KanbanColumn
|
<KanbanColumn
|
||||||
key={column.id}
|
key={column.id}
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import { useState, useRef, useEffect, useCallback } from "react";
|
import { useState, useRef, useEffect, useCallback } from "react";
|
||||||
import { ArrowLeft, Settings, Search, Undo2, Redo2 } from "lucide-react";
|
import { ArrowLeft, Settings, Search, Undo2, Redo2, SlidersHorizontal } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useAppStore } from "@/stores/app-store";
|
import { useAppStore } from "@/stores/app-store";
|
||||||
import { useBoardStore } from "@/stores/board-store";
|
import { useBoardStore } from "@/stores/board-store";
|
||||||
|
|
||||||
@@ -158,6 +167,37 @@ export function TopBar() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{isBoardView && board && (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
className="text-pylon-text-secondary hover:text-pylon-text"
|
||||||
|
>
|
||||||
|
<SlidersHorizontal className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuSub>
|
||||||
|
<DropdownMenuSubTrigger>Background</DropdownMenuSubTrigger>
|
||||||
|
<DropdownMenuSubContent>
|
||||||
|
{(["none", "dots", "grid", "gradient"] as const).map((bg) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={bg}
|
||||||
|
onClick={() => useBoardStore.getState().updateBoardSettings({ ...board.settings, background: bg })}
|
||||||
|
>
|
||||||
|
{bg.charAt(0).toUpperCase() + bg.slice(1)}
|
||||||
|
{board.settings.background === bg && (
|
||||||
|
<span className="ml-auto text-pylon-accent">*</span>
|
||||||
|
)}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
</DropdownMenuSub>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
{savingStatus && (
|
{savingStatus && (
|
||||||
<span className="mr-2 font-mono text-xs text-pylon-text-secondary">
|
<span className="mr-2 font-mono text-xs text-pylon-text-secondary">
|
||||||
{savingStatus}
|
{savingStatus}
|
||||||
|
|||||||
Reference in New Issue
Block a user