UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

351 lines (348 loc) 15.2 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { forwardRef, useState, useRef, useCallback } from 'react'; import { useMotionPreferenceContext } from '../../contexts/MotionPreferenceContext.js'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { useA11yId } from '../../utils/a11y.js'; import { useGlassSound } from '../../utils/soundDesign.js'; const GlassKanbanBoard = /*#__PURE__*/forwardRef(({ columns, title, description, enableDrag = true, showCardCounts = true, showColumnLimits = true, cardSize = "md", virtualized = false, columnWidth = "300px", maxHeight, renderCard, renderColumnHeader, onCardMove, onCardClick, onCardDoubleClick, onColumnAdd, onColumnUpdate, onColumnDelete, onCardAdd, onCardUpdate, onCardDelete, showAddColumn = true, showAddCard = true, emptyState, respectMotionPreference = true, className, ...props }, ref) => { const { prefersReducedMotion } = useMotionPreferenceContext(); const { play, feedback } = useGlassSound(); const kanbanId = useA11yId("glass-kanban-board"); const [dragState, setDragState] = useState({ isDragging: false }); const [scrollPositions, setScrollPositions] = useState({}); const columnRefs = useRef({}); // Card size configurations const cardSizeConfig = { sm: { padding: "glass-p-3", text: "glass-text-sm", spacing: "glass-gap-2", minHeight: "min-h-[80px]" }, md: { padding: "glass-p-4", text: "glass-text-sm", spacing: "glass-gap-3", minHeight: "min-h-[100px]" }, lg: { padding: "glass-p-6", text: "glass-text-base", spacing: "glass-gap-4", minHeight: "min-h-[120px]" } }; const config = cardSizeConfig[cardSize]; // Priority colors const priorityColors = { low: "border-green-500/30 bg-green-500/5", medium: "border-yellow-500/30 bg-yellow-500/5", high: "border-orange-500/30 bg-orange-500/5", urgent: "border-red-500/30 bg-red-500/5" }; // Handle drag start const handleDragStart = useCallback((card, columnId) => { if (!enableDrag) return; setDragState({ isDragging: true, draggedCard: card, draggedFrom: columnId }); feedback("slide"); }, [enableDrag, feedback]); // Handle drag over const handleDragOver = useCallback((e, columnId) => { if (!dragState.isDragging) return; e.preventDefault(); setDragState(prev => ({ ...prev, dragOverColumn: columnId })); }, [dragState.isDragging]); // Handle drag end const handleDragEnd = useCallback(() => { setDragState({ isDragging: false }); }, []); // Handle drop const handleDrop = useCallback((e, columnId, position) => { e.preventDefault(); if (!dragState.draggedCard || !dragState.draggedFrom) return; const targetPosition = position ?? 0; if (dragState.draggedFrom !== columnId) { onCardMove?.(dragState.draggedCard.id, dragState.draggedFrom, columnId, targetPosition); feedback("success"); } setDragState({ isDragging: false }); }, [dragState, onCardMove, feedback]); // Get card priority indicator const getPriorityIndicator = priority => { if (!priority) return null; const colors = { low: "bg-green-500", medium: "bg-yellow-500", high: "bg-orange-500", urgent: "bg-red-500" }; return jsx("div", { className: cn("w-1 h-full absolute left-0 top-0 rounded-l-md", colors[priority]) }); }; // Default card renderer const defaultRenderCard = useCallback((card, columnId) => { return jsxs(OptimizedGlassCore, { draggable: enableDrag && !columns.find(c => c.id === columnId)?.readOnly, onDragStart: e => handleDragStart(card, columnId), onDragEnd: handleDragEnd, onClick: () => onCardClick?.(card, columnId), onDoubleClick: () => onCardDoubleClick?.(card, columnId), elevation: "level2", intensity: "medium", depth: 1, tint: "neutral", border: "subtle", className: cn("glass-kanban-card relative cursor-pointer transition-all duration-200", "glass-backdrop-blur-md border border-border/20 glass-radius-md", config.padding, config.minHeight, "hover:scale-[1.02] hover:shadow-lg", dragState.isDragging && dragState.draggedCard?.id === card.id && "opacity-50 scale-95", card.priority && priorityColors[card.priority], enableDrag && "cursor-grab active:cursor-grabbing"), role: "article", "aria-label": `Card: ${card.title}`, tabIndex: 0, onKeyDown: e => { if (e.key === "Enter" || e.key === " ") { onCardClick?.(card, columnId); } }, children: [getPriorityIndicator(card.priority), jsxs("div", { className: cn("relative", config.spacing), children: [jsx("h3", { className: cn("font-semibold text-foreground line-clamp-2", config.text), children: card.title }), card.description && jsx("p", { className: 'glass-text-secondary glass-text-sm line-clamp-3', children: card.description }), card.content && jsx("div", { className: "glass-text-sm", children: card.content }), card.tags && card.tags.length > 0 && jsxs("div", { className: "glass-flex glass-flex-wrap glass-gap-1", children: [card.tags.slice(0, 3).map((tag, index) => jsx("span", { className: 'glass-px-2 glass-py-1 glass-text-xs glass-surface-primary/10 text-primary glass-radius-full', children: tag }, index)), card.tags.length > 3 && jsxs("span", { className: "glass-px-2 glass-py-1 glass-text-xs glass-surface-subtle glass-text-secondary glass-radius-full", children: ["+", card.tags.length - 3] })] }), jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between pt-2', children: [card.assignee && jsx("div", { className: "glass-flex glass-items-center glass-gap-2", children: card.assignee.avatar ? jsx("img", { src: card.assignee.avatar, alt: card.assignee.name, className: 'w-6 h-6 glass-radius-full' }) : jsx("div", { className: 'w-6 h-6 glass-surface-primary/20 text-primary glass-radius-full glass-flex glass-items-center glass-justify-center glass-text-xs font-medium', children: card.assignee.name.charAt(0).toUpperCase() }) }), card.dueDate && jsx("div", { className: cn("glass-text-xs glass-px-2 glass-py-1 glass-radius-md", new Date(card.dueDate) < new Date() ? "bg-red-500/10 text-red-600" : new Date(card.dueDate).getTime() - Date.now() < 24 * 60 * 60 * 1000 ? "bg-yellow-500/10 text-yellow-600" : "bg-muted/20 glass-text-secondary"), children: new Date(card.dueDate).toLocaleDateString() })] })] })] }, card.id); }, [enableDrag, columns, handleDragStart, handleDragEnd, onCardClick, onCardDoubleClick, config, dragState, priorityColors]); // Default column header renderer const defaultRenderColumnHeader = useCallback(column => { const cardCount = column.cards.length; const isOverLimit = column.limit && cardCount > column.limit; return jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("div", { className: 'w-3 h-3 glass-radius-full', style: { backgroundColor: column.color || "var(--glass-gray-500)" } }), jsx("h2", { className: 'font-semibold text-primary', children: column.title }), showCardCounts && jsxs("span", { className: cn("glass-px-2 glass-py-1 glass-text-xs glass-radius-full", isOverLimit ? "bg-red-500/20 text-red-600" : "bg-muted/20 glass-text-secondary"), children: [cardCount, showColumnLimits && column.limit && ` / ${column.limit}`] })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-1", children: [showAddCard && !column.readOnly && jsx("button", { onClick: () => onCardAdd?.(column.id), className: cn("w-6 h-6 glass-radius-full bg-primary/10 hover:bg-primary/20", "text-primary glass-text-sm font-bold transition-colors", "flex items-center justify-center glass-focus glass-touch-target glass-contrast-guard"), title: "Add card", "aria-label": `Add card to ${column.title}`, children: "+" }), onColumnDelete && jsx("button", { onClick: () => onColumnDelete(column.id), className: cn("w-6 h-6 glass-radius-full bg-red-500/10 hover:bg-red-500/20", "text-red-500 glass-text-sm transition-colors", "flex items-center justify-center glass-focus glass-touch-target glass-contrast-guard"), title: "Delete column", "aria-label": `Delete ${column.title} column`, children: "\u00D7" })] })] }); }, [showCardCounts, showColumnLimits, showAddCard, onCardAdd, onColumnDelete]); // Handle column scroll const handleColumnScroll = useCallback((columnId, scrollTop) => { setScrollPositions(prev => ({ ...prev, [columnId]: scrollTop })); }, []); return jsx(OptimizedGlassCore, { ref: ref, id: kanbanId, elevation: "level1", intensity: "medium", depth: 1, tint: "neutral", border: "subtle", className: cn("glass-kanban-board glass-radius-lg glass-backdrop-blur-md border border-border/20", className), style: maxHeight ? { maxHeight } : undefined, ...props, children: jsxs(MotionFramer, { preset: !prefersReducedMotion && respectMotionPreference ? "fadeIn" : "none", className: "glass-flex glass-flex-col glass-h-full", children: [(title || description) && jsxs("div", { className: "glass-p-6 glass-border-b glass-border-glass-border/20", children: [title && jsx("h1", { className: 'glass-text-xl font-bold text-primary mb-2', children: title }), description && jsx("p", { className: "glass-text-secondary", children: description })] }), jsx("div", { className: 'glass-flex-1 overflow-x-auto overflow-y-hidden', children: columns.length === 0 && emptyState ? jsx("div", { className: "glass-flex glass-items-center glass-justify-center glass-h-full glass-p-8", children: emptyState }) : jsxs("div", { className: "glass-flex glass-gap-6 glass-p-6 glass-h-full", style: { minWidth: "fit-content" }, children: [columns.map(column => jsx(MotionFramer, { preset: !prefersReducedMotion && respectMotionPreference ? "slideUp" : "none", delay: columns.indexOf(column) * 100, children: jsxs(OptimizedGlassCore, { elevation: "level2", intensity: "medium", depth: 2, tint: "neutral", border: "subtle", className: cn("glass-kanban-column flex flex-col h-full", "glass-backdrop-blur-md border border-border/20 glass-radius-lg", dragState.dragOverColumn === column.id && "ring-2 ring-primary/50"), style: { width: columnWidth, minWidth: columnWidth }, onDragOver: e => handleDragOver(e, column.id), onDrop: e => handleDrop(e, column.id), role: "region", "aria-label": `Column: ${column.title}`, children: [jsxs("div", { className: "glass-p-4 glass-border-b glass-border-glass-border/20", children: [renderColumnHeader ? renderColumnHeader(column) : defaultRenderColumnHeader(column), column.description && jsx("p", { className: "glass-text-sm glass-text-secondary glass-mt-2", children: column.description })] }), jsxs("div", { ref: el => { if (el) columnRefs.current[column.id] = el; }, className: 'glass-flex-1 overflow-y-auto glass-p-4 glass-gap-3', onScroll: e => handleColumnScroll(column.id, e.currentTarget.scrollTop), children: [column.cards.map((card, index) => jsx(MotionFramer, { preset: !prefersReducedMotion && respectMotionPreference ? "slideUp" : "none", delay: index * 50, children: renderCard ? renderCard(card, column.id) : defaultRenderCard(card, column.id) }, card.id)), enableDrag && dragState.isDragging && jsx("div", { className: cn("h-2 glass-radius-md border-2 border-dashed border-primary/30 transition-all", dragState.dragOverColumn === column.id && "bg-primary/10"), onDragOver: e => e.preventDefault(), onDrop: e => handleDrop(e, column.id, column.cards.length) }), column.cards.length === 0 && jsx("div", { className: 'glass-flex glass-items-center glass-justify-center h-32 glass-text-secondary glass-text-sm glass-border-2 glass-border-dashed glass-border-glass-border/20 glass-radius-lg', children: column.readOnly ? "No cards" : "Drop cards here or click + to add" })] })] }) }, column.id)), showAddColumn && onColumnAdd && jsx(MotionFramer, { preset: !prefersReducedMotion && respectMotionPreference ? "slideUp" : "none", delay: columns.length * 100, children: jsxs("button", { onClick: onColumnAdd, className: cn("flex flex-col items-center justify-center h-full min-w-[280px]", "border-2 border-dashed border-border/30 glass-radius-lg", "hover:border-primary/50 hover:bg-primary/5 transition-all", "glass-text-secondary hover:text-primary glass-focus glass-touch-target glass-contrast-guard"), "aria-label": "Add new column", children: [jsx("div", { className: 'w-12 h-12 glass-radius-full bg-transparent/10 glass-flex glass-items-center glass-justify-center mb-2', children: jsx("span", { className: 'glass-text-2xl font-light', children: "+" }) }), jsx("span", { className: 'glass-text-sm font-medium', children: "Add Column" })] }) })] }) })] }) }); }); GlassKanbanBoard.displayName = "GlassKanbanBoard"; export { GlassKanbanBoard, GlassKanbanBoard as default }; //# sourceMappingURL=GlassKanbanBoard.js.map