Skip to content

Commit 6477b90

Browse files
authored
feat: Show identity switcher directly on mobile identity manage page (#3655)
Show identity switcher directly on mobile identity manage page # Changes - Add identity switcher to header on mobile. - Animate sidebar menu on mobile. - Remove identity switcher from sidebar on mobile.
1 parent 5f091dc commit 6477b90

File tree

3 files changed

+33
-99
lines changed

3 files changed

+33
-99
lines changed

src/frontend/src/routes/(new-styling)/manage/(authenticated)/+layout.svelte

Lines changed: 33 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,10 @@
3434
import ChooseLanguage from "$lib/components/views/ChooseLanguage.svelte";
3535
import Avatar from "$lib/components/ui/Avatar.svelte";
3636
import { Trans } from "$lib/components/locale";
37-
import { getMetadataString, openIdLogo } from "$lib/utils/openID";
37+
import { getMetadataString } from "$lib/utils/openID";
3838
import ProgressRing from "$lib/components/ui/ProgressRing.svelte";
3939
import { analytics } from "$lib/utils/analytics/analytics";
4040
import { onMount } from "svelte";
41-
import PasskeyIcon from "$lib/components/icons/PasskeyIcon.svelte";
4241
4342
const { children, data }: LayoutProps = $props();
4443
@@ -191,12 +190,22 @@
191190

192191
<!-- Layout -->
193192
<div class="bg-bg-primary_alt flex min-h-[100dvh] flex-row">
194-
<!-- Sidebar -->
193+
<!-- Sidebar and backdrop on mobile -->
194+
<div
195+
onclick={() => (isMobileSidebarOpen = false)}
196+
class={[
197+
"bg-bg-overlay absolute inset-0 z-1 transition-opacity duration-200 ease-out sm:hidden",
198+
isMobileSidebarOpen ? "opacity-80" : "pointer-events-none opacity-0 ",
199+
]}
200+
aria-hidden="true"
201+
></div>
195202
<aside
196203
class={[
197204
"bg-bg-primary border-border-secondary flex w-74 flex-col sm:border-r sm:max-md:w-19",
198-
"max-sm:invisible max-sm:absolute max-sm:inset-0 max-sm:z-1 max-sm:w-full max-sm:overflow-y-auto",
199-
isMobileSidebarOpen && "max-sm:visible",
205+
"max-sm:absolute max-sm:start-0 max-sm:end-20 max-sm:top-0 max-sm:bottom-0 max-sm:z-1 max-sm:w-auto max-sm:overflow-y-auto max-sm:transition-transform max-sm:duration-200 max-sm:ease-out",
206+
isMobileSidebarOpen
207+
? "max-sm:translate-x-0"
208+
: "max-sm:pointer-events-none max-sm:translate-x-[-100%]",
200209
]}
201210
>
202211
<div class="h-[env(safe-area-inset-top)]"></div>
@@ -210,7 +219,7 @@
210219
</div>
211220
<button
212221
onclick={() => (isMobileSidebarOpen = false)}
213-
class="btn btn-tertiary btn-icon ms-auto sm:hidden"
222+
class="btn btn-tertiary btn-icon ms-auto -me-1.5 sm:hidden"
214223
aria-label={$t`Close menu`}
215224
>
216225
<XIcon class="size-5" />
@@ -228,66 +237,6 @@
228237
Internet Identity
229238
</div>
230239
</div>
231-
<!-- Mobile identity button-->
232-
{#if $lastUsedIdentitiesStore.selected !== undefined}
233-
{@const logo =
234-
"openid" in $lastUsedIdentitiesStore.selected.authMethod &&
235-
$lastUsedIdentitiesStore.selected.authMethod.openid.metadata !==
236-
undefined
237-
? openIdLogo(
238-
$lastUsedIdentitiesStore.selected.authMethod.openid.iss,
239-
$lastUsedIdentitiesStore.selected.authMethod.openid.metadata,
240-
)
241-
: undefined}
242-
<button
243-
onclick={() => (isIdentityPopoverOpen = true)}
244-
class={[
245-
"mx-4 mb-6 flex flex-row items-center gap-3 p-3 text-start sm:hidden",
246-
"border-border-secondary hover:bg-bg-primary_hover rounded-md border",
247-
]}
248-
aria-label={$t`Switch identity`}
249-
>
250-
<div class="relative">
251-
<Avatar size="sm">
252-
<UserIcon class="size-5" />
253-
</Avatar>
254-
<div
255-
class="bg-bg-primary_alt border-border-secondary absolute -right-1.25 -bottom-1.25 flex size-5 items-center justify-center rounded-full border"
256-
>
257-
{#if logo !== undefined}
258-
<div class="text-fg-tertiary size-3.25">
259-
{@html logo}
260-
</div>
261-
{:else}
262-
<PasskeyIcon class="text-fg-tertiary !size-3" />
263-
{/if}
264-
</div>
265-
</div>
266-
<div class="flex flex-col overflow-hidden">
267-
<div class="text-text-primary text-sm font-semibold">
268-
{$lastUsedIdentitiesStore.selected.name ??
269-
$lastUsedIdentitiesStore.selected.identityNumber}
270-
</div>
271-
<div
272-
class="text-text-tertiary overflow-hidden text-sm overflow-ellipsis whitespace-nowrap"
273-
>
274-
{#if "openid" in $lastUsedIdentitiesStore.selected.authMethod && $lastUsedIdentitiesStore.selected.authMethod.openid.metadata !== undefined}
275-
<span
276-
>{getMetadataString(
277-
$lastUsedIdentitiesStore.selected.authMethod.openid.metadata,
278-
"email",
279-
) ?? $t`Hidden email`}</span
280-
>
281-
{:else}
282-
<span>
283-
{$t`Passkey`}
284-
</span>
285-
{/if}
286-
</div>
287-
</div>
288-
<ChevronDownIcon class="text-text-primary ms-auto size-5 shrink-0" />
289-
</button>
290-
{/if}
291240
<!-- Navigation -->
292241
<nav class="flex flex-col gap-0.5 px-4">
293242
<ul class="contents">
@@ -355,27 +304,8 @@
355304
<div class="h-[env(safe-area-inset-top)]"></div>
356305
<!-- Header -->
357306
<header class="flex h-16 flex-row items-center px-4 sm:px-8 md:px-12">
358-
<!-- Mobile logo -->
359-
<Logo class="text-fg-primary h-4 sm:hidden" />
360-
<!-- Empty space between left and right content -->
361-
<div class="flex-1"></div>
362-
<!-- Identity button-->
363-
<button
364-
bind:this={identityButtonRef}
365-
onclick={() => (isIdentityPopoverOpen = true)}
366-
class="btn btn-tertiary gap-2.5 pr-3 max-sm:hidden sm:-mr-3"
367-
aria-label={$t`Switch identity`}
368-
>
369-
<Avatar size="xs">
370-
<UserIcon class="size-4" />
371-
</Avatar>
372-
<span>
373-
{data.identityInfo.name[0] ?? data.identityNumber.toString()}
374-
</span>
375-
<ChevronDownIcon size="1rem" />
376-
</button>
377307
<!-- Mobile menu button -->
378-
<div class="relative ms-2 sm:hidden">
308+
<div class="relative -ms-1.5 sm:hidden">
379309
<button
380310
onclick={() => (isMobileSidebarOpen = true)}
381311
class="btn btn-tertiary btn-icon"
@@ -394,6 +324,23 @@
394324
]}
395325
></div>
396326
</div>
327+
<!-- Empty space between left and right content -->
328+
<div class="flex-1"></div>
329+
<!-- Identity button-->
330+
<button
331+
bind:this={identityButtonRef}
332+
onclick={() => (isIdentityPopoverOpen = true)}
333+
class="btn btn-tertiary gap-2.5 pe-3 max-sm:-me-2 sm:-me-3"
334+
aria-label={$t`Switch identity`}
335+
>
336+
<Avatar size="xs">
337+
<UserIcon class="size-4" />
338+
</Avatar>
339+
<span>
340+
{data.identityInfo.name[0] ?? data.identityNumber.toString()}
341+
</span>
342+
<ChevronDownIcon size="1rem" />
343+
</button>
397344
</header>
398345
<!-- Page content -->
399346
<main class="flex flex-col px-4 py-5 sm:px-8 sm:py-3 md:px-12">

src/frontend/tests/e2e-playwright/fixtures/managePage.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class ManagePage {
99
}
1010

1111
async signOut() {
12-
await this.#openMenuOnMobile();
1312
await this.#page.getByRole("button", { name: "Switch identity" }).click();
1413
await this.#page
1514
.getByRole("group")
@@ -31,15 +30,6 @@ class ManagePage {
3130
this.#page.getByText("Your identity and sign-in methods at a glance."),
3231
).toBeVisible();
3332
}
34-
35-
async #openMenuOnMobile() {
36-
const menuButton = this.#page
37-
.getByRole("banner")
38-
.getByRole("button", { name: "Open menu" });
39-
if (await menuButton.isVisible()) {
40-
await menuButton.click();
41-
}
42-
}
4333
}
4434

4535
export const test = base.extend<{

src/frontend/tests/e2e-playwright/routes/manage/index.spec.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ test.describe("Dashboard Navigation", () => {
6666
await page.getByRole("link", { name: "Access and recovery" }).click();
6767

6868
// Switch to second identity
69-
if (await menuButton.isVisible()) {
70-
await menuButton.click();
71-
}
7269
await page.getByRole("button", { name: "Switch identity" }).click();
7370
auth2(page);
7471
await page.getByRole("button", { name: "Test 2" }).click();

0 commit comments

Comments
 (0)