Unverified Commit 3db66286 authored by Alireza Heidari's avatar Alireza Heidari
Browse files

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.
parent 39f808af
Loading
Loading
Loading
Loading
+56 −2
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@
 *   @tagClick="onTagClick" />
 */

import type { Ref } from "vue";

import type { AnyHistoryEntry, MyHistory } from "@/api/histories";
import { isMyHistory } from "@/api/histories";

@@ -76,6 +78,26 @@ interface Props {
     * @default []
     */
    selectedHistoryIds?: { id: string }[];

    /**
     * Whether cards are clickable for navigation
     * @type {boolean}
     * @default false
     */
    clickable?: boolean;

    /**
     * Item refs for keyboard navigation
     * @type {Record<string, Ref<InstanceType<typeof HistoryCard> | null>>}
     * @default {}
     */
    itemRefs?: Record<string, Ref<InstanceType<typeof HistoryCard> | null>>;

    /**
     * Range select anchor for keyboard navigation
     * @type {AnyHistoryEntry | undefined}
     */
    rangeSelectAnchor?: AnyHistoryEntry;
}

const props = withDefaults(defineProps<Props>(), {
@@ -83,6 +105,9 @@ const props = withDefaults(defineProps<Props>(), {
    publishedView: false,
    selectable: false,
    selectedHistoryIds: () => [],
    clickable: false,
    itemRefs: () => ({}),
    rangeSelectAnchor: undefined,
});

/**
@@ -112,14 +137,28 @@ const emit = defineEmits<{
     * @event updateFilter
     */
    (e: "updateFilter", key: string, value: any): void;

    /**
     * Emitted when a keyboard event occurs on a history card
     * @event on-key-down
     */
    (e: "on-key-down", history: AnyHistoryEntry, event: KeyboardEvent): void;

    /**
     * Emitted when a history card is clicked
     * @event on-history-card-click
     */
    (e: "on-history-card-click", history: AnyHistoryEntry, event: Event): void;
}>();
</script>

<template>
    <div class="history-card-list d-flex flex-wrap overflow-auto">
    <div class="history-card-list d-flex flex-wrap overflow-auto pt-1">
        <HistoryCard
            v-for="history in props.histories"
            :ref="props.itemRefs[history.id]"
            :key="history.id"
            tabindex="0"
            :history="history"
            :grid-view="props.gridView"
            :shared-view="props.sharedView"
@@ -127,15 +166,30 @@ const emit = defineEmits<{
            :archived-view="props.archivedView"
            :selectable="props.selectable"
            :selected="props.selectedHistoryIds.some((selected) => selected.id === history.id)"
            :clickable="props.clickable"
            class="history-card-in-list"
            :class="{ 'range-select-anchor-history': props.rangeSelectAnchor?.id === history.id }"
            @select="isMyHistory(history) && emit('select', history)"
            @tagClick="(...args) => emit('tagClick', ...args)"
            @refreshList="(...args) => emit('refreshList', ...args)"
            @updateFilter="(...args) => emit('updateFilter', ...args)" />
            @updateFilter="(...args) => emit('updateFilter', ...args)"
            @on-key-down="(...args) => emit('on-key-down', ...args)"
            @on-history-card-click="(...args) => emit('on-history-card-click', ...args)" />
    </div>
</template>

<style lang="scss" scoped>
@import "theme/blue.scss";

.history-card-list {
    container: cards-list / inline-size;

    .history-card-in-list {
        &.range-select-anchor-history {
            &:deep(.g-card-content) {
                box-shadow: 0 0 0 0.2rem transparentize($brand-primary, 0.75);
            }
        }
    }
}
</style>