feat(redesign): design-system tokens, 23 Vue components, game-aware shell + Fleet/Solo dashboard
All checks were successful
Test Asgard Runner / test (push) Successful in 4s
All checks were successful
Test Asgard Runner / test (push) Successful in 4s
Tokens ported 1:1 from the Claude Design bundle (colors/game-themes/type/spacing/elevation/motion/fonts) with the data-theme/data-game theming contract via useThemeGame (+ cc-skin-swap repaint guard). 23 design-system components reimplemented as Vue SFCs (core/forms/data/navigation/feedback/brand). DashboardLayout rebuilt as the game-aware shell (GameSwitcher, grouped nav with permission gating preserved, agent-health footer, topbar). DashboardView: Fleet + Solo with per-game GAME_FIELDS rows and the themed ECharts PlayersChart; Solo wired to the real server store, Fleet on representative data pending the multi-instance backend. All four game skins (Rust/Dune/Conan/Soulmask). vue-tsc + vite build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
67
frontend/src/components/ds/navigation/Tabs.vue
Normal file
67
frontend/src/components/ds/navigation/Tabs.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Tabs — horizontal tab bar. variant="pill" fills active tab with accent-soft;
|
||||
* variant="line" underlines with accent. Items can be bare strings or TabItem objects.
|
||||
*/
|
||||
import Icon from '@/components/ds/core/Icon.vue'
|
||||
|
||||
export interface TabItem {
|
||||
value: string
|
||||
label: string
|
||||
icon?: string
|
||||
count?: number | string
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
items: (string | TabItem)[]
|
||||
variant?: 'pill' | 'line'
|
||||
class?: string
|
||||
}>(),
|
||||
{ variant: 'pill' },
|
||||
)
|
||||
|
||||
const model = defineModel<string>({ required: true })
|
||||
|
||||
function normalise(it: string | TabItem): TabItem {
|
||||
return typeof it === 'string' ? { value: it, label: it } : it
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="['cc-tabs', `cc-tabs--${variant}`, props.class]"
|
||||
role="tablist"
|
||||
>
|
||||
<button
|
||||
v-for="raw in items"
|
||||
:key="normalise(raw).value"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="normalise(raw).value === model"
|
||||
class="cc-tab"
|
||||
@click="model = normalise(raw).value"
|
||||
>
|
||||
<Icon v-if="normalise(raw).icon" :name="normalise(raw).icon ?? ''" :size="15" />
|
||||
{{ normalise(raw).label }}
|
||||
<span v-if="normalise(raw).count != null" class="cc-tab__count">{{ normalise(raw).count }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.cc-tabs { display:flex; align-items:center; gap:2px; position:relative; }
|
||||
.cc-tabs--line { box-shadow: inset 0 -1px 0 var(--border-subtle); gap:4px; }
|
||||
.cc-tab {
|
||||
display:inline-flex; align-items:center; gap:7px; height:32px; padding:0 11px; border:0; background:transparent;
|
||||
font-family:var(--font-sans); font-size:var(--text-sm); font-weight:500; color:var(--text-tertiary);
|
||||
cursor:pointer; border-radius:var(--radius-sm); transition:var(--transition-colors); white-space:nowrap; position:relative;
|
||||
}
|
||||
.cc-tab:hover { color:var(--text-primary); background:var(--surface-hover); }
|
||||
.cc-tabs--pill .cc-tab[aria-selected="true"] { color:var(--accent-text); background:var(--accent-soft); }
|
||||
.cc-tabs--line .cc-tab { border-radius:0; height:38px; padding:0 4px; margin:0 7px; }
|
||||
.cc-tabs--line .cc-tab:hover { background:transparent; }
|
||||
.cc-tabs--line .cc-tab[aria-selected="true"] { color:var(--text-primary); box-shadow: inset 0 -2px 0 var(--accent); }
|
||||
.cc-tab__count { font-family:var(--font-mono); font-size:11px; padding:1px 6px; border-radius:var(--radius-pill); background:var(--surface-active); color:var(--text-tertiary); }
|
||||
.cc-tab[aria-selected="true"] .cc-tab__count { background:var(--accent-soft); color:var(--accent-text); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user