UNPKG

react-custom-kanban-board

Version:

A customizable Kanban board component for React with advanced features like search, filtering, and WIP limits.

361 lines (360 loc) 28.7 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); var jsx_runtime_1 = require("react/jsx-runtime"); var react_1 = require("react"); var framer_motion_1 = require("framer-motion"); require("./KanbanBoard.css"); // Enhanced Icon Components var DeleteIcon = function () { return ((0, jsx_runtime_1.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M19.5 5.5L18.8803 15.5251C18.7219 18.0864 18.6428 19.3671 18.0008 20.2879C17.6833 20.7431 17.2747 21.1273 16.8007 21.416C15.8421 22 14.559 22 12 22C9.44098 22 8.15402 22 7.19926 21.4159C6.72521 21.1271 6.31729 20.743 6.00058 20.2879C5.35858 19.3671 5.27812 18.0863 5.11963 15.525L4.5 5.5", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }), (0, jsx_runtime_1.jsx)("path", { d: "M3 5.5H21M16.0557 5.5L15.3731 4.09173C14.9196 3.15626 14.6928 2.68852 14.3017 2.39681C14.215 2.3321 14.1231 2.27454 14.027 2.2247C13.5939 2 13.0741 2 12.0345 2C10.9688 2 10.436 2 9.99568 2.23412C9.89809 2.28601 9.80498 2.3459 9.71729 2.41317C9.32163 2.7167 9.10062 3.20155 8.6586 4.17126L8.05292 5.5", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" })] })); }; var EditIcon = function () { return ((0, jsx_runtime_1.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M14.2454 5.05L15.9954 3.3C16.3954 2.9 16.9954 2.9 17.3954 3.3L20.6954 6.6C21.0954 7 21.0954 7.6 20.6954 8L8.69543 20C8.49543 20.2 8.19543 20.3 7.99543 20.3L3.69543 21L4.39543 16.7C4.39543 16.5 4.49543 16.2 4.69543 16L14.2454 5.05Z", stroke: "currentColor", strokeWidth: "1.8", strokeMiterlimit: "10", strokeLinecap: "round", strokeLinejoin: "round" }), (0, jsx_runtime_1.jsx)("path", { d: "M12.2954 7L17.2954 12", stroke: "currentColor", strokeWidth: "1.8", strokeMiterlimit: "10", strokeLinecap: "round", strokeLinejoin: "round" })] })); }; var SearchIcon = function () { return ((0, jsx_runtime_1.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M11 19C15.4183 19 19 15.4183 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11C3 15.4183 6.58172 19 11 19Z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }), (0, jsx_runtime_1.jsx)("path", { d: "M21 21L16.65 16.65", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })] })); }; // New collapse/expand icon with animation var CollapseExpandIcon = function (_a) { var isExpanded = _a.isExpanded; return ((0, jsx_runtime_1.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", style: { transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)", transition: "transform 0.3s ease", }, "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { d: "M7 10L12 15L17 10", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })); }; var LoadingSpinner = function () { return ((0, jsx_runtime_1.jsxs)("div", { className: "loading-spinner", children: [(0, jsx_runtime_1.jsx)("div", { className: "spinner" }), (0, jsx_runtime_1.jsx)("p", { children: "Loading board..." })] })); }; var KanbanBoard = function (_a) { var columns = _a.columns, columnForAddCard = _a.columnForAddCard, initialCards = _a.initialCards, onCardMove = _a.onCardMove, onCardEdit = _a.onCardEdit, onCardDelete = _a.onCardDelete, onTaskAddedCallback = _a.onTaskAddedCallback, renderCard = _a.renderCard, renderAvatar = _a.renderAvatar, renderAddCard = _a.renderAddCard, _b = _a.isLoading, isLoading = _b === void 0 ? false : _b, loadingComponent = _a.loadingComponent, _c = _a.emptyColumnMessage, emptyColumnMessage = _c === void 0 ? "No cards yet" : _c, _d = _a.enableSearch, enableSearch = _d === void 0 ? false : _d, _e = _a.enableFiltering, enableFiltering = _e === void 0 ? false : _e, _f = _a.filterConfigs, filterConfigs = _f === void 0 ? [] : _f, onFilterChange = _a.onFilterChange, renderSearchInput = _a.renderSearchInput, renderFilterMenu = _a.renderFilterMenu; var _g = (0, react_1.useState)(initialCards), cards = _g[0], setCards = _g[1]; var _h = (0, react_1.useState)(""), searchTerm = _h[0], setSearchTerm = _h[1]; var _j = (0, react_1.useState)({}), filters = _j[0], setFilters = _j[1]; var _k = (0, react_1.useState)(initialCards), filteredCards = _k[0], setFilteredCards = _k[1]; var boardRef = (0, react_1.useRef)(null); (0, react_1.useEffect)(function () { setCards(initialCards); }, [initialCards]); // Handle filter change var handleFilterChange = function (field, value) { var _a; var newFilters = __assign(__assign({}, filters), (_a = {}, _a[field] = value || null, _a)); // If value is empty, remove the filter if (!value) { delete newFilters[field]; } setFilters(newFilters); onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange(newFilters); // Notify parent component if callback exists }; // Clear all filters var clearAllFilters = function () { setFilters({}); onFilterChange === null || onFilterChange === void 0 ? void 0 : onFilterChange({}); }; (0, react_1.useEffect)(function () { var result = __spreadArray([], cards, true); if (searchTerm) { result = result.filter(function (card) { return card.title.toLowerCase().includes(searchTerm.toLowerCase()); }); } // Apply all active filters dynamically Object.entries(filters).forEach(function (_a) { var field = _a[0], value = _a[1]; if (value) { result = result.filter(function (card) { // Handle special case for tags which is an array if (field === "tags" && Array.isArray(card.tags)) { return card.tags.includes(value); } // Standard field comparison return card[field] === value; }); } }); setFilteredCards(result); }, [searchTerm, filters, cards]); if (isLoading) { return ((0, jsx_runtime_1.jsx)("div", { className: "kanban-board-container", children: loadingComponent || (0, jsx_runtime_1.jsx)(LoadingSpinner, {}) })); } return ((0, jsx_runtime_1.jsxs)("div", { className: "kanban-board-container", ref: boardRef, children: [(enableSearch || (enableFiltering && filterConfigs.length > 0)) && ((0, jsx_runtime_1.jsxs)("div", { className: "kanban-search", children: [enableSearch && ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: renderSearchInput ? (renderSearchInput(searchTerm, setSearchTerm)) : ((0, jsx_runtime_1.jsxs)("div", { className: "search-input-wrapper", children: [(0, jsx_runtime_1.jsx)(SearchIcon, {}), (0, jsx_runtime_1.jsx)("input", { type: "text", value: searchTerm, onChange: function (e) { return setSearchTerm(e.target.value); }, placeholder: "Search cards...", "aria-label": "Search cards" }), searchTerm && ((0, jsx_runtime_1.jsx)("button", { className: "clear-search", onClick: function () { return setSearchTerm(""); }, "aria-label": "Clear search", children: "\u00D7" }))] })) })), enableFiltering && filterConfigs.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "filter-controls", children: [filterConfigs.map(function (config) { return ((0, jsx_runtime_1.jsx)("div", { className: "filter-select-container", children: renderFilterMenu ? (renderFilterMenu(config, filters[config.field] || null, handleFilterChange)) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("label", { htmlFor: "filter-".concat(config.field), className: "filter-label", children: [config.label, ":"] }), (0, jsx_runtime_1.jsxs)("select", { id: "filter-".concat(config.field), value: filters[config.field] || "", onChange: function (e) { return handleFilterChange(config.field, e.target.value || null); }, "aria-label": "Filter by ".concat(config.label), children: [(0, jsx_runtime_1.jsxs)("option", { value: "", children: ["All ", config.label, "s"] }), config.options.map(function (option) { return ((0, jsx_runtime_1.jsx)("option", { value: option.value, children: option.label }, option.value)); })] })] })) }, config.field)); }), (0, jsx_runtime_1.jsx)("button", { className: "clear-filters", onClick: clearAllFilters, disabled: Object.keys(filters).length === 0, "aria-label": "Clear filters", children: "Clear Filters" })] }))] })), (0, jsx_runtime_1.jsx)("div", { className: "kanban-board", children: columns.map(function (column) { return ((0, jsx_runtime_1.jsx)(ColumnComponent, { title: column.title, column: column.key, cards: cards, filteredCards: filteredCards.filter(function (card) { return card.status === column.key; }), setCards: setCards, color: column.color, limit: column.limit, onCardMove: onCardMove, onCardEdit: onCardEdit, onCardDelete: onCardDelete, renderCard: renderCard, renderAvatar: renderAvatar, renderAddCard: renderAddCard, onTaskAddedCallback: onTaskAddedCallback, columnForAddCard: columnForAddCard, emptyColumnMessage: emptyColumnMessage }, column.key)); }) })] })); }; var ColumnComponent = function (_a) { var title = _a.title, column = _a.column, cards = _a.cards, filteredCards = _a.filteredCards, setCards = _a.setCards, columnForAddCard = _a.columnForAddCard, color = _a.color, limit = _a.limit, onCardMove = _a.onCardMove, onCardEdit = _a.onCardEdit, onCardDelete = _a.onCardDelete, renderCard = _a.renderCard, renderAvatar = _a.renderAvatar, renderAddCard = _a.renderAddCard, onTaskAddedCallback = _a.onTaskAddedCallback, emptyColumnMessage = _a.emptyColumnMessage; var _b = (0, react_1.useState)(false), active = _b[0], setActive = _b[1]; var _c = (0, react_1.useState)(null), editingCardId = _c[0], setEditingCardId = _c[1]; var _d = (0, react_1.useState)(""), newTitle = _d[0], setNewTitle = _d[1]; var _e = (0, react_1.useState)({}), expandedCards = _e[0], setExpandedCards = _e[1]; var columnRef = (0, react_1.useRef)(null); var isLimitExceeded = limit !== undefined && filteredCards.length > limit; var columnStyle = { backgroundColor: color, }; var handleDragStart = function (e, card) { e.dataTransfer.setData("cardId", card.id); // Create and set custom drag image var dragPreview = document.createElement("div"); dragPreview.className = "card-drag-preview"; dragPreview.textContent = card.title.length > 25 ? card.title.substring(0, 25) + "..." : card.title; document.body.appendChild(dragPreview); e.dataTransfer.setDragImage(dragPreview, 20, 20); // Remove after drag ends setTimeout(function () { if (document.body.contains(dragPreview)) { document.body.removeChild(dragPreview); } }, 0); }; var toggleExpand = function (id) { setExpandedCards(function (prev) { var _a; return (__assign(__assign({}, prev), (_a = {}, _a[id] = !prev[id], _a))); }); }; var handleDragEnd = function (e) { var cardId = e.dataTransfer.getData("cardId"); setActive(false); clearHighlights(); // Get all drop indicators in the current column var indicators = getIndicators(); // Find the nearest indicator based on mouse position var element = getNearestIndicator(e, indicators).element; // Get the ID of the card before which to insert the dragged card var before = element.dataset.before || "-1"; // Only proceed if we're not trying to place the card in its original position if (before !== cardId) { var copy = __spreadArray([], cards, true); // Find the card being dragged var cardToMove = copy.find(function (c) { return c.id === cardId; }); if (!cardToMove) return; // Get the current status of the card var currentStatus = cardToMove.status; var isSameColumn = currentStatus === column; // If moving to a different column, check column limits if (!isSameColumn && limit !== undefined) { var columnCardCount = copy.filter(function (c) { return c.status === column && c.id !== cardId; }).length; // If moving to this column would exceed the limit if (columnCardCount >= limit) { showWipLimitNotification(columnRef.current); return; } } // Remove the card from its original position copy = copy.filter(function (c) { return c.id !== cardId; }); // Create updated card with new status if column changed var updatedCard = isSameColumn ? cardToMove : __assign(__assign({}, cardToMove), { status: column }); // Determine if we're adding the card to the end of the column var moveToBack = before === "-1"; if (moveToBack) { // Add the card to the end of the array copy.push(updatedCard); } else { // Find the index where to insert the card var insertAtIndex = copy.findIndex(function (el) { return el.id === before; }); if (insertAtIndex === -1) return; // Invalid index // Insert the card at the proper position copy.splice(insertAtIndex, 0, updatedCard); } // Update state and trigger callback if the column changed setCards(copy); if (!isSameColumn) { onCardMove === null || onCardMove === void 0 ? void 0 : onCardMove(cardId, column); } } }; var showWipLimitNotification = function (columnEl) { if (!columnEl) return; var notification = document.createElement("div"); notification.className = "wip-limit-notification"; notification.textContent = "WIP limit (".concat(limit, ") reached!"); columnEl.appendChild(notification); setTimeout(function () { notification.classList.add("show"); setTimeout(function () { notification.classList.remove("show"); setTimeout(function () { if (columnEl.contains(notification)) { columnEl.removeChild(notification); } }, 300); }, 2000); }, 10); }; var handleDragOver = function (e) { e.preventDefault(); highlightIndicator(e); setActive(true); }; var clearHighlights = function (els) { var indicators = els || getIndicators(); indicators.forEach(function (i) { i.style.opacity = "0"; }); }; var highlightIndicator = function (e) { var indicators = getIndicators(); clearHighlights(indicators); var el = getNearestIndicator(e, indicators); el.element.style.opacity = "1"; }; // Improved getNearestIndicator for better accuracy in finding drop targets var getNearestIndicator = function (e, indicators) { var DISTANCE_OFFSET = 50; // Calculate the position of each indicator relative to mouse var el = indicators.reduce(function (closest, child) { var box = child.getBoundingClientRect(); var offset = e.clientY - (box.top + DISTANCE_OFFSET); // If this indicator is closer to the mouse than our current closest if (offset < 0 && offset > closest.offset) { return { offset: offset, element: child }; } else { return closest; } }, { offset: Number.NEGATIVE_INFINITY, element: indicators[indicators.length - 1], }); return el; }; var handleDragLeave = function () { clearHighlights(); setActive(false); }; var getIndicators = function () { return Array.from(document.querySelectorAll("[data-column=\"".concat(column, "\"]"))); }; var handleDeleteCard = function (cardId) { if (onCardDelete) onCardDelete(cardId); setCards(function (prevCards) { return prevCards.filter(function (card) { return card.id !== cardId; }); }); }; var handleEditClick = function (cardId, currentTitle) { setEditingCardId(cardId); setNewTitle(currentTitle); }; var handleSaveEdit = function (cardId) { if (newTitle.trim() === "") return; if (onCardEdit) onCardEdit(cardId, newTitle); setCards(function (prevCards) { return prevCards.map(function (card) { return card.id === cardId ? __assign(__assign({}, card), { title: newTitle }) : card; }); }); setEditingCardId(null); }; var handleEditChange = function (e) { setNewTitle(e.target.value); }; var handleKeyDown = function (e, cardId) { if (e.key === "Enter") { handleSaveEdit(cardId); } else if (e.key === "Escape") { setEditingCardId(null); } }; return ((0, jsx_runtime_1.jsxs)("div", { className: "kanban-column ".concat(active ? "active" : "", " ").concat(isLimitExceeded ? "limit-exceeded" : ""), onDrop: handleDragEnd, onDragOver: handleDragOver, onDragLeave: handleDragLeave, ref: columnRef, "data-testid": "column-".concat(column), children: [(0, jsx_runtime_1.jsxs)("div", { className: "column-title", style: columnStyle, children: [(0, jsx_runtime_1.jsx)("div", { className: "column-title-text", children: title }), (0, jsx_runtime_1.jsx)("div", { className: "column-counter-container", children: (0, jsx_runtime_1.jsxs)("span", { className: "counter ".concat(isLimitExceeded ? "exceeded" : ""), children: [filteredCards.length, limit !== undefined && "/".concat(limit)] }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "column-content ".concat(active ? "active" : ""), children: [filteredCards.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "column-empty-state", children: emptyColumnMessage })) : ((0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: filteredCards.map(function (card) { return ((0, jsx_runtime_1.jsxs)("div", { className: "motion-container", children: [(0, jsx_runtime_1.jsx)(DropIndicator, { beforeId: card.id, column: column }), (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { layout: true, initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, scale: 0.8 }, transition: { duration: 0.2 }, "data-card-id": card.id, className: "motion-card-wrapper", children: editingCardId === card.id ? ((0, jsx_runtime_1.jsx)("div", { className: "card-edit", children: (0, jsx_runtime_1.jsx)("input", { autoFocus: true, type: "text", value: newTitle, onChange: handleEditChange, onBlur: function () { return handleSaveEdit(card.id); }, onKeyDown: function (e) { return handleKeyDown(e, card.id); }, "aria-label": "Edit card title" }) })) : renderCard ? (renderCard(card, handleDragStart, expandedCards[card.id], toggleExpand)) : ((0, jsx_runtime_1.jsx)(DefaultCard, __assign({}, card, { handleDragStart: handleDragStart, renderAvatar: renderAvatar, isExpanded: expandedCards[card.id], toggleExpand: function () { return toggleExpand(card.id); }, onDelete: function () { return handleDeleteCard(card.id); }, onEdit: function () { return handleEditClick(card.id, card.title); } }))) })] }, card.id)); }) })), (0, jsx_runtime_1.jsx)(DropIndicator, { beforeId: -1, column: column }), (0, jsx_runtime_1.jsx)("div", { className: "add-card-container", children: columnForAddCard === column ? (renderAddCard ? (renderAddCard(column, setCards)) : ((0, jsx_runtime_1.jsx)(DefaultAddCard, { column: column, setCards: setCards, onTaskAddedCallback: onTaskAddedCallback }))) : null })] })] })); }; // Enhanced default card with better styling and icon positioning var DefaultCard = function (_a) { var title = _a.title, avatarPath = _a.avatarPath, id = _a.id, status = _a.status, priority = _a.priority, dueDate = _a.dueDate, tags = _a.tags, description = _a.description, handleDragStart = _a.handleDragStart, renderAvatar = _a.renderAvatar, isExpanded = _a.isExpanded, toggleExpand = _a.toggleExpand, onDelete = _a.onDelete, onEdit = _a.onEdit; var hasDetails = priority || dueDate || description || (tags && tags.length > 0); // Priority color mapping var priorityColors = { High: "#F87171", Medium: "#FBBF24", Low: "#34D399", }; // Get border color based on priority var borderColor = priority ? priorityColors[priority] : undefined; return ((0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { layout: true, layoutId: id, className: "card ".concat(isExpanded ? "expanded" : ""), draggable: true, onDragStart: function (e) { return handleDragStart(e, { title: title, id: id, status: status }); }, style: { borderLeft: borderColor ? "4px solid ".concat(borderColor) : undefined, }, whileHover: { y: -2, boxShadow: "0 6px 16px rgba(0, 0, 0, 0.08)", }, transition: { type: "spring", stiffness: 400, damping: 17, }, tabIndex: 0, role: "article", "aria-label": "Card: ".concat(title), onKeyDown: function (e) { if (e.key === "Enter" || e.key === " ") { if (toggleExpand) toggleExpand(id); } }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header", children: [(0, jsx_runtime_1.jsx)("h3", { className: "card-title", children: isExpanded || title.length <= 100 ? title : "".concat(title.substring(0, 100), "...") }), (0, jsx_runtime_1.jsxs)("div", { className: "card-actions", children: [onEdit && ((0, jsx_runtime_1.jsx)("button", { className: "card-action-button edit", onClick: function () { return onEdit(id); }, "aria-label": "Edit card", type: "button", children: (0, jsx_runtime_1.jsx)(EditIcon, {}) })), onDelete && ((0, jsx_runtime_1.jsx)("button", { className: "card-action-button delete", onClick: function () { return onDelete(id); }, "aria-label": "Delete card", type: "button", children: (0, jsx_runtime_1.jsx)(DeleteIcon, {}) }))] })] }), priority && ((0, jsx_runtime_1.jsx)("div", { className: "priority-badge-container", children: (0, jsx_runtime_1.jsx)("span", { className: "priority-badge ".concat(priority.toLowerCase()), children: priority }) })), isExpanded && ((0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.3 }, className: "card-details", children: [description && ((0, jsx_runtime_1.jsx)("div", { className: "card-description", children: (0, jsx_runtime_1.jsx)("p", { children: description }) })), dueDate && ((0, jsx_runtime_1.jsxs)("div", { className: "card-detail", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Due:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: dueDate })] })), tags && tags.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "card-tags", children: tags.map(function (tag) { return ((0, jsx_runtime_1.jsx)("span", { className: "card-tag", children: tag }, tag)); }) }))] })), (0, jsx_runtime_1.jsxs)("div", { className: "card-footer", children: [renderAvatar ? renderAvatar(avatarPath) : avatarPath && (0, jsx_runtime_1.jsx)(DefaultAvatar, { avatarPath: avatarPath }), hasDetails && toggleExpand && ((0, jsx_runtime_1.jsx)("button", { onClick: function () { return toggleExpand(id); }, className: "expand-toggle", "aria-label": isExpanded ? "Collapse card" : "Expand card", type: "button", children: (0, jsx_runtime_1.jsx)(CollapseExpandIcon, { isExpanded: isExpanded }) }))] })] })); }; var DefaultAvatar = function (_a) { var avatarPath = _a.avatarPath; if (!avatarPath) return null; return ((0, jsx_runtime_1.jsx)("div", { className: "avatar-container", children: (0, jsx_runtime_1.jsx)("img", { src: avatarPath, alt: "", className: "avatar", loading: "lazy" }) })); }; var DefaultAddCard = function (_a) { var column = _a.column, setCards = _a.setCards, onTaskAddedCallback = _a.onTaskAddedCallback; var _b = (0, react_1.useState)(false), isAdding = _b[0], setIsAdding = _b[1]; var _c = (0, react_1.useState)(""), newTitle = _c[0], setNewTitle = _c[1]; // Handle form submission for new card var handleSubmit = function (e) { e.preventDefault(); if (newTitle.trim()) { var newCard_1 = { id: "card-".concat(Date.now()), title: newTitle, status: column, }; setCards(function (prev) { return __spreadArray(__spreadArray([], prev, true), [newCard_1], false); }); setNewTitle(""); setIsAdding(false); onTaskAddedCallback === null || onTaskAddedCallback === void 0 ? void 0 : onTaskAddedCallback(newTitle); } }; // Improved rendering to ensure proper overflow handling return isAdding ? ((0, jsx_runtime_1.jsx)("div", { className: "add-card-motion-container", children: (0, jsx_runtime_1.jsxs)(framer_motion_1.motion.form, { onSubmit: handleSubmit, className: "add-card-form", initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.2 }, children: [(0, jsx_runtime_1.jsx)("div", { className: "add-card-input-container", children: (0, jsx_runtime_1.jsx)("input", { type: "text", className: "add-card-input", value: newTitle, onChange: function (e) { return setNewTitle(e.target.value); }, placeholder: "Enter card title...", autoFocus: true, onKeyDown: function (e) { if (e.key === "Escape") { setIsAdding(false); } }, "aria-label": "New card title", maxLength: 200 }) }), (0, jsx_runtime_1.jsxs)("div", { className: "add-card-buttons", children: [(0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: !newTitle.trim(), children: "Add Card" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: function () { return setIsAdding(false); }, children: "Cancel" })] })] }) })) : ((0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { className: "add-card", onClick: function () { return setIsAdding(true); }, whileHover: { scale: 1.03 }, whileTap: { scale: 0.97 }, role: "button", tabIndex: 0, "aria-label": "Add new card", onKeyDown: function (e) { if (e.key === "Enter" || e.key === " ") { setIsAdding(true); } }, children: [(0, jsx_runtime_1.jsx)("span", { children: "+" }), " Add Card"] })); }; var DropIndicator = function (_a) { var beforeId = _a.beforeId, column = _a.column; return ((0, jsx_runtime_1.jsx)("div", { "data-before": beforeId, "data-column": column, className: "drop-indicator", "aria-hidden": "true" })); }; exports.default = KanbanBoard;