diff options
| author | Leitwolf | 2026-10-18 02:22:48 +0300 |
|---|---|---|
| committer | GitHub | 2026-10-18 02:22:48 +0300 |
| commit | c2eb3d208b37826167496cfdf657ffca33fef703 (patch) | |
| tree | 7e459be0fa4ac04c126413e405c3939e26cb86bc /frontend/src/components | |
| parent | 7190d8a0911b4cea07cc502a1e471f0889b6d20b (diff) | |
| parent | 43c431a793affe2fa41ead816268d6af760f72d0 (diff) | |
| download | website-c2eb3d208b37826167496cfdf657ffca33fef703.tar.gz | |
Merge pull request #40 from Progmasoft/design/landing-pages
Give each site a calm landing page with a "Why" section
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/WhyTabs.vue | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/frontend/src/components/WhyTabs.vue b/frontend/src/components/WhyTabs.vue new file mode 100644 index 0000000..3e0a04d --- /dev/null +++ b/frontend/src/components/WhyTabs.vue @@ -0,0 +1,97 @@ +<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> --> +<!-- SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 --> + +<script setup lang="ts"> +import { computed, ref, watch } from "vue"; +import type { WhyTab } from "../content/landing"; + +/** + * A row of tabs over one code panel. Each tab shows one strength with a short caption and a sample that the + * compiler repository checks. The arrow keys, Home and End move between tabs, as the ARIA tabs pattern asks. + */ +const props = defineProps<{ + tabs: readonly WhyTab[]; + copyLabel: string; + copiedLabel: string; + id: string; +}>(); + +const selected = ref(0); +const copied = ref(false); +const current = computed(() => props.tabs[selected.value] ?? props.tabs[0]); +const buttons = ref<HTMLButtonElement[]>([]); + +watch( + () => props.tabs, + () => { + selected.value = 0; + }, +); + +function select(index: number, focus = false) { + const count = props.tabs.length; + selected.value = (index + count) % count; + copied.value = false; + if (focus) buttons.value[selected.value]?.focus(); +} + +function onKey(event: KeyboardEvent) { + const moves: Record<string, () => number> = { + ArrowRight: () => selected.value + 1, + ArrowLeft: () => selected.value - 1, + Home: () => 0, + End: () => props.tabs.length - 1, + }; + const move = moves[event.key]; + if (!move) return; + event.preventDefault(); + select(move(), true); +} + +async function copy() { + try { + await navigator.clipboard.writeText(current.value.code); + copied.value = true; + } catch { + copied.value = false; + } +} +</script> + +<template> + <div class="why-tabs"> + <div class="why-tablist" role="tablist" @keydown="onKey"> + <button + v-for="(tab, index) in tabs" + :id="`${id}-tab-${index}`" + :key="tab.label" + ref="buttons" + role="tab" + type="button" + :aria-selected="index === selected" + :aria-controls="`${id}-panel`" + :tabindex="index === selected ? 0 : -1" + @click="select(index)" + > + {{ tab.label }} + </button> + </div> + <div + :id="`${id}-panel`" + class="why-panel" + role="tabpanel" + :aria-labelledby="`${id}-tab-${selected}`" + > + <p class="why-caption">{{ current.caption }}</p> + <div class="why-code" dir="ltr"> + <div class="why-code-bar"> + <span>{{ current.file }}</span> + <button type="button" class="why-copy" @click="copy"> + {{ copied ? copiedLabel : copyLabel }} + </button> + </div> + <pre><code>{{ current.code }}</code></pre> + </div> + </div> + </div> +</template> |