callumalpass_tasknotes/styles/task-card-bem.css
Callum Alpass c83cf83871 feat: Add expandable subtasks to project task cards
Add chevron icon to project task cards that allows users to expand and view
subtasks inline with proper indentation and visual hierarchy.

Key features:
- Chevron icon positioned next to folder icon on project tasks
- Click to expand/collapse shows subtasks below with 20px indentation
- Smooth rotation animation and hover effects
- Configurable via new "Show expandable subtasks" setting (default: enabled)
- Session-persistent expanded state via ExpandedProjectsService
- Proper DOM structure with main-row wrapper for correct layout
- Subtasks render with lighter background and visual separation
- Lazy loading - subtasks only fetch when expanded
- Graceful error handling and loading states

Technical implementation:
- New ExpandedProjectsService for state management
- Enhanced TaskCard component with main-row/subtasks layout
- Comprehensive BEM CSS styling for chevron and subtask containers
- Updated settings interface with new showExpandableSubtasks option
- Integrated with existing ProjectSubtasksService for data fetching
2025-08-03 16:01:24 +10:00

844 lines
22 KiB
CSS

/* =================================================================
TASKCARD COMPONENT - BEM NAMING CONVENTION & PROPER SCOPING
================================================================= */
/* All TaskCard styles are scoped under .tasknotes-plugin for proper isolation */
.tasknotes-plugin .task-card {
/* Layout & Structure */
display: flex;
flex-direction: column;
gap: 0;
position: relative;
/* Spacing & Sizing - Todoist compact style */
padding: var(--tn-spacing-md) var(--tn-spacing-lg);
margin-bottom: 1px;
/* Visual Styling - Todoist minimal style */
background-color: transparent;
border: none;
border-radius: 0;
/* Interactions & Accessibility - Simplified */
cursor: pointer;
transition: background-color var(--tn-transition-fast);
will-change: background-color;
outline: none;
role: listitem;
tabindex: 0;
}
.tasknotes-plugin .task-card:hover {
background-color: var(--tn-interactive-hover);
}
.tasknotes-plugin .task-card:focus {
background-color: var(--tn-interactive-hover);
outline: 1px solid var(--tn-border-color);
outline-offset: -1px;
}
.tasknotes-plugin .task-card:focus:not(:focus-visible) {
outline: none;
}
.tasknotes-plugin .task-card:last-child {
margin-bottom: 0;
}
/* Remove border radius for cleaner list appearance */
.tasknotes-plugin .task-card:first-child,
.tasknotes-plugin .task-card:last-child,
.tasknotes-plugin .task-card:first-child:last-child {
border-radius: 0;
}
/* =================================================================
TASKCARD ELEMENTS (BEM __element)
================================================================= */
.tasknotes-plugin .task-card__main-row {
display: flex;
align-items: flex-start;
gap: 0;
width: 100%;
}
.tasknotes-plugin .task-card__content {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.tasknotes-plugin .task-card__title {
font-size: var(--tn-font-size-lg);
font-weight: 400;
line-height: 1.3;
color: var(--tn-text-normal);
margin: 0;
word-wrap: break-word;
word-break: break-word;
white-space: normal;
transition: color 0.2s ease;
}
.tasknotes-plugin .task-card__metadata {
font-size: var(--tn-font-size-md);
color: var(--tn-text-muted);
line-height: 1.2;
margin: 0;
font-weight: 400;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
margin-top: 2px;
}
.tasknotes-plugin .task-card__metadata-item {
display: flex;
align-items: center;
gap: var(--tn-spacing-xs);
white-space: nowrap;
}
.tasknotes-plugin .task-card__metadata-separator {
color: var(--tn-text-faint);
opacity: 0.5;
font-size: 0.8em;
}
.tasknotes-plugin .task-card__tag {
background: var(--tn-bg-secondary);
color: var(--tn-text-muted);
padding: var(--tn-spacing-xs) var(--tn-spacing-sm);
border-radius: var(--tn-radius-xs);
font-size: var(--tn-font-size-sm);
font-weight: 400;
border: none;
white-space: nowrap;
}
.tasknotes-plugin .task-card__due-date {
color: var(--tn-color-success);
font-size: var(--tn-font-size-sm);
font-weight: 400;
padding: var(--tn-spacing-xs) var(--tn-spacing-xs);
border-radius: var(--tn-radius-xs);
background: color-mix(in srgb, var(--tn-color-success) 15%, var(--tn-bg-primary));
}
.tasknotes-plugin .task-card__due-date--overdue {
color: var(--tn-color-error);
background: color-mix(in srgb, var(--tn-color-error) 15%, var(--tn-bg-primary));
}
.tasknotes-plugin .task-card__due-date--today {
color: var(--tn-color-warning);
background: color-mix(in srgb, var(--tn-color-warning) 15%, var(--tn-bg-primary));
}
.tasknotes-plugin .task-card__priority-badge {
display: none; /* Hide priority badges in favor of dots */
}
/* Simplified info display - no progressive disclosure for Todoist style */
/* Drag handle indicator */
.tasknotes-plugin .task-card__drag-handle {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 20px;
background: repeating-linear-gradient(
to bottom,
var(--tn-text-faint) 0px,
var(--tn-text-faint) 2px,
transparent 2px,
transparent 4px
);
opacity: 0;
transition: opacity var(--tn-transition-fast);
border-radius: 2px;
}
.tasknotes-plugin .task-card[draggable="true"]:hover .task-card__drag-handle {
opacity: 0.5;
}
.tasknotes-plugin .task-card[draggable="true"]:active .task-card__drag-handle {
opacity: 0.8;
}
/* Container queries for adaptive layouts */
@container (max-width: 300px) {
.tasknotes-plugin .task-card {
padding: var(--tn-spacing-sm) var(--tn-spacing-md);
}
.tasknotes-plugin .task-card__quick-actions {
gap: 2px;
}
.tasknotes-plugin .task-card__quick-action {
width: 20px;
height: 20px;
}
.tasknotes-plugin .task-card__metadata {
gap: var(--tn-spacing-xs);
}
}
.tasknotes-plugin .task-card__status-dot {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 8px;
flex-shrink: 0;
border: 2px solid var(--tn-border-color);
background-color: transparent;
transition: all 0.2s ease;
align-self: center;
cursor: pointer;
position: relative;
}
.tasknotes-plugin .task-card__status-dot:hover {
border-color: var(--tn-text-muted);
box-shadow: 0 0 8px rgba(100, 149, 237, 0.5);
transform: scale(1.1);
}
/* Completed status - filled circle with status color */
.tasknotes-plugin .task-card--completed .task-card__status-dot {
background-color: var(--current-status-color, var(--tn-color-success));
border-color: var(--current-status-color, var(--tn-color-success));
}
.tasknotes-plugin .task-card__recurring-indicator {
position: absolute;
top: 8px;
right: 26px;
width: 16px;
height: 16px;
color: var(--tn-text-muted);
opacity: 0.8;
z-index: 1;
display: none; /* Hidden by default, shown via modifier */
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 3px;
padding: 2px;
margin: -2px;
}
.tasknotes-plugin .task-card__recurring-indicator:hover {
opacity: 1;
color: var(--interactive-accent);
background: var(--background-modifier-hover);
transform: scale(1.1);
}
.tasknotes-plugin .task-card__recurring-indicator svg {
width: 100%;
height: 100%;
}
.tasknotes-plugin .task-card__project-indicator {
position: absolute;
top: 8px;
right: 44px; /* Position after recurring indicator */
width: 16px;
height: 16px;
color: var(--tn-text-muted);
opacity: 0.8;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-s);
padding: 2px;
cursor: pointer;
transition: all var(--tn-transition-fast);
}
.tasknotes-plugin .task-card__project-indicator:hover {
opacity: 1;
color: var(--interactive-accent);
background: var(--background-modifier-hover);
transform: scale(1.1);
}
.tasknotes-plugin .task-card__project-indicator svg {
width: 100%;
height: 100%;
}
.tasknotes-plugin .task-card__chevron {
position: absolute;
top: 8px;
right: 62px; /* Position after project indicator */
width: 16px;
height: 16px;
color: var(--tn-text-muted);
opacity: 0.8;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-s);
padding: 2px;
cursor: pointer;
transition: all var(--tn-transition-fast);
}
.tasknotes-plugin .task-card__chevron:hover {
opacity: 1;
color: var(--interactive-accent);
background: var(--background-modifier-hover);
transform: scale(1.1);
}
.tasknotes-plugin .task-card__chevron svg {
width: 100%;
height: 100%;
transition: transform var(--tn-transition-fast);
}
.tasknotes-plugin .task-card__chevron--expanded svg {
transform: rotate(90deg);
}
.tasknotes-plugin .task-card__subtasks {
margin-left: 20px;
margin-top: 4px;
border-left: 2px solid var(--tn-border-color);
padding-left: 12px;
}
.tasknotes-plugin .task-card__subtasks .task-card {
margin-bottom: 2px;
padding: var(--tn-spacing-sm) var(--tn-spacing-md);
}
.tasknotes-plugin .task-card__subtasks .task-card:last-child {
margin-bottom: 0;
}
.tasknotes-plugin .task-card--subtask {
background: color-mix(in srgb, var(--tn-bg-secondary) 30%, transparent);
border-radius: var(--tn-radius-xs);
}
.tasknotes-plugin .task-card--subtask:hover {
background: color-mix(in srgb, var(--tn-interactive-hover) 70%, var(--tn-bg-secondary));
}
.tasknotes-plugin .task-card__subtasks-loading {
margin-left: 20px;
margin-top: 4px;
padding: var(--tn-spacing-sm);
color: var(--tn-text-muted);
font-size: var(--tn-font-size-sm);
font-style: italic;
}
.tasknotes-plugin .task-card__context-menu {
position: absolute;
top: 8px;
right: 8px;
width: 16px;
height: 16px;
color: var(--tn-text-faint);
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
border-radius: 2px;
}
.tasknotes-plugin .task-card__context-menu:hover {
color: var(--tn-text-muted);
background: var(--tn-interactive-hover);
}
.tasknotes-plugin .task-card:hover .task-card__context-menu {
opacity: 1;
}
.tasknotes-plugin .task-card__context-menu svg {
width: 14px;
height: 14px;
}
/* Quick actions removed for Todoist-style simplicity */
/* =====================================================================
HOVER CONTEXT MENU INTERACTIONS
===================================================================== */
/* Enhanced priority dot hover for context menu */
.tasknotes-plugin .task-card__priority-dot {
transition: all 0.2s ease;
}
.tasknotes-plugin .task-card__priority-dot:hover {
transform: scale(1.2);
border-width: 3px;
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2);
}
/* Hoverable date text elements */
.tasknotes-plugin .task-card__metadata-date {
cursor: pointer;
transition: all 0.2s ease;
border-radius: var(--radius-s);
padding: 2px 4px;
margin: -2px -4px;
position: relative;
}
.tasknotes-plugin .task-card__metadata-date:hover {
background: var(--background-modifier-hover);
color: var(--interactive-accent);
text-decoration: underline;
}
.tasknotes-plugin .task-card__metadata-date--due:hover {
background: rgba(220, 38, 127, 0.1);
color: var(--color-red);
}
.tasknotes-plugin .task-card__metadata-date--scheduled:hover {
background: rgba(8, 109, 221, 0.1);
color: var(--color-blue);
}
/* Project links */
.tasknotes-plugin .task-card__project-link {
color: var(--interactive-accent);
text-decoration: none;
transition: all 0.2s ease;
border-radius: var(--radius-s);
padding: 1px 2px;
margin: -1px -2px;
cursor: pointer;
display: inline-block;
position: relative;
z-index: 1;
}
.tasknotes-plugin .task-card__project-link:hover,
.tasknotes-plugin .task-card__project-link:focus {
background: var(--background-modifier-hover);
text-decoration: underline;
color: var(--interactive-accent-hover);
outline: none;
}
.tasknotes-plugin .task-card__project-link:focus {
box-shadow: 0 0 0 2px var(--interactive-accent);
}
.tasknotes-plugin .task-card__project-link.internal-link {
/* Ensure consistent styling with Obsidian's internal links */
color: var(--link-color);
}
.tasknotes-plugin .task-card__project-link.internal-link:hover,
.tasknotes-plugin .task-card__project-link.internal-link:focus {
color: var(--link-color-hover);
}
/* =================================================================
TASKCARD MODIFIERS (BEM --modifier)
================================================================= */
/* Completion State - Use theme colors */
.tasknotes-plugin .task-card--completed {
opacity: 0.6;
}
.tasknotes-plugin .task-card--completed .task-card__title {
text-decoration: line-through;
color: var(--tn-text-muted);
}
/* Archived State */
.tasknotes-plugin .task-card--archived {
opacity: 0.5;
background-color: transparent;
}
/* Active Time Tracking */
.tasknotes-plugin .task-card--actively-tracked {
position: relative;
}
.tasknotes-plugin .task-card--actively-tracked::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
border: 2px solid var(--tn-color-info);
border-radius: var(--tn-radius-xs);
opacity: 0.6;
animation: pulse-tracking 2s infinite;
}
@keyframes pulse-tracking {
0%, 100% { opacity: 0.6; }
50% { opacity: 0.3; }
}
/* Recurring Task */
.tasknotes-plugin .task-card--recurring .task-card__recurring-indicator {
display: flex;
}
/* Priority indicators - Use user's priority settings */
.tasknotes-plugin .task-card__priority-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 6px;
flex-shrink: 0;
align-self: center;
background-color: var(--priority-color, transparent);
cursor: pointer;
}
/* Dynamic priority colors using CSS variables set by PriorityManager */
.tasknotes-plugin .task-card[data-priority] .task-card__priority-dot {
background-color: var(--current-priority-color, var(--tn-text-muted));
}
/* Fallback priority colors using existing system */
.tasknotes-plugin .task-card--priority-high .task-card__priority-dot {
background-color: var(--priority-high-color, var(--tn-color-error));
}
.tasknotes-plugin .task-card--priority-medium .task-card__priority-dot,
.tasknotes-plugin .task-card--priority-normal .task-card__priority-dot {
background-color: var(--priority-normal-color, var(--tn-interactive-accent));
}
.tasknotes-plugin .task-card--priority-low .task-card__priority-dot {
background-color: var(--priority-low-color, var(--tn-text-muted));
}
/* Status colors - Use dynamic status colors from user settings */
.tasknotes-plugin .task-card[data-status] .task-card__status-dot {
border-color: var(--current-status-color, var(--tn-border-color));
}
/* Minimal animations - Use theme colors */
.tasknotes-plugin .task-card--updated {
background-color: var(--tn-interactive-hover);
transition: background-color 0.3s ease;
}
/* =================================================================
RESPONSIVE DESIGN
================================================================= */
/* Mobile & Touch Devices - Use theme colors */
@media (pointer: coarse) {
.tasknotes-plugin .task-card {
padding: 12px 16px;
min-height: 44px;
margin-bottom: 2px;
}
.tasknotes-plugin .task-card:active {
background-color: var(--tn-interactive-hover);
}
/* Larger touch targets */
.tasknotes-plugin .task-card__context-menu {
width: 24px;
height: 24px;
opacity: 1;
}
.tasknotes-plugin .task-card__context-menu svg {
width: 16px;
height: 16px;
}
/* Larger status dots for touch */
.tasknotes-plugin .task-card__status-dot {
width: 18px;
height: 18px;
}
}
/* High density displays */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.tasknotes-plugin .task-card {
border-left-width: 1px;
}
.tasknotes-plugin .task-card::before {
width: 1px;
}
}
/* Reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
.tasknotes-plugin .task-card,
.tasknotes-plugin .task-card *,
.tasknotes-plugin .task-card::before,
.tasknotes-plugin .task-card::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
.tasknotes-plugin .task-card--dragging {
animation: none;
}
}
/* Print styles */
@media print {
.tasknotes-plugin .task-card {
break-inside: avoid;
page-break-inside: avoid;
background: white !important;
box-shadow: none !important;
border: 1px solid #ccc !important;
}
.tasknotes-plugin .task-card__quick-actions,
.tasknotes-plugin .task-card__context-menu,
.tasknotes-plugin .task-card__enhanced-info {
display: none !important;
}
}
/* Compact mode for smaller screens - Todoist style */
@media (max-width: 768px) {
.tasknotes-plugin .task-card {
padding: 10px 14px;
}
.tasknotes-plugin .task-card__content {
gap: 1px;
}
.tasknotes-plugin .task-card__title {
font-size: 13px;
}
.tasknotes-plugin .task-card__metadata {
font-size: 11px;
}
}
/* =================================================================
LEGACY COMPATIBILITY - DEPRECATED
Keep existing classes for backward compatibility but prefer BEM structure above.
These will be removed in a future version.
================================================================= */
/* DEPRECATED - Use .task-card instead */
.tasknotes-plugin .tasknotes-card.task-card {
/* Inherit new styles */
}
/* DEPRECATED - Use .task-card__content instead */
.tasknotes-plugin .task-card .task-content {
/* Map to new BEM structure */
flex: 1;
display: flex;
flex-direction: column;
gap: var(--tn-spacing-xs);
min-width: 0;
}
/* DEPRECATED - Use .task-card__title instead */
.tasknotes-plugin .task-card .task-title {
font-size: var(--tn-font-size-lg);
font-weight: var(--tn-font-weight-normal);
line-height: 1.3;
color: var(--tn-text-normal);
margin: 0;
word-wrap: break-word;
}
/* DEPRECATED - Use .task-card__title.task-card--completed instead */
.tasknotes-plugin .task-card .task-title.completed {
text-decoration: line-through;
color: var(--tn-text-muted);
}
/* DEPRECATED - Use .task-card__metadata instead */
.tasknotes-plugin .task-card .task-metadata-line {
font-size: var(--tn-font-size-xs);
color: var(--tn-text-muted);
line-height: 1.2;
margin: 0;
}
/* DEPRECATED - Use .task-card__status-dot instead */
.tasknotes-plugin .task-card .status-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: var(--tn-radius-full);
margin-right: var(--tn-spacing-sm);
margin-top: 4px;
flex-shrink: 0;
background-color: var(--tn-text-muted);
}
/* DEPRECATED - Use .task-card__recurring-indicator instead */
.tasknotes-plugin .task-card .recurring-indicator {
position: absolute;
top: var(--tn-spacing-md);
right: 32px;
width: 14px;
height: 14px;
color: var(--tn-text-muted);
opacity: 0.7;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
/* DEPRECATED - Use .task-card__context-menu instead */
.tasknotes-plugin .task-card .task-context-icon {
position: absolute;
top: var(--tn-spacing-md);
right: var(--tn-spacing-md);
width: 16px;
height: 16px;
color: var(--tn-text-muted);
cursor: pointer;
opacity: 0;
transition: all var(--tn-transition-fast);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
border-radius: var(--tn-radius-xs);
}
.tasknotes-plugin .task-card .task-context-icon:hover {
color: var(--tn-text-normal);
background: var(--tn-interactive-hover);
transform: scale(1.1);
}
.tasknotes-plugin .task-card:hover .task-context-icon {
opacity: 1;
}
/* =================================================================
TASKCARD DRAG AND DROP STATES
================================================================= */
/* Draggable task cards */
.tasknotes-plugin .task-card[draggable="true"] {
transition: all var(--tn-transition-fast);
}
.tasknotes-plugin .task-card[draggable="true"]:hover {
cursor: grab;
transform: translateY(-1px);
box-shadow: 0 2px 8px color-mix(in srgb, var(--tn-shadow-color) 15%, transparent);
}
.tasknotes-plugin .task-card[draggable="true"]:active {
cursor: grabbing;
transform: scale(0.98) rotate(1deg);
}
/* Enhanced dragging state */
.tasknotes-plugin .task-card--dragging {
opacity: 0.8;
transform: rotate(5deg) scale(1.02);
cursor: grabbing;
z-index: 1000;
pointer-events: none;
box-shadow: 0 8px 32px color-mix(in srgb, var(--tn-shadow-color) 30%, transparent),
0 0 0 1px color-mix(in srgb, var(--tn-interactive-accent) 50%, transparent);
transition: none;
animation: drag-pulse 2s infinite ease-in-out;
}
@keyframes drag-pulse {
0%, 100% {
box-shadow: 0 8px 32px color-mix(in srgb, var(--tn-shadow-color) 30%, transparent),
0 0 0 1px color-mix(in srgb, var(--tn-interactive-accent) 50%, transparent);
}
50% {
box-shadow: 0 12px 40px color-mix(in srgb, var(--tn-shadow-color) 40%, transparent),
0 0 0 2px color-mix(in srgb, var(--tn-interactive-accent) 70%, transparent);
}
}
/* Drag preview ghost */
.tasknotes-plugin .task-card--drag-ghost {
opacity: 0.3;
transform: scale(0.95);
transition: all var(--tn-transition-fast);
pointer-events: none;
filter: blur(1px);
}
/* Drop zone feedback */
.tasknotes-plugin .task-card--drop-target {
border: 2px dashed var(--tn-interactive-accent);
background: color-mix(in srgb, var(--tn-interactive-accent) 5%, var(--tn-bg-primary));
transform: scale(1.02);
transition: all var(--tn-transition-fast);
}
.tasknotes-plugin .task-card--drop-target::before {
content: '';
position: absolute;
inset: -2px;
border-radius: var(--tn-radius-sm);
background: linear-gradient(
45deg,
transparent 25%,
color-mix(in srgb, var(--tn-interactive-accent) 10%, transparent) 25%,
color-mix(in srgb, var(--tn-interactive-accent) 10%, transparent) 50%,
transparent 50%,
transparent 75%,
color-mix(in srgb, var(--tn-interactive-accent) 10%, transparent) 75%
);
background-size: 8px 8px;
animation: drop-zone-shimmer 1s linear infinite;
z-index: -1;
}
@keyframes drop-zone-shimmer {
0% { background-position: 0 0; }
100% { background-position: 8px 8px; }
}