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
JavaScript
;
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;