Skip to content

Commit 3db6628

Browse files
committed
Adds keyboard navigation support to history card list
Enhances the history card list component with keyboard navigation capabilities by adding clickable props, item references for focus management, and range selection anchor highlighting. Introduces new event handlers for keyboard interactions and card clicks to enable accessible navigation through history entries. Adds visual styling for range selection anchor with primary color shadow to improve user feedback during keyboard navigation.
1 parent 39f808a commit 3db6628

File tree

1 file changed

+56
-2
lines changed

1 file changed

+56
-2
lines changed

client/src/components/History/HistoryCardList.vue

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
* @tagClick="onTagClick" />
2424
*/
2525
26+
import type { Ref } from "vue";
27+
2628
import type { AnyHistoryEntry, MyHistory } from "@/api/histories";
2729
import { isMyHistory } from "@/api/histories";
2830
@@ -76,13 +78,36 @@ interface Props {
7678
* @default []
7779
*/
7880
selectedHistoryIds?: { id: string }[];
81+
82+
/**
83+
* Whether cards are clickable for navigation
84+
* @type {boolean}
85+
* @default false
86+
*/
87+
clickable?: boolean;
88+
89+
/**
90+
* Item refs for keyboard navigation
91+
* @type {Record<string, Ref<InstanceType<typeof HistoryCard> | null>>}
92+
* @default {}
93+
*/
94+
itemRefs?: Record<string, Ref<InstanceType<typeof HistoryCard> | null>>;
95+
96+
/**
97+
* Range select anchor for keyboard navigation
98+
* @type {AnyHistoryEntry | undefined}
99+
*/
100+
rangeSelectAnchor?: AnyHistoryEntry;
79101
}
80102
81103
const props = withDefaults(defineProps<Props>(), {
82104
gridView: false,
83105
publishedView: false,
84106
selectable: false,
85107
selectedHistoryIds: () => [],
108+
clickable: false,
109+
itemRefs: () => ({}),
110+
rangeSelectAnchor: undefined,
86111
});
87112
88113
/**
@@ -112,30 +137,59 @@ const emit = defineEmits<{
112137
* @event updateFilter
113138
*/
114139
(e: "updateFilter", key: string, value: any): void;
140+
141+
/**
142+
* Emitted when a keyboard event occurs on a history card
143+
* @event on-key-down
144+
*/
145+
(e: "on-key-down", history: AnyHistoryEntry, event: KeyboardEvent): void;
146+
147+
/**
148+
* Emitted when a history card is clicked
149+
* @event on-history-card-click
150+
*/
151+
(e: "on-history-card-click", history: AnyHistoryEntry, event: Event): void;
115152
}>();
116153
</script>
117154

118155
<template>
119-
<div class="history-card-list d-flex flex-wrap overflow-auto">
156+
<div class="history-card-list d-flex flex-wrap overflow-auto pt-1">
120157
<HistoryCard
121158
v-for="history in props.histories"
159+
:ref="props.itemRefs[history.id]"
122160
:key="history.id"
161+
tabindex="0"
123162
:history="history"
124163
:grid-view="props.gridView"
125164
:shared-view="props.sharedView"
126165
:published-view="props.publishedView"
127166
:archived-view="props.archivedView"
128167
:selectable="props.selectable"
129168
:selected="props.selectedHistoryIds.some((selected) => selected.id === history.id)"
169+
:clickable="props.clickable"
170+
class="history-card-in-list"
171+
:class="{ 'range-select-anchor-history': props.rangeSelectAnchor?.id === history.id }"
130172
@select="isMyHistory(history) && emit('select', history)"
131173
@tagClick="(...args) => emit('tagClick', ...args)"
132174
@refreshList="(...args) => emit('refreshList', ...args)"
133-
@updateFilter="(...args) => emit('updateFilter', ...args)" />
175+
@updateFilter="(...args) => emit('updateFilter', ...args)"
176+
@on-key-down="(...args) => emit('on-key-down', ...args)"
177+
@on-history-card-click="(...args) => emit('on-history-card-click', ...args)" />
134178
</div>
135179
</template>
136180

137181
<style lang="scss" scoped>
182+
@import "theme/blue.scss";
183+
138184
.history-card-list {
139185
container: cards-list / inline-size;
186+
187+
.history-card-in-list {
188+
&.range-select-anchor-history {
189+
&:deep(.g-card-content) {
190+
box-shadow: 0 0 0 0.2rem transparentize($brand-primary, 0.75);
191+
}
192+
}
193+
}
140194
}
141195
</style>

0 commit comments

Comments
 (0)