mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
feat(kit): render the chips with UiChip and UiButton
UiChip is a non-interactive label chip and renders the voice card's chips. The pressable chips and pills are pressed toggles, so the rail card chips, map source chips and city and place pills render through UiButton with pressedClass, keeping their classes, attributes and the classes other code adds. The pills now report aria-pressed.
This commit is contained in:
1 parent
d98a7c81be
commit
28ca784d49
10 files changed
+81
-16
No files matched your search
+4
-1
@@ -65,7 +65,10 @@
|
||||
what the answering action returned. The Display, Radio, RTL-SDR, Cockpit,
|
||||
mission replay and style parameter sliders are the kit's `UiSlider`, a
|
||||
native range input that emits numbers, and the pressed toggles are the
|
||||
kit's `UiButton`, which gains `pressedClass`. `syncScopeControls`
|
||||
kit's `UiButton`, which gains `pressedClass`; so are the rail card
|
||||
chips, map source chips and location pills (which now report
|
||||
`aria-pressed`), and the voice card's chips
|
||||
are the kit's new `UiChip`. `syncScopeControls`
|
||||
(`./ui/display`) and `shouldHideCollapsedRightPanels` (`./ui/layout`) are
|
||||
deprecated: the Display view and the rail now own what they did. `./ui/input` imports Vue
|
||||
(samehkhamis).
|
||||
|
||||
@@ -61,7 +61,10 @@ adapter is gone.
|
||||
`active`); its pressed classes toggle through `classList`, so a class other
|
||||
code adds (a voice highlight) survives a press. Every pressed toggle in the
|
||||
views is a `UiButton` except Bhote Koshi's cinematic button and the
|
||||
tilt-map button, whose owners still write their state into the DOM.
|
||||
tilt-map button, whose owners still write their state into the DOM. Core's
|
||||
pressable chips and pills (rail card chips, map sources, cities and places)
|
||||
are `UiButton`s too; `UiChip` is ui-vue's non-interactive label chip, used by
|
||||
the voice card.
|
||||
`UiSlider` keeps the native range input where ui-vue draws a
|
||||
`role="slider"` track, so Core's keyboard, form behaviour and styling stay;
|
||||
it takes ui-vue's `v-model`, `min`, `max`, `step` and `disabled`, emits a
|
||||
|
||||
@@ -1132,6 +1132,9 @@
|
||||
"src/ui/kit/UiSlider.test.mjs",
|
||||
"src/ui/kit/UiButton.vue",
|
||||
"src/ui/kit/UiButton.test.mjs",
|
||||
"src/ui/kit/UiChip.vue",
|
||||
"src/ui/kit/UiChip.test.mjs",
|
||||
"src/ui/rail/RailChips.vue",
|
||||
"src/ui/displayPanelActions.test.mjs",
|
||||
"src/testSupport/vueDom.mjs",
|
||||
"build/vueLoader.js",
|
||||
|
||||
@@ -134,7 +134,8 @@
|
||||
"src/ui/rail/PanelCollapseButton.vue",
|
||||
"src/ui/rail/panelState.js",
|
||||
"src/sdr/gain.js",
|
||||
"src/ui/kit/UiSlider.js"
|
||||
"src/ui/kit/UiSlider.js",
|
||||
"src/ui/kit/UiChip.vue"
|
||||
],
|
||||
"external": ["vue", "pinia"]
|
||||
},
|
||||
@@ -667,7 +668,8 @@
|
||||
"src/ui/rail/utcClock.js",
|
||||
"src/ui/rail/RailTimeline.vue",
|
||||
"src/ui/rail/rangeValue.js",
|
||||
"src/ui/weather/WeatherReadout.vue"
|
||||
"src/ui/weather/WeatherReadout.vue",
|
||||
"src/ui/kit/UiButton.vue"
|
||||
],
|
||||
"external": ["vue"]
|
||||
},
|
||||
@@ -1066,7 +1068,8 @@
|
||||
"src/ui/stores/panelChrome.js",
|
||||
"src/ui/rail/panelState.js",
|
||||
"src/ui/rail/fieldValue.js",
|
||||
"src/ui/kit/UiSlider.js"
|
||||
"src/ui/kit/UiSlider.js",
|
||||
"src/ui/kit/UiButton.vue"
|
||||
],
|
||||
"external": ["cesium", "vue", "pinia"]
|
||||
},
|
||||
@@ -3076,7 +3079,8 @@
|
||||
"src/ui/stores/panelChrome.js",
|
||||
"src/ui/rail/panelState.js",
|
||||
"src/ui/rail/fieldValue.js",
|
||||
"src/ui/kit/UiSlider.js"
|
||||
"src/ui/kit/UiSlider.js",
|
||||
"src/ui/kit/UiButton.vue"
|
||||
],
|
||||
"external": [
|
||||
"hls.js",
|
||||
|
||||
@@ -898,7 +898,9 @@ test('row info and legends reconcile in place while chips retain focus and order
|
||||
controls.chips.push({ id: 'two', label: 'Two' });
|
||||
manager._refreshTogglePanel();
|
||||
assert.deepEqual(
|
||||
[...block.children].map((node) => node.className),
|
||||
[...block.children].map((node) =>
|
||||
[...node.classList].filter((name) => !name.startsWith('ui-')).join(' '),
|
||||
),
|
||||
[
|
||||
'data-toggle-chip chip-idle',
|
||||
'data-toggle-chip chip-idle',
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { after, before, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { installVueDom } from '../../testSupport/vueDom.mjs';
|
||||
|
||||
let mount;
|
||||
let UiChip;
|
||||
let uninstall;
|
||||
before(async () => {
|
||||
uninstall = installVueDom();
|
||||
({ mount } = await import('@vue/test-utils'));
|
||||
UiChip = (await import('./UiChip.vue')).default;
|
||||
});
|
||||
after(() => uninstall());
|
||||
|
||||
test('a chip is a span with the kit classes and Core’s', async () => {
|
||||
const wrapper = mount(UiChip, {
|
||||
attrs: { class: 'gev-voice-card-chip', 'data-tone': 'ok' },
|
||||
slots: { default: 'ok' },
|
||||
});
|
||||
const chip = wrapper.get('span');
|
||||
assert.deepEqual(chip.classes(), ['ui-chip', 'gev-voice-card-chip']);
|
||||
assert.equal(chip.attributes('data-tone'), 'ok');
|
||||
assert.equal(chip.text(), 'ok');
|
||||
await wrapper.setProps({ selected: true, mono: true });
|
||||
assert.ok(chip.classes().includes('ui-chip--selected'));
|
||||
assert.ok(chip.classes().includes('ui-mono'));
|
||||
wrapper.unmount();
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
// Core's chip, with @halfpixel/ui-vue UiChip's props and classes so the two
|
||||
// kits can merge: a label that says what something is, never a control.
|
||||
// Core styles it through the classes its callers pass; the kit classes carry
|
||||
// no Core styles yet. A chip that presses is a UiButton with pressedClass.
|
||||
defineProps({
|
||||
selected: { type: Boolean, default: false },
|
||||
mono: { type: Boolean, default: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="ui-chip"
|
||||
:class="{ 'ui-chip--selected': selected, 'ui-mono': mono }"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup>
|
||||
import UiButton from '../kit/UiButton.vue';
|
||||
|
||||
// Keyed chips shared by rail cards; the owner decides what a click does.
|
||||
defineProps({
|
||||
chips: { type: Array, default: () => [] },
|
||||
@@ -6,23 +8,23 @@ defineProps({
|
||||
});
|
||||
|
||||
const chipClass = (chip) =>
|
||||
`data-toggle-chip chip-${chip.state || (chip.active ? 'active' : 'idle')}${chip.active ? ' active' : ''}`;
|
||||
`data-toggle-chip chip-${chip.state || (chip.active ? 'active' : 'idle')}`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
<UiButton
|
||||
v-for="chip in chips"
|
||||
:key="chip.id"
|
||||
type="button"
|
||||
:data-chip-id="chip.id"
|
||||
:class="chipClass(chip)"
|
||||
:pressed="Boolean(chip.active)"
|
||||
pressed-class="active"
|
||||
:style="chip.color ? `--chip-color: ${chip.color}` : undefined"
|
||||
:data-chip-color="chip.color || undefined"
|
||||
:title="chip.title || undefined"
|
||||
:disabled="Boolean(chip.disabled)"
|
||||
:aria-pressed="String(Boolean(chip.active))"
|
||||
:aria-busy="String(Boolean(chip.busy))"
|
||||
@click="onSelect(chip)"
|
||||
>{{ chip.label }}</button
|
||||
>{{ chip.label }}</UiButton
|
||||
>
|
||||
</template>
|
||||
@@ -117,7 +117,7 @@ function selectMapSource(chip) {
|
||||
<span data-i18n id="map-source-label">MAP SOURCE</span>
|
||||
<span id="map-stack-status" class="pp-stack-status" v-class-toggle="{ warn: !!mapSource?.status.warn }" aria-live="polite">{{ mapSource ? mapSource.status.text : '3D' }}</span>
|
||||
</div>
|
||||
<div id="map-stack-chips" class="map-stack-chip-row" role="group" aria-label="Map source" data-i18n-aria-label><button v-for="chip in mapSource?.chips" :key="chip.id" type="button" class="map-stack-chip" v-class-toggle="{ active: chip.active, unavailable: !chip.available }" :data-stack-id="chip.id" :title="chip.title" :aria-pressed="String(chip.active)" :aria-disabled="String(!chip.available)" :aria-label="chip.ariaLabel" @click="selectMapSource(chip)"><span class="map-stack-chip-label">{{ chip.label }}</span><span v-if="chip.requirement" class="map-stack-chip-req">{{ chip.requirement }}</span></button></div>
|
||||
<div id="map-stack-chips" class="map-stack-chip-row" role="group" aria-label="Map source" data-i18n-aria-label><UiButton v-for="chip in mapSource?.chips" :key="chip.id" class="map-stack-chip" :pressed="Boolean(chip.active)" pressed-class="active" v-class-toggle="{ unavailable: !chip.available }" :data-stack-id="chip.id" :title="chip.title" :aria-disabled="String(!chip.available)" :aria-label="chip.ariaLabel" @click="selectMapSource(chip)"><span class="map-stack-chip-label">{{ chip.label }}</span><span v-if="chip.requirement" class="map-stack-chip-req">{{ chip.requirement }}</span></UiButton></div>
|
||||
</section>
|
||||
</div>
|
||||
<div id="style-mini-status" class="panel-mini-status" aria-live="polite">
|
||||
@@ -145,10 +145,10 @@ function selectMapSource(chip) {
|
||||
</div>
|
||||
<div id="location-bar-popover" class="dock-popover-content">
|
||||
<UiButton variant="icon" class="dock-pin-btn" data-pin-target="location-bar" :label="t('Pin location tray')" :title="t('Keep location tray open')" :pressed="!!panels['location-bar']?.pinned" @click="pin($event, 'location-bar')"><img src="/pin.svg" alt="" /></UiButton>
|
||||
<div id="poi-row" class="poi-row-container" v-class-toggle="{ expanded: !!loc?.poisExpanded }"><button v-for="poi in loc?.pois" :key="`${loc.poiCity}:${poi.index}`" type="button" class="poi-pill" v-class-toggle="{ active: poi.active }" :data-poi-index="poi.index" @click="locationControls?.choosePoi(poi.index)"><span class="poi-pill-key">{{ poi.key }}</span><span class="poi-pill-name">{{ poi.name }}</span></button></div>
|
||||
<div id="poi-row" class="poi-row-container" v-class-toggle="{ expanded: !!loc?.poisExpanded }"><UiButton v-for="poi in loc?.pois" :key="`${loc.poiCity}:${poi.index}`" class="poi-pill" :pressed="Boolean(poi.active)" pressed-class="active" :data-poi-index="poi.index" @click="locationControls?.choosePoi(poi.index)"><span class="poi-pill-key">{{ poi.key }}</span><span class="poi-pill-name">{{ poi.name }}</span></UiButton></div>
|
||||
<div class="location-bar-divider" id="location-bar-divider" v-class-toggle="{ visible: !!loc?.poisExpanded }"></div>
|
||||
<div class="location-city-row">
|
||||
<div id="location-pills" class="location-pills"><button v-for="city in loc?.cities" :key="city.id" type="button" class="location-pill" v-class-toggle="{ active: city.active }" :data-location-id="city.id" @click="locationControls?.chooseCity(city.id)">{{ city.label }}</button></div>
|
||||
<div id="location-pills" class="location-pills"><UiButton v-for="city in loc?.cities" :key="city.id" class="location-pill" :pressed="Boolean(city.active)" pressed-class="active" :data-location-id="city.id" @click="locationControls?.chooseCity(city.id)">{{ city.label }}</UiButton></div>
|
||||
<div class="location-search-wrap">
|
||||
<button id="search-toggle" class="search-toggle-btn" title="Search any location" data-i18n-title>
|
||||
<span>🔎</span>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { t } from '../../../i18n/index.js';
|
||||
import { ASSISTANT_NAME } from '../../../voice/control.js';
|
||||
import UiChip from '../../kit/UiChip.vue';
|
||||
|
||||
// Captions, plan and result, as VoiceCardControls (src/voice/voiceCard.js)
|
||||
// presents them. Captions update silently; the polite live region carries
|
||||
@@ -22,7 +23,7 @@ const emit = defineEmits(['dismiss']);
|
||||
<ol id="gev-voice-card-plan" class="gev-voice-card-plan" :aria-label="t('Plan')" :hidden="card.steps.length === 0"><li v-for="(step, index) in card.steps" :key="step.key ?? index" class="gev-voice-card-step" :data-status="step.status"><span class="gev-voice-card-step-label">{{ step.label }}</span><span class="gev-voice-card-sr">{{ step.statusText ? `, ${step.statusText}` : '' }}</span><span v-if="step.detail" class="gev-voice-card-step-detail">{{ step.detail }}</span></li></ol>
|
||||
<div id="gev-voice-card-result" class="gev-voice-card-result" :hidden="!card.result.visible">
|
||||
<div id="gev-voice-card-result-title" class="gev-voice-card-result-title">{{ card.result.title }}</div>
|
||||
<div id="gev-voice-card-chips" class="gev-voice-card-chips" :hidden="card.result.chips.length === 0"><span v-for="(chip, index) in card.result.chips" :key="index" class="gev-voice-card-chip" :data-tone="chip">{{ chip }}</span></div>
|
||||
<div id="gev-voice-card-chips" class="gev-voice-card-chips" :hidden="card.result.chips.length === 0"><UiChip v-for="(chip, index) in card.result.chips" :key="index" class="gev-voice-card-chip" :data-tone="chip">{{ chip }}</UiChip></div>
|
||||
<ul id="gev-voice-card-lines" class="gev-voice-card-lines"><li v-for="(line, index) in card.result.lines" :key="index">{{ line }}</li></ul>
|
||||
<ol id="gev-voice-card-referents" class="gev-voice-card-referents" :aria-label="t('Referenced items')" :hidden="card.result.referents.length === 0"><li v-for="(ref, index) in card.result.referents" :key="index" :value="ref.n">{{ ref.label }}</li></ol>
|
||||
<details id="gev-voice-card-notes" class="gev-voice-card-notes" :hidden="card.result.notes.length === 0">
|
||||
|
||||
Reference in new issue
Block a user