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 @@
+
+
+
+
+
+
+
+ {{
+ $formatMessage(
+ {
+ description: 'Selection bar: count of items',
+ defaultMessage: '{count} selected',
+ id: 'lqyIjk',
+ },
+ {
+ count: selectionStore.count,
+ },
+ )
+ }}
+
+
+
+
+
+
+
+
+
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 }
+})