Files
classroomio/packages/ui/src/custom/public-course/footer-nav.svelte
T

118 lines
3.8 KiB
Svelte

<script lang="ts">
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
import ArrowRightIcon from '@lucide/svelte/icons/arrow-right';
import { cn } from '../../tools';
import type { PublicCourseSidebarItem } from './types';
interface Props {
/** Previous item (or `null` if on the first item). */
prev?: PublicCourseSidebarItem | null;
/** Next item (or `null` if on the last item). */
next?: PublicCourseSidebarItem | null;
/** Builds the href for a target item. Falls back to plain buttons if omitted. */
hrefFor?: (item: PublicCourseSidebarItem) => string;
/** Click handler — used when `hrefFor` is omitted, or for SPA navigation alongside `href`. */
onNavigate?: (item: PublicCourseSidebarItem) => void;
/** Localized labels for the prev / next captions. */
prevLabel?: string;
nextLabel?: string;
class?: string;
}
let {
prev = null,
next = null,
hrefFor,
onNavigate,
prevLabel = 'Previous',
nextLabel = 'Next',
class: className
}: Props = $props();
const cardClass =
'ui:group ui:flex ui:flex-1 ui:items-center ui:gap-3 ui:rounded-lg ui:border ui:border-border ui:bg-background ui:px-4 ui:py-3 ui:text-left ui:transition-colors ui:hover:border-primary/40 ui:hover:bg-muted/50';
const prevHref = $derived(prev ? hrefFor?.(prev) : undefined);
const nextHref = $derived(next ? hrefFor?.(next) : undefined);
function handleAnchorClick(event: MouseEvent, item: PublicCourseSidebarItem) {
if (
!onNavigate ||
event.defaultPrevented ||
event.button !== 0 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey
) {
return;
}
event.preventDefault();
onNavigate(item);
}
</script>
{#snippet prevBody(item: PublicCourseSidebarItem, label: string)}
<ArrowLeftIcon class="ui:size-4 ui:text-muted-foreground ui:group-hover:text-primary" />
<div class="ui:flex ui:min-w-0 ui:flex-col">
<span class="ui:text-xs ui:font-medium ui:uppercase ui:tracking-wide ui:text-muted-foreground">
{label}
</span>
<span class="ui:truncate ui:text-sm ui:font-medium ui:text-foreground ui:group-hover:text-primary">
{item.title}
</span>
</div>
{/snippet}
{#snippet nextBody(item: PublicCourseSidebarItem, label: string)}
<div class="ui:flex ui:min-w-0 ui:flex-col ui:items-end">
<span class="ui:text-xs ui:font-medium ui:uppercase ui:tracking-wide ui:text-muted-foreground">
{label}
</span>
<span class="ui:truncate ui:text-sm ui:font-medium ui:text-foreground ui:group-hover:text-primary">
{item.title}
</span>
</div>
<ArrowRightIcon class="ui:size-4 ui:text-muted-foreground ui:group-hover:text-primary" />
{/snippet}
{#if prev || next}
<nav
class={cn('ui:mt-12 ui:flex ui:flex-col ui:gap-3 ui:border-t ui:border-border ui:pt-6 ui:sm:flex-row', className)}
aria-label="Lesson navigation"
>
{#if prev}
{#if prevHref}
<a href={prevHref} class={cardClass} onclick={(event) => handleAnchorClick(event, prev)}>
{@render prevBody(prev, prevLabel)}
</a>
{:else}
<button type="button" class={cardClass} onclick={() => onNavigate?.(prev)}>
{@render prevBody(prev, prevLabel)}
</button>
{/if}
{:else}
<div class="ui:flex-1"></div>
{/if}
{#if next}
{#if nextHref}
<a
href={nextHref}
class={cn(cardClass, 'ui:justify-end ui:text-right')}
onclick={(event) => handleAnchorClick(event, next)}
>
{@render nextBody(next, nextLabel)}
</a>
{:else}
<button type="button" class={cn(cardClass, 'ui:justify-end ui:text-right')} onclick={() => onNavigate?.(next)}>
{@render nextBody(next, nextLabel)}
</button>
{/if}
{:else}
<div class="ui:flex-1"></div>
{/if}
</nav>
{/if}