Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions playwright/support/sections/ResultsSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ export class ResultsSection {
}

public async switchToSummary(): Promise<void> {
// NcCheckboxRadioSwitch renders a hidden <input type="radio"> with
// v-on="{ change: onToggle }". Dispatch the change event directly.
await this.summaryTab.dispatchEvent('change')
if (await this.summaryTab.isChecked()) {
return
}
// NcRadioGroupButton wraps the hidden radio input in a clickable container.
// Click the parent to trigger the same interaction path as a real user.
await this.summaryTab.locator('xpath=..').click()
}

public async switchToResponses(): Promise<void> {
await this.responsesTab.dispatchEvent('change')
if (await this.responsesTab.isChecked()) {
return
}
await this.responsesTab.locator('xpath=..').click()
}

/**
Expand Down
7 changes: 3 additions & 4 deletions playwright/support/sections/TopBarSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ export class TopBarSection {
if (await radio.isChecked()) {
return
}
// NcCheckboxRadioSwitch hides the input inside a label; click the label
// to trigger Vue's event chain rather than force-checking the hidden input
// (which would fail because Vue resets the controlled input state before
// Playwright can verify it)
// The radio input is visually hidden and wrapped in a clickable button-like
// container. Click the parent container to follow the real user interaction
// path and let Vue update the controlled state.
await radio.locator('xpath=..').click()
await this.page.waitForURL(viewRoutes[view])
}
Expand Down
81 changes: 40 additions & 41 deletions src/components/PillMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,39 @@

<template>
<div class="pill-menu">
<NcCheckboxRadioSwitch
v-for="option of options"
:key="option.id"
:aria-label="isMobile ? option.ariaLabel : null"
<NcRadioGroup
:label="groupLabel"
:modelValue="active.id"
:disabled="disabled || option.disabled"
class="pill-menu__toggle"
:class="{ 'pill-menu__toggle--icon-only': isMobile && option.icon }"
buttonVariant
buttonVariantGrouped="horizontal"
type="radio"
:value="option.id"
@update:modelValue="$emit('update:active', option)">
<template v-if="option.icon" #icon>
<NcIconSvgWrapper :svg="option.icon" />
</template>
{{ !isMobile || !option.icon ? option.title : null }}
</NcCheckboxRadioSwitch>
hideLabel
@update:modelValue="onUpdateActive">
<NcRadioGroupButton
v-for="option of options"
:key="option.id"
:value="option.id"
:aria-label="isMobile && option.icon ? option.ariaLabel : undefined"
:label="!isMobile || !option.icon ? option.title : undefined"
:disabled="disabled || option.disabled">
<template v-if="option.icon" #icon>
<NcIconSvgWrapper :svg="option.icon" />
</template>
</NcRadioGroupButton>
</NcRadioGroup>
</div>
</template>

<script>
import { useIsSmallMobile } from '@nextcloud/vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcRadioGroup from '@nextcloud/vue/components/NcRadioGroup'
import NcRadioGroupButton from '@nextcloud/vue/components/NcRadioGroupButton'

export default {
name: 'PillMenu',

components: {
NcCheckboxRadioSwitch,
NcIconSvgWrapper,
NcRadioGroup,
NcRadioGroupButton,
},

props: {
Expand All @@ -56,6 +57,14 @@ export default {
default: false,
},

/**
* Accessible label for the radio group
*/
groupLabel: {
type: String,
required: true,
},

/**
* List of available options
* `option: {id: string, title: string, ariaLabel: string, icon?: string}`
Expand All @@ -73,27 +82,17 @@ export default {
isMobile: useIsSmallMobile(),
}
},
}
</script>

<style lang="scss" scoped>
.pill-menu {
align-items: center;
align-self: flex-end;
display: flex;
justify-content: flex-end;

#{&} &__toggle {
// Make it a bit more condensed
:deep(.checkbox-radio-switch__content) {
flex-direction: row;
padding-block: 0;
}

// Make icon only toggle round intead of elipse
&--icon-only :deep(.checkbox-radio-switch__content) {
padding-inline: 0;
}
}
methods: {
/**
* Emit the full selected option to keep PillMenu API stable
*
* @param {string} optionId The selected option id
*/
onUpdateActive(optionId) {
const option = this.options.find((entry) => entry.id === optionId)
if (option) this.$emit('update:active', option)
},
},
}
</style>
</script>
1 change: 1 addition & 0 deletions src/components/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
v-if="!canOnlySubmit && currentView"
:active="currentView"
:options="availableViews"
:groupLabel="t('forms', 'View mode')"
@update:active="onChangeView" />
<NcButton
v-if="canShare && !sidebarOpened"
Expand Down
1 change: 1 addition & 0 deletions src/views/Results.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
v-model:active="activeResponseView"
:disabled="noSubmissions"
:options="responseViews"
:groupLabel="t('forms', 'View mode')"
class="response-actions__toggle"
@update:active="loadFormResults" />

Expand Down
Loading