selectors

This commit is contained in:
Gauthier Roebroeck 2026-01-09 14:18:51 +08:00
parent 47465a4ff4
commit fbf8dcfa30
8 changed files with 270 additions and 0 deletions

View file

@ -71,6 +71,8 @@ declare module 'vue' {
PageHashKnownTable: typeof import('./components/pageHash/KnownTable.vue')['default']
PageHashMatchTable: typeof import('./components/pageHash/MatchTable.vue')['default']
PageHashUnknownTable: typeof import('./components/pageHash/UnknownTable.vue')['default']
PageSizeSelector: typeof import('./components/PageSizeSelector.vue')['default']
PresentationSelector: typeof import('./components/PresentationSelector.vue')['default']
ReleaseCard: typeof import('./components/release/Card.vue')['default']
RemoteFileList: typeof import('./components/RemoteFileList.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']

View file

@ -0,0 +1,11 @@
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import * as Stories from './PageSizeSelector.stories';
<Meta of={Stories} />
# PageSizeSelector
A button that will display a list of page sizes when clicked, optionally allowing unpaged content.
<Canvas of={Stories.Default} />

View file

@ -0,0 +1,62 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import PageSizeSelector from './PageSizeSelector.vue'
import { expect, fn } from 'storybook/test'
const meta = {
component: PageSizeSelector,
render: (args: object) => ({
components: { PageSizeSelector },
setup() {
return { args }
},
template: '<PageSizeSelector v-model="args.modelValue" v-bind="args" />',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
},
args: {
modelValue: 20,
'onUpdate:modelValue': fn(),
},
} satisfies Meta<typeof PageSizeSelector>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}
export const Clicked: Story = {
args: {},
play: async ({ canvas, userEvent }) => {
await expect(canvas.getByRole('button')).toBeEnabled()
await userEvent.click(canvas.getByRole('button'))
},
}
export const Unpaged: Story = {
args: {
modelValue: 'unpaged',
allowUnpaged: true,
},
play: async ({ canvas, userEvent }) => {
await expect(canvas.getByRole('button')).toBeEnabled()
await userEvent.click(canvas.getByRole('button'))
},
}
export const CustomSizes: Story = {
args: {
allowUnpaged: true,
sizes: [1, 2, 3, 4, 5],
},
play: async ({ canvas, userEvent }) => {
await expect(canvas.getByRole('button')).toBeEnabled()
await userEvent.click(canvas.getByRole('button'))
},
}

View file

@ -0,0 +1,58 @@
<template>
<v-menu>
<template #activator="{ props }">
<v-btn
v-bind="props"
icon="i-mdi:view-grid-plus"
:aria-label="
$formatMessage({
description: 'Page size selector button: aria-label',
defaultMessage: 'page size selector',
id: '2EMvSm',
})
"
/>
</template>
<v-list
:selected="[pageSize]"
color="primary"
>
<v-list-item
v-for="size in sizes"
:key="size"
:title="size"
:value="size"
@click="pageSize = size"
/>
<v-list-item
v-if="allowUnpaged"
:title="
$formatMessage({
description: 'Page size selector: unpaged option',
defaultMessage: 'All',
id: 'MC2JtF',
})
"
value="unpaged"
@click="pageSize = 'unpaged'"
/>
</v-list>
</v-menu>
</template>
<script setup lang="ts">
import type { PageSize } from '@/types/page'
const pageSize = defineModel<PageSize>({ required: true })
const { sizes = [20, 50, 100, 200, 500], allowUnpaged = false } = defineProps<{
sizes?: number[]
allowUnpaged?: boolean
}>()
</script>
<script lang="ts"></script>
<style scoped></style>

View file

@ -0,0 +1,11 @@
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import * as Stories from './PresentationSelector.stories';
<Meta of={Stories} />
# PresentationSelector
A button that will display a list of presentation modes when clicked.
<Canvas of={Stories.Default} />

View file

@ -0,0 +1,50 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import PresentationSelector from './PresentationSelector.vue'
import { expect, fn } from 'storybook/test'
const meta = {
component: PresentationSelector,
render: (args: object) => ({
components: { PresentationSelector },
setup() {
return { args }
},
template: '<PresentationSelector v-model="args.modelValue" v-bind="args" />',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
},
args: {
modelValue: 'grid',
modes: ['grid', 'list', 'table'],
'onUpdate:modelValue': fn(),
},
} satisfies Meta<typeof PresentationSelector>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}
export const Clicked: Story = {
args: {},
play: async ({ canvas, userEvent }) => {
await expect(canvas.getByRole('button')).toBeEnabled()
await userEvent.click(canvas.getByRole('button'))
},
}
export const LimitedSet: Story = {
args: {
modes: ['grid', 'list'],
},
play: async ({ canvas, userEvent }) => {
await expect(canvas.getByRole('button')).toBeEnabled()
await userEvent.click(canvas.getByRole('button'))
},
}

View file

@ -0,0 +1,75 @@
<template>
<v-menu>
<template #activator="{ props }">
<v-btn
v-bind="props"
:icon="allModes[currentMode].icon"
:aria-label="
$formatMessage({
description: 'Presentation selector button: aria-label',
defaultMessage: 'presentation selector',
id: 'sUl0GP',
})
"
/>
</template>
<v-list
:selected="[currentMode]"
color="primary"
>
<v-list-item
v-for="mode in modes"
:key="mode"
:title="allModes[mode].title"
:value="mode"
:prepend-icon="allModes[mode].icon"
@click="currentMode = mode"
/>
</v-list>
</v-menu>
</template>
<script setup lang="ts">
import { useIntl } from 'vue-intl'
import type { PresentationMode } from '@/types/libraries'
const intl = useIntl()
const allModes: Record<PresentationMode, { title: string; icon: string }> = {
grid: {
title: intl.formatMessage({
description: 'Presentation mode: grid',
defaultMessage: 'Grid',
id: 'Hv7lqq',
}),
icon: 'i-mdi:view-grid',
},
list: {
title: intl.formatMessage({
description: 'Presentation mode: list',
defaultMessage: 'List',
id: 'JNBONk',
}),
icon: 'i-mdi:view-list',
},
table: {
title: intl.formatMessage({
description: 'Presentation mode: table',
defaultMessage: 'Table',
id: 'efspoY',
}),
icon: 'i-mdi:table',
},
}
const currentMode = defineModel<PresentationMode>({ required: true, default: 'grid' })
const { modes } = defineProps<{
modes: PresentationMode[]
}>()
</script>
<script lang="ts"></script>
<style scoped></style>

View file

@ -0,0 +1 @@
export type PageSize = 'unpaged' | number