Skip to content

Commit d7c0176

Browse files
committed
Implement redesigned access methods.
1 parent 4c5e725 commit d7c0176

File tree

18 files changed

+613
-195
lines changed

18 files changed

+613
-195
lines changed

src/frontend/src/lib/components/icons/GoogleIcon.svelte

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
<script lang="ts">
22
import type { SVGAttributes } from "svelte/elements";
33
4-
type Props = SVGAttributes<SVGSVGElement> & {
5-
size?: string;
6-
};
7-
8-
const { size = "1.25rem", ...props }: Props = $props();
4+
const { class: className, ...props }: SVGAttributes<SVGSVGElement> = $props();
95
</script>
106

11-
<svg viewBox="0 0 20 20" width={size} height={size} {...props}>
7+
<svg viewBox="0 0 20 20" {...props} class={["size-5", className]}>
128
<path
139
d="M10 .83a9.15 9.15 0 0 0-8.18 13.28c1.5 3 4.6 5.06 8.18 5.06 2.47 0 4.55-.82 6.07-2.22a8.95 8.95 0 0 0 2.73-6.74c0-.65-.06-1.28-.17-1.88H10v3.55h4.93a4.23 4.23 0 0 1-1.84 2.76 5.47 5.47 0 0 1-8.22-2.9h-.01A5.5 5.5 0 0 1 10 4.48a5 5 0 0 1 3.5 1.37l2.63-2.63A8.8 8.8 0 0 0 10 .83Z"
1410
class="fill-current"

src/frontend/src/lib/components/icons/PasskeyIcon.svelte

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
<script lang="ts">
22
import type { SVGAttributes } from "svelte/elements";
33
4-
type Props = SVGAttributes<SVGSVGElement> & {
5-
size?: string;
6-
};
7-
8-
const { size = "1.25rem", ...props }: Props = $props();
4+
const { class: className, ...props }: SVGAttributes<SVGSVGElement> = $props();
95
</script>
106

11-
<svg viewBox="0 0 20 20" width={size} height={size} {...props}>
7+
<svg viewBox="0 0 20 20" {...props} class={["size-5", className]}>
128
<path
139
d="M9.32 1.63c-.93 0-1.8.36-2.44 1l-.02.01-.2.22-.01.02a3.4 3.4 0 0 0-.77 2.2c0 .92.35 1.8 1 2.44h.01l.22.21.02.02c.6.5 1.38.76 2.2.76.92 0 1.8-.35 2.43-1h.02l.2-.23.01-.02c.5-.6.77-1.37.77-2.19 0-.93-.35-1.8-1-2.44V2.6l-.23-.2-.02-.01a3.4 3.4 0 0 0-2.19-.77Zm6.88 6.13a3.2 3.2 0 0 0-2.27.94v.01l-.2.2-.01.02a3.17 3.17 0 0 0-.73 2.04 3.18 3.18 0 0 0 2.3 3.07v4.27l1.37 1.38 1.84-1.84-1.38-1.37 1.38-1.38-1.15-1.15a3.3 3.3 0 0 0 1.48-1.14 3.2 3.2 0 0 0-.36-4.1v-.03l-.21-.18-.02-.02a3.17 3.17 0 0 0-2.04-.72Zm0 1.83c.28 0 .47.08.66.27l.06.07c.14.17.2.34.2.58 0 .27-.08.47-.26.65l-.07.07a.86.86 0 0 1-.59.2.84.84 0 0 1-.65-.27l-.06-.07a.86.86 0 0 1-.2-.58c0-.27.08-.47.26-.65l.08-.07c.16-.13.34-.2.57-.2Zm-6.88.76c-.95 0-1.9.11-2.83.34h-.01l-.34.1h-.01c-.83.22-1.7.54-2.6.94-.45.22-.84.54-1.12.94v.01l-.09.13v.01c-.23.38-.34.84-.34 1.34v2.16H13.9v-1.36l-.36-.25a4.34 4.34 0 0 1-1.42-1.62l-.14-.3a4.47 4.47 0 0 1-.36-1.55l-.04-.69-.68-.09-.43-.05h-.01l-.55-.05H9.9l-.59-.01z"
1410
class="fill-current"

src/frontend/src/lib/components/locale/LanguageSelector.svelte

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,29 @@
55
localeStore,
66
} from "$lib/stores/locale.store";
77
import Button from "$lib/components/ui/Button.svelte";
8-
import Popover from "$lib/components/ui/Popover.svelte";
98
import { ChevronDownIcon } from "@lucide/svelte";
10-
11-
let buttonRef = $state<HTMLElement>();
12-
let isOpen = $state(false);
9+
import Select from "$lib/components/ui/Select.svelte";
1310
</script>
1411

1512
{#if $locales.length > 1}
16-
<Button
17-
bind:element={buttonRef}
18-
onclick={() => (isOpen = true)}
19-
variant="tertiary"
20-
class="uppercase"
21-
>
22-
{$localeStore}
23-
<ChevronDownIcon class="size-5" />
24-
</Button>
25-
{/if}
26-
{#if isOpen}
27-
<Popover
28-
anchor={buttonRef}
29-
onClose={() => (isOpen = false)}
30-
direction="down"
31-
align="start"
32-
distance="0.25rem"
33-
responsive={false}
34-
class="!w-18 !p-1.5"
13+
<Select
14+
class="!w-18"
15+
options={$locales.map((locale) => ({
16+
value: locale,
17+
label: locale.toUpperCase(),
18+
}))}
19+
onChange={(value) => {
20+
// Switch back to locale auto-detection if locale matches browser
21+
if (value === availableBrowserLocale) {
22+
localeStore.reset();
23+
} else {
24+
localeStore.set(value);
25+
}
26+
}}
3527
>
36-
<ul class="flex flex-col">
37-
{#each $locales as locale}
38-
<li class="contents">
39-
<Button
40-
onclick={() => {
41-
isOpen = false;
42-
// Switch back to locale auto-detection if locale matches browser
43-
if (locale === availableBrowserLocale) {
44-
localeStore.reset();
45-
} else {
46-
localeStore.set(locale);
47-
}
48-
}}
49-
variant="tertiary"
50-
class={[
51-
"justify-start text-start uppercase",
52-
locale === $localeStore &&
53-
"[ul:not(:hover)_&]:bg-bg-primary_hover",
54-
]}
55-
>
56-
{locale}
57-
</Button>
58-
</li>
59-
{/each}
60-
</ul>
61-
</Popover>
28+
<Button variant="tertiary" class="uppercase">
29+
{$localeStore}
30+
<ChevronDownIcon class="size-5" />
31+
</Button>
32+
</Select>
6233
{/if}

src/frontend/src/lib/components/ui/NavItem.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
<ButtonOrAnchor
1717
{...props}
1818
class={[
19-
"flex h-10 flex-row items-center gap-3 rounded-sm px-3 py-2 text-base font-medium",
19+
"flex h-10 flex-row items-center gap-3 rounded-sm px-3 py-2 text-base font-medium outline-none",
2020
current
21-
? "text-text-primary bg-bg-active ring-border-primary hover:bg-bg-primary_hover ring-1 ring-inset"
21+
? "text-text-primary bg-bg-active ring-border-secondary hover:bg-bg-primary_hover ring-1 ring-inset"
2222
: "text-text-secondary hover:bg-bg-active",
2323
current
2424
? "[&_svg]:text-fg-primary"
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<script lang="ts" generics="T = number">
2+
import type { Component } from "svelte";
3+
import type { HTMLAttributes } from "svelte/elements";
4+
import Popover from "$lib/components/ui/Popover.svelte";
5+
import { isNullish, nonNullish } from "@dfinity/utils";
6+
import Button from "$lib/components/ui/Button.svelte";
7+
8+
type Option<T> = {
9+
value?: T;
10+
label: string;
11+
icon?: Component;
12+
selected?: boolean;
13+
onClick?: () => void;
14+
};
15+
type Direction = "up" | "right" | "down" | "left";
16+
type Align = "start" | "center" | "end";
17+
18+
type Props = HTMLAttributes<HTMLElement> & {
19+
options: Option<T>[];
20+
onChange?: (value: T) => void;
21+
direction?: Direction;
22+
align?: Align;
23+
distance?: string;
24+
};
25+
let {
26+
children,
27+
class: className,
28+
options,
29+
onChange,
30+
direction = "down",
31+
align = "start",
32+
distance = "0px",
33+
...props
34+
}: Props = $props();
35+
36+
let wrapperRef = $state<HTMLElement>();
37+
let isOpen = $state(false);
38+
39+
const childrenRef = $derived(
40+
wrapperRef?.firstElementChild as HTMLElement | undefined,
41+
);
42+
43+
const handleClick = (option: Option<T>, index: number) => {
44+
isOpen = false;
45+
option.onClick?.();
46+
onChange?.((option.value ?? index) as T);
47+
};
48+
49+
$effect(() => {
50+
if (isNullish(childrenRef)) {
51+
return;
52+
}
53+
const listener = () => (isOpen = true);
54+
childrenRef?.addEventListener("click", listener);
55+
return () => childrenRef.removeEventListener("click", listener);
56+
});
57+
</script>
58+
59+
<div bind:this={wrapperRef} class="contents">
60+
{@render children?.()}
61+
</div>
62+
63+
{#if isOpen && nonNullish(childrenRef)}
64+
<Popover
65+
{...props}
66+
anchor={childrenRef}
67+
{direction}
68+
{align}
69+
{distance}
70+
responsive={false}
71+
onClose={() => (isOpen = false)}
72+
class={["!w-max !p-1.5 !shadow-lg", className]}
73+
>
74+
<ul class="flex flex-col">
75+
{#each options as option, index}
76+
<li class="contents">
77+
<Button
78+
onclick={() => handleClick(option, index)}
79+
variant="tertiary"
80+
class={[
81+
"justify-start gap-2.5 !px-3 text-start",
82+
option.selected && "[ul:not(:hover)_&]:bg-bg-primary_hover",
83+
]}
84+
>
85+
{#if nonNullish(option.icon)}
86+
{@const Icon = option.icon}
87+
<div class="text-fg-quaternary [&_svg]:size-4">
88+
<Icon />
89+
</div>
90+
{/if}
91+
<span>{option.label}</span>
92+
</Button>
93+
</li>
94+
{/each}
95+
</ul>
96+
</Popover>
97+
{/if}

src/frontend/src/lib/components/views/AccessMethods.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@
268268

269269
{#if isAddAccessMethodWizardOpen}
270270
<AddAccessMethodWizard
271-
onOpenIDLinked={handleOpenIDLinked}
271+
onOpenIdLinked={handleOpenIDLinked}
272272
onPasskeyRegistered={handlePasskeyRegistered}
273273
onOtherDeviceRegistered={handleOtherDeviceRegistered}
274274
onClose={() => (isAddAccessMethodWizardOpen = false)}

src/frontend/src/lib/components/views/AccessMethodsPanel.svelte

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
<script lang="ts">
22
import Panel from "$lib/components/ui/Panel.svelte";
3-
import identityInfo from "$lib/stores/identity-info.state.svelte";
43
import { ChevronRightIcon } from "@lucide/svelte";
54
import Button from "$lib/components/ui/Button.svelte";
5+
6+
interface Props {
7+
totalAccessMethods: number;
8+
}
9+
10+
const { totalAccessMethods }: Props = $props();
611
</script>
712

813
<Panel>
@@ -24,7 +29,7 @@
2429
</h5>
2530
<div class="flex items-center">
2631
<h5 class="text-text-primary text-sm font-semibold nth-[2]:hidden">
27-
{identityInfo.totalAccessMethods}
32+
{totalAccessMethods}
2833
</h5>
2934
</div>
3035
<div class="flex items-center justify-center">

src/frontend/src/lib/components/views/IdentityInfoPanel.svelte

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<script lang="ts">
22
import Panel from "$lib/components/ui/Panel.svelte";
3-
import identityInfo from "$lib/stores/identity-info.state.svelte";
43
import Tooltip from "$lib/components/ui/Tooltip.svelte";
54
import { InfoIcon } from "@lucide/svelte";
6-
import PlaceHolder from "$lib/components/ui/PlaceHolder.svelte";
7-
import { fade } from "svelte/transition";
5+
6+
interface Props {
7+
name: string;
8+
}
9+
10+
const { name }: Props = $props();
811
</script>
912

1013
<Panel>
@@ -25,16 +28,9 @@
2528
Identity Name
2629
</h5>
2730
<div class="flex items-center">
28-
{#if identityInfo.name}
29-
<h5
30-
class="text-text-primary text-sm font-semibold nth-[2]:hidden"
31-
transition:fade={{ delay: 30 }}
32-
>
33-
{identityInfo.name}
34-
</h5>
35-
{:else}
36-
<PlaceHolder class="mr-8 h-4 w-full !rounded-sm" />
37-
{/if}
31+
<h5 class="text-text-primary text-sm font-semibold nth-[2]:hidden">
32+
{name}
33+
</h5>
3834
</div>
3935
<div class="flex items-center justify-center">
4036
<Tooltip

src/frontend/src/lib/components/wizards/addAccessMethod/AddAccessMethodWizard.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import { ConfirmAccessMethodWizard } from "$lib/components/wizards/confirmAccessMethod";
1414
1515
interface Props {
16-
onOpenIDLinked: (credential: OpenIdCredential) => void;
16+
onOpenIdLinked: (credential: OpenIdCredential) => void;
1717
onPasskeyRegistered: (credential: AuthnMethodData) => void;
1818
onOtherDeviceRegistered: () => void;
1919
onClose: () => void;
@@ -24,7 +24,7 @@
2424
}
2525
2626
const {
27-
onOpenIDLinked,
27+
onOpenIdLinked,
2828
onPasskeyRegistered,
2929
onOtherDeviceRegistered,
3030
onClose,
@@ -43,7 +43,7 @@
4343
4444
const handleContinueWithOpenId = async (config: OpenIdConfig) => {
4545
try {
46-
onOpenIDLinked(await addAccessMethodFlow.linkOpenIdAccount(config));
46+
onOpenIdLinked(await addAccessMethodFlow.linkOpenIdAccount(config));
4747
onClose();
4848
} catch (error) {
4949
onError(error);

src/frontend/src/lib/components/wizards/addAccessMethod/views/AddAccessMethod.svelte

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,6 @@
7676
/>
7777
{/if}
7878
<div class="flex flex-col items-stretch gap-3">
79-
<Tooltip
80-
label="You have reached the maximum number of passkeys."
81-
hidden={!maxPasskeysReached}
82-
>
83-
<Button
84-
onclick={continueWithPasskey}
85-
disabled={!isPasskeySupported || authenticating || maxPasskeysReached}
86-
size="xl"
87-
>
88-
<PasskeyIcon />
89-
Continue with passkey
90-
</Button>
91-
</Tooltip>
9279
<div class="flex flex-row flex-nowrap justify-stretch gap-3">
9380
{#each openIdProviders as provider}
9481
<Tooltip
@@ -113,6 +100,20 @@
113100
</Tooltip>
114101
{/each}
115102
</div>
103+
<Tooltip
104+
label="You have reached the maximum number of passkeys."
105+
hidden={!maxPasskeysReached}
106+
>
107+
<Button
108+
onclick={continueWithPasskey}
109+
variant="secondary"
110+
disabled={!isPasskeySupported || authenticating || maxPasskeysReached}
111+
size="xl"
112+
>
113+
<PasskeyIcon />
114+
Continue with passkey
115+
</Button>
116+
</Tooltip>
116117
</div>
117118
</div>
118119

0 commit comments

Comments
 (0)