FIX - refine Radio Sky UI controls

This commit is contained in:
DerEchteAlec
2026-08-16 20:23:10 +02:00
parent 6fca2789c1
commit 30fb4360bc
6 changed files with 160 additions and 15 deletions
+2
View File
@@ -1887,6 +1887,8 @@ label.sky-list-item__row {
width: 100%;
min-width: 0;
height: 28px;
position: relative;
z-index: 1;
margin: 0;
padding: 0;
flex: 1;
@@ -61,4 +61,13 @@ describe('SkyRange', () => {
)
expect(tokens).toContain('--sky-shadow-glass-thumb-glow')
})
it('keeps the native range above its enlarged pointer target', () => {
const uiDirectory = fileURLToPath(new URL('..', import.meta.url))
const controls = readFileSync(`${uiDirectory}/controls.css`, 'utf8')
expect(controls).toMatch(
/\.sky-range__input\s*\{[^}]*position:\s*relative;[^}]*z-index:\s*1;/s,
)
})
})
+14
View File
@@ -236,6 +236,20 @@ label.sky-settings-row__title {
padding: 0;
}
.sky-settings-group__list
> .sky-field:not(.sky-field--has-label)
.sky-field__inner {
display: flex;
flex-direction: column;
justify-content: center;
}
.sky-settings-group__list
> .sky-field:not(.sky-field--has-label)
.sky-field__control {
margin: 0;
}
.sky-settings-group__list > .sky-field .sky-field__label {
font-size: 13px;
line-height: 18px;
@@ -65,4 +65,13 @@ describe('SkySettingsGroup', () => {
/\.sky-settings-group__title\s*\{[^}]*margin:\s*32px 16px 8px/s,
)
})
it('centers standalone fields without the base field negative margins', () => {
expect(settingsStyles).toMatch(
/\.sky-settings-group__list\s*>\s*\.sky-field:not\(\.sky-field--has-label\)\s*\.sky-field__inner\s*\{[^}]*display:\s*flex[^}]*justify-content:\s*center/s,
)
expect(settingsStyles).toMatch(
/\.sky-settings-group__list\s*>\s*\.sky-field:not\(\.sky-field--has-label\)\s*\.sky-field__control\s*\{[^}]*margin:\s*0/s,
)
})
})
@@ -78,6 +78,69 @@ describe('RadioApp Sky UI contract', () => {
expect(source).toContain('connectHistory(entry)')
})
it('keeps the volume slider inside its Sky list row and exposes speaker progress', () => {
const volumeTitle = ':title="phone.t(\'Apps.radio.volume\')"'
const volumeRowStart = source.lastIndexOf(
'<SkyListItem',
source.indexOf(volumeTitle),
)
const volumeRow = source.slice(
volumeRowStart,
source.indexOf(
'</SkyListItem>',
volumeRowStart,
) + '</SkyListItem>'.length,
)
expect(volumeRow).toContain('<template #after>')
expect(volumeRow).toContain('<template #inner>')
expect(volumeRow).toContain('<SkyRange')
expect(volumeRow).not.toContain(':caption=')
expect(source.match(/media-class="radio-audio-control-icon"/g)).toHaveLength(
2,
)
expect(source.match(/:strong-title="false"/g)).toHaveLength(2)
expect(
source.match(/title-font-size-ios="radio-audio-control-title"/g),
).toHaveLength(2)
expect(source).toMatch(
/:deep\(\.radio-audio-control-icon\)\s*\{[^}]*color:\s*var\(--sky-text\)/s,
)
expect(source).toMatch(
/:deep\(\.radio-audio-control-title\)\s*\{[^}]*font-size:\s*12px[^}]*font-weight:\s*400[^}]*line-height:\s*16px/s,
)
expect(source).toContain('inner-class="radio-speaker-content"')
expect(source).toMatch(
/:deep\(\.radio-speaker-content \.sky-list-item__subtitle\)\s*\{[^}]*color:\s*var\(--sky-muted\)[^}]*font-size:\s*12px[^}]*line-height:\s*16px/s,
)
expect(source).toContain(':aria-busy="radio.speakerPending || undefined"')
expect(source).toContain(
':disabled="!radio.data.connected || radio.speakerPending"',
)
})
it('uses the rounded Sky button treatment for both primary actions', () => {
const primaryActions = source.slice(
source.indexOf('<div class="radio-primary-action">'),
source.indexOf(
'</div>',
source.indexOf('<div class="radio-primary-action">'),
) + '</div>'.length,
)
expect(primaryActions.match(/<SkyButton\b/g)).toHaveLength(2)
expect(primaryActions.match(/\r?\n\s+rounded\r?\n/g)).toHaveLength(2)
})
it('keeps the recently connected list close to its section title', () => {
expect(source).toContain(
'<SkyList v-else inset strong class="radio-history-list">',
)
expect(source).toMatch(
/\.radio-history-list\s*\{[^}]*margin-top:\s*var\(--sky-space-2\)/s,
)
})
it('preserves theme, labels, loading, empty, error and feedback states', () => {
expect(source).toContain(':dark="phone.isDarkMode"')
expect(source).toContain(':label="phone.t(\'Apps.radio.name\')"')
+63 -15
View File
@@ -291,27 +291,42 @@ onBeforeUnmount(() => {
<span class="radio-unit">{{ phone.t('Apps.radio.mhz') }}</span>
</template>
</SkyField>
<SkyListItem>
<SkyListItem
media-class="radio-audio-control-icon"
:strong-title="false"
:title="phone.t('Apps.radio.volume')"
title-font-size-ios="radio-audio-control-title"
>
<template #media>
<Volume2 :size="20" aria-hidden="true" />
</template>
<SkyRange
id="radio-volume"
v-model="volumeInput"
:aria-label="phone.t('Apps.radio.volume')"
:aria-value-text="`${volumeInput}%`"
:caption="phone.t('Apps.radio.volume')"
:min="0"
:max="100"
:step="1"
@change="saveVolume"
>
<output for="radio-volume">{{ volumeInput }}%</output>
</SkyRange>
<template #after>
<output class="radio-volume-value" for="radio-volume">
{{ volumeInput }}%
</output>
</template>
<template #inner>
<SkyRange
id="radio-volume"
v-model="volumeInput"
class="radio-volume-slider"
:aria-label="phone.t('Apps.radio.volume')"
:aria-value-text="`${volumeInput}%`"
:min="0"
:max="100"
:step="1"
@change="saveVolume"
/>
</template>
</SkyListItem>
<SkyListItem
v-if="radio.data.speakerSupported"
inner-class="radio-speaker-content"
media-class="radio-audio-control-icon"
:aria-busy="radio.speakerPending || undefined"
:strong-title="false"
:title="phone.t('Apps.radio.speaker')"
title-font-size-ios="radio-audio-control-title"
:subtitle="phone.t('Apps.radio.speakerDescription')"
>
<template #media>
@@ -333,6 +348,7 @@ onBeforeUnmount(() => {
v-if="!radio.data.connected"
block
large
rounded
:disabled="radio.isLoading"
@click="connect()"
>
@@ -342,6 +358,7 @@ onBeforeUnmount(() => {
v-else
block
large
rounded
variant="danger"
:disabled="radio.isLoading"
@click="disconnect"
@@ -392,7 +409,7 @@ onBeforeUnmount(() => {
<Clock3 :size="32" aria-hidden="true" />
</template>
</SkyEmptyState>
<SkyList v-else inset strong>
<SkyList v-else inset strong class="radio-history-list">
<SkyListItem
v-for="entry in radio.data.history"
:key="`${entry.primary}-${entry.secondary}`"
@@ -549,10 +566,41 @@ onBeforeUnmount(() => {
font-weight: 600;
}
.radio-volume-slider {
width: 100%;
}
.radio-volume-value {
color: var(--sky-muted);
font-size: 12px;
font-variant-numeric: tabular-nums;
line-height: 16px;
}
:deep(.radio-audio-control-icon) {
color: var(--sky-text);
}
:deep(.radio-audio-control-title) {
font-size: 12px;
font-weight: 400;
line-height: 16px;
}
:deep(.radio-speaker-content .sky-list-item__subtitle) {
color: var(--sky-muted);
font-size: 12px;
line-height: 16px;
}
.radio-primary-action {
margin-top: 12px;
}
.radio-history-list {
margin-top: var(--sky-space-2);
}
.radio-error {
margin: 9px 4px 0;
color: var(--sky-danger);