feat(kit): render the pressed toggles with UiButton

UiButton gains pressedClass, Core's class on a pressed button. The pressed
classes toggle through classList, so classes other code adds survive a
press, a caller's class change keeps them, a renamed pressedClass leaves
the button, and server-rendered markup carries them.

The Display, SDR, Radio and Cockpit enable, Cockpit weather and briefing,
mission replay, Street Level, TR-3B, overlay, voice and CCTV toggles now
render through it with the same ids, classes and aria-pressed values.
This commit is contained in:
Sameh Khamis committed 2026-11-01 11:59:30 -08:00
1 parent 5019bd9e8a
commit d98a7c81be
23 files changed
+212 -62

No files matched your search

+30 -7
View File
@@ -1,11 +1,17 @@
<script setup>
import { computed } from 'vue';
import { vClassToggle } from '../rail/classToggle.js';
// Core's button, with @halfpixel/ui-vue UiButton's props, classes and
// attribute contract so the two kits can merge: the button is always the root,
// attributes passed in win over its own (a `title` replaces the label's), and
// `pressed` renders aria-pressed only when it is passed. Core styles the
// button through the classes its callers pass; the kit classes carry no Core
// styles yet. Not mirrored yet: icon, loading, disabledReason and the
// missing-label warning.
// styles yet. Core adds `pressedClass`, its class on a pressed button (most
// toggles use `active`). The pressed classes toggle through `classList`, so
// classes other code adds to the button (a voice highlight) survive a press.
// Not mirrored yet: icon, loading, disabledReason and the missing-label
// warning.
defineOptions({ inheritAttrs: false });
const props = defineProps({
@@ -14,6 +20,8 @@ const props = defineProps({
/** The accessible name (aria-label) and, with nativeTitle, the tip. */
label: { type: String, default: undefined },
pressed: { type: Boolean, default: undefined },
/** Core's class on the button while it is pressed. */
pressedClass: { type: String, default: undefined },
disabled: { type: Boolean, default: false },
type: { type: String, default: 'button' },
/** The id of the form a submit button belongs to, where it sits outside it. */
@@ -21,6 +29,24 @@ const props = defineProps({
nativeTitle: { type: Boolean, default: true },
});
const emit = defineEmits(['click']);
// A pressedClass that changes keeps its old name, switched off, so the class
// leaves the button with it.
const pressedClasses = computed((previous) => {
const on = Boolean(props.pressed);
const classes = { 'ui-button--pressed': on };
for (const name of Object.keys(previous || {}))
if (name !== 'ui-button--pressed') classes[name] = false;
if (props.pressedClass) classes[props.pressedClass] = on;
return classes;
});
// Server-rendered markup carries the pressed classes in its class attribute.
const vPressedClasses = {
...vClassToggle,
getSSRProps: ({ value }) => ({
class: Object.keys(value).filter((name) => value[name]),
}),
};
</script>
<template>
@@ -28,11 +54,7 @@ const emit = defineEmits(['click']);
:type="props.type"
:form="props.form"
class="ui-button ui-focus"
:class="[
`ui-button--${props.variant}`,
`ui-button--${props.tone}`,
{ 'ui-button--pressed': props.pressed },
]"
:class="[`ui-button--${props.variant}`, `ui-button--${props.tone}`]"
:aria-label="props.label"
:title="props.nativeTitle ? props.label : undefined"
:aria-pressed="
@@ -40,6 +62,7 @@ const emit = defineEmits(['click']);
"
:disabled="props.disabled"
v-bind="$attrs"
v-pressed-classes="pressedClasses"
@click="emit('click', $event)"
>
<slot />