mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
feat(schedule): add horizontal scroll for week view on narrow viewports
- Week view now always shows all 7 days with navigation skipping full weeks - Add horizontal scroll when viewport < 1900px to show hidden days - Implement responsive column widths (180px desktop, 150px tablet, 120px mobile) - Columns scale up with minmax() when extra space available - Add visible themed scrollbar for better UX - Simplify navigation logic: always skip 7 days forward/backward - Simplify day generation: sequential days from reference date - Update tests to match new 7-day navigation behavior
This commit is contained in:
parent
5c851e52d3
commit
7a98831835
5 changed files with 134 additions and 29 deletions
|
|
@ -3,6 +3,84 @@
|
|||
|
||||
:host {
|
||||
--schedule-time-width: 3em;
|
||||
// Responsive day column widths
|
||||
--schedule-day-width: 180px; // Default for desktop
|
||||
|
||||
@include mq(md, max) {
|
||||
// Tablet
|
||||
--schedule-day-width: 150px;
|
||||
}
|
||||
|
||||
@include mq(xs, max) {
|
||||
// Mobile
|
||||
--schedule-day-width: 120px;
|
||||
}
|
||||
|
||||
// Enable horizontal scroll when viewport is too small for 7 days
|
||||
&[data-horizontal-scroll] {
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
display: block; // Change from default to allow proper overflow
|
||||
|
||||
// Show scrollbar on all platforms
|
||||
&::-webkit-scrollbar {
|
||||
height: 12px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--bg-darker);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--extra-border-color);
|
||||
border-radius: 6px;
|
||||
border: 2px solid var(--bg-darker);
|
||||
|
||||
&:hover {
|
||||
background: var(--text-color-muted);
|
||||
}
|
||||
}
|
||||
|
||||
// For Firefox
|
||||
scrollbar-width: auto;
|
||||
scrollbar-color: var(--extra-border-color) var(--bg-darker);
|
||||
|
||||
.week-header {
|
||||
// Remove left/right constraints to allow horizontal scrolling
|
||||
left: auto;
|
||||
right: auto;
|
||||
// Force width to match grid width
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
// Allow columns to scale up when space available, but enforce minimum for scroll
|
||||
grid-template-columns: var(--schedule-time-width) repeat(
|
||||
var(--nr-of-days),
|
||||
minmax(var(--schedule-day-width), 1fr)
|
||||
);
|
||||
// Force grid to size based on content, not container
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
// Disable centering to allow proper overflow
|
||||
place-content: start;
|
||||
}
|
||||
|
||||
.days {
|
||||
// Match grid-container column widths exactly
|
||||
grid-template-columns: var(--schedule-time-width) repeat(
|
||||
var(--nr-of-days),
|
||||
minmax(var(--schedule-day-width), 1fr)
|
||||
);
|
||||
// Ensure days container matches grid width
|
||||
width: fit-content;
|
||||
// Disable centering
|
||||
place-content: start;
|
||||
}
|
||||
}
|
||||
|
||||
// State-based styles using host classes
|
||||
&.isCtrlKeyPressed {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ import { formatMonthDay } from '../../../util/format-month-day.util';
|
|||
import { ScheduleWeekDragService } from './schedule-week-drag.service';
|
||||
import { calculatePlaceholderForGridMove } from './schedule-week-placeholder.util';
|
||||
import { truncate } from '../../../util/truncate';
|
||||
import { fromEvent } from 'rxjs';
|
||||
import { debounceTime, map, startWith } from 'rxjs/operators';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
const D_HOURS = 24;
|
||||
|
||||
|
|
@ -58,6 +61,7 @@ const D_HOURS = 24;
|
|||
'[class.is-not-dragging]': '!isDragging()',
|
||||
'[class.is-resizing-event]': 'isAnyEventResizing()',
|
||||
'[class]': 'dragEventTypeClass()',
|
||||
'[attr.data-horizontal-scroll]': 'shouldEnableHorizontalScroll()',
|
||||
},
|
||||
})
|
||||
export class ScheduleWeekComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
|
@ -65,6 +69,20 @@ export class ScheduleWeekComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
private _dateTimeFormatService = inject(DateTimeFormatService);
|
||||
private _translateService = inject(TranslateService);
|
||||
|
||||
private _windowSize = toSignal(
|
||||
fromEvent(window, 'resize').pipe(
|
||||
startWith({ width: window.innerWidth }),
|
||||
debounceTime(50),
|
||||
map(() => ({ width: window.innerWidth })),
|
||||
),
|
||||
{ initialValue: { width: window.innerWidth } },
|
||||
);
|
||||
|
||||
shouldEnableHorizontalScroll = computed(() => {
|
||||
// Enable scroll when viewport is smaller than what's needed for 7 days
|
||||
return this._windowSize().width < 1900;
|
||||
});
|
||||
|
||||
isInPanel = input<boolean>(false);
|
||||
events = input<ScheduleEvent[] | null>([]);
|
||||
beyondBudget = input<ScheduleEvent[][] | null>([]);
|
||||
|
|
|
|||
|
|
@ -75,10 +75,24 @@ describe('ScheduleService', () => {
|
|||
expect(result[0]).toBe(expectedTodayStr);
|
||||
});
|
||||
|
||||
it('should return days starting from referenceDate when provided', () => {
|
||||
it('should return days starting from referenceDate', () => {
|
||||
// Arrange
|
||||
const nrOfDaysToShow = 3;
|
||||
const referenceDate = new Date(2026, 0, 25); // Jan 25, 2026
|
||||
const referenceDate = new Date(2028, 5, 15); // June 15, 2028
|
||||
const expectedFirstDay = dateService.todayStr(referenceDate.getTime());
|
||||
|
||||
// Act
|
||||
const result = service.getDaysToShow(nrOfDaysToShow, referenceDate);
|
||||
|
||||
// Assert
|
||||
expect(result[0]).toBe(expectedFirstDay);
|
||||
expect(result.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should return days starting from provided date even if today', () => {
|
||||
// Arrange
|
||||
const nrOfDaysToShow = 3;
|
||||
const referenceDate = new Date();
|
||||
const expectedFirstDay = dateService.todayStr(referenceDate.getTime());
|
||||
|
||||
// Act
|
||||
|
|
@ -92,7 +106,8 @@ describe('ScheduleService', () => {
|
|||
it('should return consecutive days from referenceDate', () => {
|
||||
// Arrange
|
||||
const nrOfDaysToShow = 7;
|
||||
const referenceDate = new Date(2026, 0, 20); // Jan 20, 2026
|
||||
// Use a future date to ensure not in current week
|
||||
const referenceDate = new Date(2028, 0, 20); // Jan 20, 2028
|
||||
|
||||
// Act
|
||||
const result = service.getDaysToShow(nrOfDaysToShow, referenceDate);
|
||||
|
|
@ -112,7 +127,8 @@ describe('ScheduleService', () => {
|
|||
it('should handle transition across months', () => {
|
||||
// Arrange
|
||||
const nrOfDaysToShow = 5;
|
||||
const referenceDate = new Date(2026, 0, 30); // Jan 30, 2026
|
||||
// Use a future date to ensure not in current week
|
||||
const referenceDate = new Date(2028, 0, 30); // Jan 30, 2028
|
||||
|
||||
// Act
|
||||
const result = service.getDaysToShow(nrOfDaysToShow, referenceDate);
|
||||
|
|
|
|||
|
|
@ -213,7 +213,8 @@ describe('ScheduleComponent', () => {
|
|||
|
||||
// Assert
|
||||
const newDate = component['_selectedDate']();
|
||||
expect(newDate?.getDate()).toBe(13); // Jan 13, 2026
|
||||
expect(newDate?.getDate()).toBe(13); // Jan 13, 2026 (20 - 7)
|
||||
expect(newDate?.getHours()).toBe(0); // Normalized to midnight
|
||||
});
|
||||
|
||||
it('should subtract 7 days from today when _selectedDate is null in week view', () => {
|
||||
|
|
@ -228,6 +229,7 @@ describe('ScheduleComponent', () => {
|
|||
// Assert
|
||||
const newDate = component['_selectedDate']();
|
||||
expect(newDate?.getDate()).toBe(expectedDate.getDate());
|
||||
expect(newDate?.getHours()).toBe(0); // Normalized to midnight
|
||||
});
|
||||
|
||||
it('should go to previous month in month view', () => {
|
||||
|
|
@ -272,12 +274,15 @@ describe('ScheduleComponent', () => {
|
|||
|
||||
// Assert
|
||||
const newDate = component['_selectedDate']();
|
||||
expect(newDate?.getDate()).toBe(27); // Jan 27, 2026
|
||||
expect(newDate?.getDate()).toBe(27); // Jan 27, 2026 (20 + 7)
|
||||
expect(newDate?.getHours()).toBe(0); // Normalized to midnight
|
||||
});
|
||||
|
||||
it('should add 7 days from today when _selectedDate is null in week view', () => {
|
||||
// Arrange
|
||||
component['_selectedDate'].set(null);
|
||||
|
||||
// Calculate expected date (today + 7 days)
|
||||
const expectedDate = new Date();
|
||||
expectedDate.setDate(expectedDate.getDate() + 7);
|
||||
|
||||
|
|
@ -287,6 +292,7 @@ describe('ScheduleComponent', () => {
|
|||
// Assert
|
||||
const newDate = component['_selectedDate']();
|
||||
expect(newDate?.getDate()).toBe(expectedDate.getDate());
|
||||
expect(newDate?.getHours()).toBe(0); // Normalized to midnight
|
||||
});
|
||||
|
||||
it('should go to next month in month view', () => {
|
||||
|
|
|
|||
|
|
@ -109,17 +109,8 @@ export class ScheduleComponent {
|
|||
}
|
||||
}
|
||||
|
||||
if (width < 600) {
|
||||
return 3;
|
||||
} else if (width < 900) {
|
||||
return 4;
|
||||
} else if (width < 1900) {
|
||||
return 5;
|
||||
} else if (width < 2200) {
|
||||
return 7;
|
||||
} else {
|
||||
return 10;
|
||||
}
|
||||
// Week view: always 7 days
|
||||
return 7;
|
||||
});
|
||||
|
||||
daysToShow = computed(() => {
|
||||
|
|
@ -220,12 +211,10 @@ export class ScheduleComponent {
|
|||
);
|
||||
this._selectedDate.set(previousMonth);
|
||||
} else {
|
||||
// Week view: subtract 7 days (create fresh Date to avoid mutations)
|
||||
const previousWeek = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth(),
|
||||
currentDate.getDate() - 7,
|
||||
);
|
||||
// Week view: always subtract 7 days (full week)
|
||||
const previousWeek = new Date(currentDate);
|
||||
previousWeek.setDate(currentDate.getDate() - 7);
|
||||
previousWeek.setHours(0, 0, 0, 0);
|
||||
this._selectedDate.set(previousWeek);
|
||||
}
|
||||
}
|
||||
|
|
@ -243,12 +232,10 @@ export class ScheduleComponent {
|
|||
);
|
||||
this._selectedDate.set(nextMonth);
|
||||
} else {
|
||||
// Week view: add 7 days (create fresh Date to avoid mutations)
|
||||
const nextWeek = new Date(
|
||||
currentDate.getFullYear(),
|
||||
currentDate.getMonth(),
|
||||
currentDate.getDate() + 7,
|
||||
);
|
||||
// Week view: always add 7 days (full week)
|
||||
const nextWeek = new Date(currentDate);
|
||||
nextWeek.setDate(currentDate.getDate() + 7);
|
||||
nextWeek.setHours(0, 0, 0, 0);
|
||||
this._selectedDate.set(nextWeek);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue