feat(tasks): wrap add-task-bar actions onto multiple rows on small screens

On mobile / narrow screens the action bar hid buttons behind a horizontal
scroll, making it easy to miss actions like repeat or estimate. Below the
`xs` breakpoint the action bar now wraps onto multiple rows so every button
stays reachable. Each action button and its optional "clear" button are
grouped in an `.action-item` wrapper so a clear button never wraps away from
the button it belongs to. Larger screens keep the existing single-row
scroll behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018pcXtnrrfVNFAHgPFEpAF2
This commit is contained in:
Claude 2026-07-06 13:21:08 +00:00
parent 245330bd68
commit 855a4d5c29
No known key found for this signature in database
2 changed files with 161 additions and 135 deletions

View file

@ -42,164 +42,174 @@
</button>
@if (!isHideDueBtn()) {
<button
mat-button
class="action-btn"
data-test="add-task-bar-due-btn"
(click)="openScheduleDialog()"
[class.has-value]="state().date"
>
<mat-icon>{{ state().time ? 'schedule' : 'event' }}</mat-icon>
<span>{{ dateDisplay() || (T.F.TASK.ADD_TASK_BAR.DUE_BUTTON | translate) }}</span>
</button>
@if (state().date) {
<div class="action-item">
<button
mat-button
class="clear-btn"
data-test="add-task-bar-clear-due-btn"
(click)="clearDateWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_DATE | translate"
matTooltipPosition="above"
class="action-btn"
data-test="add-task-bar-due-btn"
(click)="openScheduleDialog()"
[class.has-value]="state().date"
>
<mat-icon>close</mat-icon>
<mat-icon>{{ state().time ? 'schedule' : 'event' }}</mat-icon>
<span>{{ dateDisplay() || (T.F.TASK.ADD_TASK_BAR.DUE_BUTTON | translate) }}</span>
</button>
}
@if (state().date) {
<button
mat-button
class="clear-btn"
data-test="add-task-bar-clear-due-btn"
(click)="clearDateWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_DATE | translate"
matTooltipPosition="above"
>
<mat-icon>close</mat-icon>
</button>
}
</div>
}
@if (state().deadlineDate) {
<button
mat-button
class="action-btn deadline-chip"
data-test="add-task-bar-deadline-btn"
(click)="openDeadlineDialog()"
[class.has-value]="true"
>
<mat-icon>{{ state().deadlineTime ? 'alarm' : 'flag' }}</mat-icon>
<span>{{ deadlineDateDisplay() }}</span>
</button>
<button
mat-button
class="clear-btn"
data-test="add-task-bar-clear-deadline-btn"
(click)="clearDeadlineWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_DEADLINE | translate"
matTooltipPosition="above"
>
<mat-icon>close</mat-icon>
</button>
}
@if (!isHideTagBtn() && allTags().length) {
<button
#tagsMenuTrigger
mat-button
class="action-btn"
[class.tags-mode]="hasTagsSelected() || state().newTagTitles.length > 0"
[matMenuTriggerFor]="tagsMenu"
[class.has-value]="hasTagsSelected() || hasNewTags()"
[class.has-new-tags]="hasNewTags()"
[class.menu-open]="isTagsMenuOpen()"
(click)="onTagsMenuClick()"
>
<!-- NOTE: we do it this way, since mat-button puts it inside the label otherwise leading to vertical alignment issues -->
<mat-icon [class.hide]="hasTagsSelected() || state().newTagTitles.length > 0"
>label</mat-icon
<div class="action-item">
<button
mat-button
class="action-btn deadline-chip"
data-test="add-task-bar-deadline-btn"
(click)="openDeadlineDialog()"
[class.has-value]="true"
>
<mat-icon>{{ state().deadlineTime ? 'alarm' : 'flag' }}</mat-icon>
<span>{{ deadlineDateDisplay() }}</span>
</button>
@if (!hasTagsSelected() && state().newTagTitles.length === 0) {
<ng-container>
<span>{{ T.F.TASK.ADD_TASK_BAR.TAGS_BUTTON | translate }}</span>
</ng-container>
} @else {
<div class="tags-display">
@for (tag of selectedTags(); track tag.id) {
<div class="tag-item">
<mat-icon [style.color]="tag.color || tag.theme?.primary">
{{ tag.icon || 'label' }}
</mat-icon>
<span class="tag-title">{{ tag.title }}</span>
</div>
}
@for (newTag of state().newTagTitles; track newTag) {
<div class="tag-item new-tag">
<mat-icon>new_releases</mat-icon>
<span class="tag-title">{{ newTag }}</span>
</div>
}
</div>
}
</button>
@if (hasTagsSelected() || hasNewTags()) {
<button
mat-button
class="clear-btn"
(click)="clearTagsWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_TAGS | translate"
data-test="add-task-bar-clear-deadline-btn"
(click)="clearDeadlineWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_DEADLINE | translate"
matTooltipPosition="above"
>
<mat-icon>close</mat-icon>
</button>
</div>
}
@if (!isHideTagBtn() && allTags().length) {
<div class="action-item">
<button
#tagsMenuTrigger
mat-button
class="action-btn"
[class.tags-mode]="hasTagsSelected() || state().newTagTitles.length > 0"
[matMenuTriggerFor]="tagsMenu"
[class.has-value]="hasTagsSelected() || hasNewTags()"
[class.has-new-tags]="hasNewTags()"
[class.menu-open]="isTagsMenuOpen()"
(click)="onTagsMenuClick()"
>
<!-- NOTE: we do it this way, since mat-button puts it inside the label otherwise leading to vertical alignment issues -->
<mat-icon [class.hide]="hasTagsSelected() || state().newTagTitles.length > 0"
>label</mat-icon
>
@if (!hasTagsSelected() && state().newTagTitles.length === 0) {
<ng-container>
<span>{{ T.F.TASK.ADD_TASK_BAR.TAGS_BUTTON | translate }}</span>
</ng-container>
} @else {
<div class="tags-display">
@for (tag of selectedTags(); track tag.id) {
<div class="tag-item">
<mat-icon [style.color]="tag.color || tag.theme?.primary">
{{ tag.icon || 'label' }}
</mat-icon>
<span class="tag-title">{{ tag.title }}</span>
</div>
}
@for (newTag of state().newTagTitles; track newTag) {
<div class="tag-item new-tag">
<mat-icon>new_releases</mat-icon>
<span class="tag-title">{{ newTag }}</span>
</div>
}
</div>
}
</button>
@if (hasTagsSelected() || hasNewTags()) {
<button
mat-button
class="clear-btn"
(click)="clearTagsWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_TAGS | translate"
matTooltipPosition="above"
>
<mat-icon>close</mat-icon>
</button>
}
</div>
}
<div class="action-item">
<button
#estimateMenuTrigger
mat-button
class="action-btn estimate-btn"
[matMenuTriggerFor]="estimateMenu"
[class.has-value]="state().estimate"
[class.menu-open]="isEstimateMenuOpen()"
(click)="onEstimateMenuClick()"
>
<mat-icon>hourglass_empty</mat-icon>
<span>{{
estimateDisplay() || (T.F.TASK.ADD_TASK_BAR.ESTIMATE_BUTTON | translate)
}}</span>
</button>
@if (state().estimate) {
<button
mat-button
class="clear-btn"
(click)="clearEstimateWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_ESTIMATE | translate"
matTooltipPosition="above"
>
<mat-icon>close</mat-icon>
</button>
}
}
</div>
<button
#estimateMenuTrigger
mat-button
class="action-btn estimate-btn"
[matMenuTriggerFor]="estimateMenu"
[class.has-value]="state().estimate"
[class.menu-open]="isEstimateMenuOpen()"
(click)="onEstimateMenuClick()"
>
<mat-icon>hourglass_empty</mat-icon>
<span>{{
estimateDisplay() || (T.F.TASK.ADD_TASK_BAR.ESTIMATE_BUTTON | translate)
}}</span>
</button>
@if (state().estimate) {
<div class="action-item">
<button
#repeatMenuTrigger
mat-button
class="clear-btn"
(click)="clearEstimateWithSyntax(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_ESTIMATE | translate"
matTooltipPosition="above"
class="action-btn"
data-test="add-task-bar-repeat-btn"
[matMenuTriggerFor]="repeatMenu"
[class.has-value]="state().repeatQuickSetting"
[class.menu-open]="isRepeatMenuOpen()"
(click)="onRepeatMenuClick()"
>
<mat-icon>close</mat-icon>
<mat-icon class="repeat-icon">repeat</mat-icon>
<span>{{
repeatDisplay() || (T.F.TASK.ADD_TASK_BAR.REPEAT_BUTTON | translate)
}}</span>
</button>
}
<button
#repeatMenuTrigger
mat-button
class="action-btn"
data-test="add-task-bar-repeat-btn"
[matMenuTriggerFor]="repeatMenu"
[class.has-value]="state().repeatQuickSetting"
[class.menu-open]="isRepeatMenuOpen()"
(click)="onRepeatMenuClick()"
>
<mat-icon class="repeat-icon">repeat</mat-icon>
<span>{{
repeatDisplay() || (T.F.TASK.ADD_TASK_BAR.REPEAT_BUTTON | translate)
}}</span>
</button>
@if (state().repeatQuickSetting) {
<button
mat-button
class="clear-btn"
data-test="add-task-bar-clear-repeat-btn"
(click)="clearRepeatSetting(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_REPEAT | translate"
matTooltipPosition="above"
>
<mat-icon>close</mat-icon>
</button>
}
@if (state().repeatQuickSetting) {
<button
mat-button
class="clear-btn"
data-test="add-task-bar-clear-repeat-btn"
(click)="clearRepeatSetting(); $event.stopPropagation()"
[matTooltip]="T.F.TASK.ADD_TASK_BAR.TOOLTIP_CLEAR_REPEAT | translate"
matTooltipPosition="above"
>
<mat-icon>close</mat-icon>
</button>
}
</div>
</div>
<!-------------- Menus ------------------->

View file

@ -64,6 +64,22 @@
gap: var(--s-quarter);
}
// On smaller screens, wrap the actions onto multiple rows instead of hiding
// them behind a horizontal scroll, so every button stays reachable.
@include mq(xs, max) {
flex-wrap: wrap;
overflow-x: visible;
gap: var(--s-quarter);
}
// Keeps an action button and its "clear" button together so a paired button
// never wraps away from the button it belongs to.
.action-item {
display: flex;
flex-shrink: 0;
align-items: center;
}
.action-btn {
cursor: pointer;
opacity: 0.4;