diff --git a/next-ui/src/components.d.ts b/next-ui/src/components.d.ts index 3e35e734..eee9029b 100644 --- a/next-ui/src/components.d.ts +++ b/next-ui/src/components.d.ts @@ -50,6 +50,7 @@ declare module 'vue' { ImportReadlistTable: typeof import('./components/import/readlist/Table.vue')['default'] ItemCard: typeof import('./components/item/card/ItemCard.vue')['default'] LayoutAppBar: typeof import('./components/layout/app/Bar.vue')['default'] + LayoutAppBarHolder: typeof import('./components/layout/app/BarHolder.vue')['default'] LayoutAppDrawer: typeof import('./components/layout/app/drawer/Drawer.vue')['default'] LayoutAppDrawerFooter: typeof import('./components/layout/app/drawer/Footer.vue')['default'] LayoutAppDrawerMenu: typeof import('./components/layout/app/drawer/menu/Menu.vue')['default'] @@ -82,6 +83,7 @@ declare module 'vue' { RemoteFileList: typeof import('./components/RemoteFileList.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SelectionBar: typeof import('./components/selection/Bar.vue')['default'] SeriesCard: typeof import('./components/series/card/SeriesCard.vue')['default'] SeriesDeletionWarning: typeof import('./components/series/DeletionWarning.vue')['default'] SeriesFormEditMetadata: typeof import('./components/series/form/EditMetadata.vue')['default'] diff --git a/next-ui/src/components/layout/app/Bar.stories.ts b/next-ui/src/components/layout/app/Bar.stories.ts new file mode 100644 index 00000000..caca5fb4 --- /dev/null +++ b/next-ui/src/components/layout/app/Bar.stories.ts @@ -0,0 +1,30 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import Bar from './Bar.vue' + +const meta = { + component: Bar, + render: (args: object) => ({ + components: { Bar }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + docs: { + description: { + component: '', + }, + }, + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {}, +} diff --git a/next-ui/src/components/layout/app/BarHolder.stories.ts b/next-ui/src/components/layout/app/BarHolder.stories.ts new file mode 100644 index 00000000..313980a3 --- /dev/null +++ b/next-ui/src/components/layout/app/BarHolder.stories.ts @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import BarHolder from './BarHolder.vue' +import { useSelectionStore } from '@/stores/selection' + +const meta = { + component: BarHolder, + render: (args: object) => ({ + components: { BarHolder }, + setup() { + const selectionStore = useSelectionStore() + return { args, selectionStore } + }, + template: + 'Add item', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + docs: { + description: { + component: + 'Holder to display the app bar, or the selection bar on top when items are selected.', + }, + }, + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {}, + play: () => { + const selectionStore = useSelectionStore() + selectionStore.selection = ['a', 'b'] + }, +} diff --git a/next-ui/src/components/layout/app/BarHolder.vue b/next-ui/src/components/layout/app/BarHolder.vue new file mode 100644 index 00000000..9b6dde8c --- /dev/null +++ b/next-ui/src/components/layout/app/BarHolder.vue @@ -0,0 +1,12 @@ + + + diff --git a/next-ui/src/components/selection/Bar.stories.ts b/next-ui/src/components/selection/Bar.stories.ts new file mode 100644 index 00000000..42f398f4 --- /dev/null +++ b/next-ui/src/components/selection/Bar.stories.ts @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +import Bar from './Bar.vue' +import { useSelectionStore } from '@/stores/selection' + +const meta = { + component: Bar, + render: (args: object) => ({ + components: { Bar }, + setup() { + return { args } + }, + template: '', + }), + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + docs: { + description: { + component: '', + }, + }, + }, + args: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: {}, + play: () => { + const selectionStore = useSelectionStore() + selectionStore.selection = ['a', 'b'] + }, +} diff --git a/next-ui/src/components/selection/Bar.vue b/next-ui/src/components/selection/Bar.vue new file mode 100644 index 00000000..19f10ff5 --- /dev/null +++ b/next-ui/src/components/selection/Bar.vue @@ -0,0 +1,38 @@ + + + + + + + diff --git a/next-ui/src/pages/libraries/[id]/series.vue b/next-ui/src/pages/libraries/[id]/series.vue index 06f9100d..84e2eb36 100644 --- a/next-ui/src/pages/libraries/[id]/series.vue +++ b/next-ui/src/pages/libraries/[id]/series.vue @@ -74,6 +74,7 @@ import { useAppStore } from '@/stores/app' import { useItemsPerPage, usePagination } from '@/composables/pagination' import { useSearchConditionLibraries } from '@/composables/search' import { storeToRefs } from 'pinia' +import { useSelectionStore } from '@/stores/selection' const route = useRoute('/libraries/[id]/series') const libraryId = route.params.id @@ -87,7 +88,8 @@ const presentationMode = appStore.getPresentationMode(`${libraryId}_series`, 'gr const { itemsPerPage } = useItemsPerPage(browsingPageSize) const { page0, page1, pageCount } = usePagination() -const selectedItems = ref([]) +const selectionStore = useSelectionStore() +const { selection: selectedItems } = storeToRefs(selectionStore) const preSelect = computed(() => selectedItems.value.length > 0) const { data: series } = useQuery(seriesListQuery, () => { diff --git a/next-ui/src/stores/selection.ts b/next-ui/src/stores/selection.ts new file mode 100644 index 00000000..21fc37c4 --- /dev/null +++ b/next-ui/src/stores/selection.ts @@ -0,0 +1,21 @@ +import { defineStore } from 'pinia' + +export const useSelectionStore = defineStore('selection', () => { + const route = useRoute() + + const selection = ref([]) + + watch( + () => route?.path, + () => { + selection.value = [] + }, + ) + + const isEmpty = computed(() => selection.value.length === 0) + const count = computed(() => selection.value.length) + + const clear = () => (selection.value = []) + + return { selection, count, isEmpty, clear } +})