refactor(document): extract DocumentMobileMenu from DocumentTopBar
Second step of the Phase 5 split. The kebab dropdown — including clickOutside handling and its own mobileMenuOpen state — becomes its own component named after its visual region. The mobile snippet duplication inside DocumentTopBar is removed; the component owns its mobile-specific markup. TDD: DocumentMobileMenu.svelte.test.ts (7 tests) was red first. The component then made it green (kebab trigger, dropdown open/close on click, transcribe button gated on canWrite × isPdf × !transcribeMode, download link gated on filePath). DocumentTopBar wraps the new component in a md:hidden div so responsive behaviour is unchanged. Existing 18-test DocumentTopBar suite still passes. Refs #496. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
96
frontend/src/lib/document/DocumentMobileMenu.svelte
Normal file
96
frontend/src/lib/document/DocumentMobileMenu.svelte
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
import { clickOutside } from '$lib/shared/actions/clickOutside';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
canWrite: boolean;
|
||||||
|
isPdf: boolean;
|
||||||
|
transcribeMode: boolean;
|
||||||
|
filePath?: string | null;
|
||||||
|
originalFilename?: string | null;
|
||||||
|
fileUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
canWrite,
|
||||||
|
isPdf,
|
||||||
|
transcribeMode = $bindable(),
|
||||||
|
filePath = null,
|
||||||
|
originalFilename = null,
|
||||||
|
fileUrl
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let mobileMenuOpen = $state(false);
|
||||||
|
|
||||||
|
function startTranscribe() {
|
||||||
|
transcribeMode = true;
|
||||||
|
mobileMenuOpen = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div role="group" class="relative" use:clickOutside onclickoutside={() => (mobileMenuOpen = false)}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
||||||
|
aria-label={m.topbar_more_actions()}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded={mobileMenuOpen}
|
||||||
|
class="flex h-9 w-9 items-center justify-center rounded border border-line bg-muted transition hover:bg-accent focus-visible:ring-2 focus-visible:ring-primary"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/View-More-MD.svg"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-5 w-5"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if mobileMenuOpen}
|
||||||
|
<div
|
||||||
|
role="menu"
|
||||||
|
class="absolute top-full right-0 z-50 mt-1 min-w-[200px] rounded-md border border-line bg-surface p-2 shadow-lg"
|
||||||
|
>
|
||||||
|
{#if canWrite && isPdf && !transcribeMode}
|
||||||
|
<button
|
||||||
|
onclick={startTranscribe}
|
||||||
|
aria-label={m.transcription_mode_label()}
|
||||||
|
aria-pressed={false}
|
||||||
|
class="flex w-full items-center gap-2 rounded px-3 py-2 text-left text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-5 w-5 shrink-0"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{m.transcription_mode_label()}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if filePath}
|
||||||
|
<a
|
||||||
|
href={fileUrl}
|
||||||
|
download={originalFilename}
|
||||||
|
onclick={() => (mobileMenuOpen = false)}
|
||||||
|
class="flex items-center gap-2 rounded px-3 py-2 text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary"
|
||||||
|
title={m.doc_download_title()}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Download-MD.svg"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-5 w-5 shrink-0"
|
||||||
|
/>
|
||||||
|
{m.doc_download_title()}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
91
frontend/src/lib/document/DocumentMobileMenu.svelte.test.ts
Normal file
91
frontend/src/lib/document/DocumentMobileMenu.svelte.test.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { describe, it, expect, afterEach } from 'vitest';
|
||||||
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
|
import { page } from 'vitest/browser';
|
||||||
|
import DocumentMobileMenu from './DocumentMobileMenu.svelte';
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
const baseProps = {
|
||||||
|
canWrite: false,
|
||||||
|
isPdf: false,
|
||||||
|
transcribeMode: false,
|
||||||
|
filePath: null as string | null,
|
||||||
|
originalFilename: 'brief.pdf' as string | null,
|
||||||
|
fileUrl: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('DocumentMobileMenu', () => {
|
||||||
|
it('renders the kebab trigger button with the more-actions aria-label', async () => {
|
||||||
|
render(DocumentMobileMenu, { props: { ...baseProps, filePath: 'docs/x.pdf' } });
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: /weitere aktionen/i })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts with the dropdown closed (aria-expanded=false)', async () => {
|
||||||
|
render(DocumentMobileMenu, { props: { ...baseProps, filePath: 'docs/x.pdf' } });
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /weitere aktionen/i }))
|
||||||
|
.toHaveAttribute('aria-expanded', 'false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens the dropdown when the trigger is clicked', async () => {
|
||||||
|
render(DocumentMobileMenu, { props: { ...baseProps, filePath: 'docs/x.pdf' } });
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /weitere aktionen/i }).click();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /weitere aktionen/i }))
|
||||||
|
.toHaveAttribute('aria-expanded', 'true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the transcribe action inside the open menu when canWrite, isPdf, and not in transcribe mode', async () => {
|
||||||
|
render(DocumentMobileMenu, {
|
||||||
|
props: { ...baseProps, canWrite: true, isPdf: true, filePath: 'docs/x.pdf' }
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /weitere aktionen/i }).click();
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides the transcribe action when already in transcribeMode', async () => {
|
||||||
|
render(DocumentMobileMenu, {
|
||||||
|
props: {
|
||||||
|
...baseProps,
|
||||||
|
canWrite: true,
|
||||||
|
isPdf: true,
|
||||||
|
transcribeMode: true,
|
||||||
|
filePath: 'docs/x.pdf'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /weitere aktionen/i }).click();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /transkribieren/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the download link inside the open menu when filePath is present', async () => {
|
||||||
|
render(DocumentMobileMenu, {
|
||||||
|
props: { ...baseProps, filePath: 'docs/x.pdf', fileUrl: '/api/docs/x' }
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /weitere aktionen/i }).click();
|
||||||
|
|
||||||
|
await expect.element(page.getByRole('link', { name: /herunterladen/i })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('omits the download link when filePath is null', async () => {
|
||||||
|
render(DocumentMobileMenu, {
|
||||||
|
props: { ...baseProps, canWrite: true, isPdf: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /weitere aktionen/i }).click();
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('link', { name: /herunterladen/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import { clickOutside } from '$lib/shared/actions/clickOutside';
|
|
||||||
import PersonChipRow from '$lib/person/PersonChipRow.svelte';
|
import PersonChipRow from '$lib/person/PersonChipRow.svelte';
|
||||||
import OverflowPillButton from '$lib/shared/primitives/OverflowPillButton.svelte';
|
import OverflowPillButton from '$lib/shared/primitives/OverflowPillButton.svelte';
|
||||||
import DocumentMetadataDrawer from './DocumentMetadataDrawer.svelte';
|
import DocumentMetadataDrawer from './DocumentMetadataDrawer.svelte';
|
||||||
import DocumentTopBarTitle from './DocumentTopBarTitle.svelte';
|
import DocumentTopBarTitle from './DocumentTopBarTitle.svelte';
|
||||||
|
import DocumentMobileMenu from './DocumentMobileMenu.svelte';
|
||||||
import BackButton from '$lib/shared/primitives/BackButton.svelte';
|
import BackButton from '$lib/shared/primitives/BackButton.svelte';
|
||||||
|
|
||||||
type Person = { id: string; firstName?: string | null; lastName: string; displayName: string };
|
type Person = { id: string; firstName?: string | null; lastName: string; displayName: string };
|
||||||
@@ -58,90 +58,8 @@ const isPdf = $derived(!!doc.filePath && doc.contentType?.startsWith('applicatio
|
|||||||
const receivers = $derived(doc.receivers ?? []);
|
const receivers = $derived(doc.receivers ?? []);
|
||||||
const extraCount = $derived(Math.max(0, receivers.length - 2));
|
const extraCount = $derived(Math.max(0, receivers.length - 2));
|
||||||
const overflowPersons = $derived(receivers.slice(2));
|
const overflowPersons = $derived(receivers.slice(2));
|
||||||
|
|
||||||
let mobileMenuOpen = $state(false);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet transcribeBtn(mobile: boolean)}
|
|
||||||
<button
|
|
||||||
onclick={() => {
|
|
||||||
transcribeMode = true;
|
|
||||||
if (mobile) mobileMenuOpen = false;
|
|
||||||
}}
|
|
||||||
aria-label={m.transcription_mode_label()}
|
|
||||||
aria-pressed={false}
|
|
||||||
class={mobile
|
|
||||||
? 'flex w-full items-center gap-2 rounded px-3 py-2 text-left text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary'
|
|
||||||
: 'hidden items-center gap-1.5 rounded border border-primary px-3 py-1.5 font-sans text-[16px] font-medium text-ink transition hover:bg-primary hover:text-primary-fg focus-visible:ring-2 focus-visible:ring-primary md:flex'}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="h-5 w-5 shrink-0"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="1.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{m.transcription_mode_label()}
|
|
||||||
</button>
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
{#snippet transcribeStopBtn(mobile: boolean)}
|
|
||||||
<button
|
|
||||||
onclick={() => {
|
|
||||||
transcribeMode = false;
|
|
||||||
if (mobile) mobileMenuOpen = false;
|
|
||||||
}}
|
|
||||||
aria-label={m.transcription_mode_stop()}
|
|
||||||
aria-pressed={true}
|
|
||||||
class={mobile
|
|
||||||
? 'flex w-full items-center gap-2 rounded bg-primary px-3 py-2 text-left text-[16px] text-primary-fg transition focus-visible:ring-2 focus-visible:ring-primary'
|
|
||||||
: 'flex items-center gap-1.5 rounded bg-primary px-3 py-1.5 font-sans text-[16px] font-medium text-primary-fg transition focus-visible:ring-2 focus-visible:ring-primary'}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="h-5 w-5 shrink-0"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="1.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{m.transcription_mode_stop()}
|
|
||||||
</button>
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
{#snippet downloadLink(mobile: boolean)}
|
|
||||||
<a
|
|
||||||
href={fileUrl}
|
|
||||||
download={doc.originalFilename}
|
|
||||||
onclick={() => {
|
|
||||||
if (mobile) mobileMenuOpen = false;
|
|
||||||
}}
|
|
||||||
class={mobile
|
|
||||||
? 'flex items-center gap-2 rounded px-3 py-2 text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary'
|
|
||||||
: 'hidden rounded border border-transparent bg-muted p-1.5 text-ink transition hover:bg-accent focus-visible:ring-2 focus-visible:ring-primary md:block'}
|
|
||||||
title={m.doc_download_title()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Download-MD.svg"
|
|
||||||
alt=""
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-5 w-5 shrink-0"
|
|
||||||
/>
|
|
||||||
{#if mobile}{m.doc_download_title()}{/if}
|
|
||||||
</a>
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
<div data-topbar class="relative z-10 border-b border-line bg-surface shadow-sm">
|
<div data-topbar class="relative z-10 border-b border-line bg-surface shadow-sm">
|
||||||
<!-- Main row -->
|
<!-- Main row -->
|
||||||
<div class="flex h-[75px] shrink-0 items-center pr-4 xs:h-[88px]">
|
<div class="flex h-[75px] shrink-0 items-center pr-4 xs:h-[88px]">
|
||||||
@@ -180,7 +98,9 @@ let mobileMenuOpen = $state(false);
|
|||||||
onclick={() => (detailsOpen = !detailsOpen)}
|
onclick={() => (detailsOpen = !detailsOpen)}
|
||||||
aria-expanded={detailsOpen}
|
aria-expanded={detailsOpen}
|
||||||
aria-label={m.doc_details_toggle()}
|
aria-label={m.doc_details_toggle()}
|
||||||
class="ml-2 inline-flex min-h-[44px] shrink-0 items-center gap-1.5 rounded border px-3 py-1 font-sans text-sm font-semibold transition-colors {detailsOpen ? 'border-primary bg-primary text-primary-fg' : 'border-line text-ink-2 hover:bg-muted hover:text-ink'}"
|
class="ml-2 inline-flex min-h-[44px] shrink-0 items-center gap-1.5 rounded border px-3 py-1 font-sans text-sm font-semibold transition-colors {detailsOpen
|
||||||
|
? 'border-primary bg-primary text-primary-fg'
|
||||||
|
: 'border-line text-ink-2 hover:bg-muted hover:text-ink'}"
|
||||||
>
|
>
|
||||||
{m.doc_details_toggle()}
|
{m.doc_details_toggle()}
|
||||||
<svg
|
<svg
|
||||||
@@ -201,11 +121,51 @@ let mobileMenuOpen = $state(false);
|
|||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<div class="flex shrink-0 items-center gap-1.5 font-sans">
|
<div class="flex shrink-0 items-center gap-1.5 font-sans">
|
||||||
{#if canWrite && isPdf && !transcribeMode}
|
{#if canWrite && isPdf && !transcribeMode}
|
||||||
{@render transcribeBtn(false)}
|
<button
|
||||||
|
onclick={() => (transcribeMode = true)}
|
||||||
|
aria-label={m.transcription_mode_label()}
|
||||||
|
aria-pressed={false}
|
||||||
|
class="hidden items-center gap-1.5 rounded border border-primary px-3 py-1.5 font-sans text-[16px] font-medium text-ink transition hover:bg-primary hover:text-primary-fg focus-visible:ring-2 focus-visible:ring-primary md:flex"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-5 w-5 shrink-0"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{m.transcription_mode_label()}
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if transcribeMode}
|
{#if transcribeMode}
|
||||||
{@render transcribeStopBtn(false)}
|
<button
|
||||||
|
onclick={() => (transcribeMode = false)}
|
||||||
|
aria-label={m.transcription_mode_stop()}
|
||||||
|
aria-pressed={true}
|
||||||
|
class="flex items-center gap-1.5 rounded bg-primary px-3 py-1.5 font-sans text-[16px] font-medium text-primary-fg transition focus-visible:ring-2 focus-visible:ring-primary"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-5 w-5 shrink-0"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{m.transcription_mode_stop()}
|
||||||
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if canWrite && !transcribeMode}
|
{#if canWrite && !transcribeMode}
|
||||||
@@ -225,47 +185,31 @@ let mobileMenuOpen = $state(false);
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if doc.filePath && !transcribeMode}
|
{#if doc.filePath && !transcribeMode}
|
||||||
{@render downloadLink(false)}
|
<a
|
||||||
|
href={fileUrl}
|
||||||
|
download={doc.originalFilename}
|
||||||
|
class="hidden rounded border border-transparent bg-muted p-1.5 text-ink transition hover:bg-accent focus-visible:ring-2 focus-visible:ring-primary md:block"
|
||||||
|
title={m.doc_download_title()}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Download-MD.svg"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="h-5 w-5 shrink-0"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Kebab menu — mobile only, contains actions hidden below md -->
|
|
||||||
{#if (canWrite && isPdf) || doc.filePath}
|
{#if (canWrite && isPdf) || doc.filePath}
|
||||||
<div
|
<div class="md:hidden">
|
||||||
role="group"
|
<DocumentMobileMenu
|
||||||
class="relative md:hidden"
|
canWrite={canWrite}
|
||||||
use:clickOutside
|
isPdf={!!isPdf}
|
||||||
onclickoutside={() => (mobileMenuOpen = false)}
|
bind:transcribeMode={transcribeMode}
|
||||||
>
|
filePath={doc.filePath}
|
||||||
<button
|
originalFilename={doc.originalFilename}
|
||||||
type="button"
|
fileUrl={fileUrl}
|
||||||
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
/>
|
||||||
aria-label={m.topbar_more_actions()}
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded={mobileMenuOpen}
|
|
||||||
class="flex h-9 w-9 items-center justify-center rounded border border-line bg-muted transition hover:bg-accent focus-visible:ring-2 focus-visible:ring-primary"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/View-More-MD.svg"
|
|
||||||
alt=""
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-5 w-5"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if mobileMenuOpen}
|
|
||||||
<div
|
|
||||||
role="menu"
|
|
||||||
class="absolute top-full right-0 z-50 mt-1 min-w-[200px] rounded-md border border-line bg-surface p-2 shadow-lg"
|
|
||||||
>
|
|
||||||
{#if canWrite && isPdf && !transcribeMode}
|
|
||||||
{@render transcribeBtn(true)}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if doc.filePath}
|
|
||||||
{@render downloadLink(true)}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user