@authrim/sveltekit 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -191
- package/README.md +527 -531
- package/dist/components/AuthProvider.svelte +56 -56
- package/dist/components/ProtectedRoute.svelte +71 -71
- package/dist/components/SignInButton.svelte +93 -93
- package/dist/components/SignOutButton.svelte +72 -72
- package/dist/components/UserProfile.svelte +71 -71
- package/dist/ui/account/LinkAccountButton.svelte +133 -133
- package/dist/ui/account/LinkedAccountsList.svelte +233 -233
- package/dist/ui/account/UnlinkAccountButton.svelte +179 -179
- package/dist/ui/forms/EmailCodeForm.svelte +224 -224
- package/dist/ui/forms/PasskeyConditionalInput.svelte +173 -173
- package/dist/ui/forms/SocialLoginButtons.svelte +209 -209
- package/dist/ui/helpers/AuthError.svelte +124 -124
- package/dist/ui/helpers/AuthLoading.svelte +83 -83
- package/dist/ui/helpers/OTPInput.svelte +214 -214
- package/dist/ui/helpers/ResendCodeButton.svelte +140 -140
- package/dist/ui/passkey/PasskeyDeleteButton.svelte +177 -177
- package/dist/ui/passkey/PasskeyList.svelte +225 -225
- package/dist/ui/passkey/PasskeyRegisterButton.svelte +52 -52
- package/dist/ui/session/SessionExpiryIndicator.svelte +109 -109
- package/dist/ui/session/SessionList.svelte +231 -231
- package/dist/ui/session/SessionRevokeButton.svelte +72 -72
- package/dist/ui/shared/Badge.svelte +100 -100
- package/dist/ui/shared/Button.svelte +213 -213
- package/dist/ui/shared/Card.svelte +85 -85
- package/dist/ui/shared/Input.svelte +192 -192
- package/dist/ui/shared/Spinner.svelte +75 -75
- package/dist/ui/styles/base.css +168 -168
- package/dist/ui/styles/theme.css +279 -279
- package/dist/ui/templates/AccountSettingsTemplate.svelte +205 -205
- package/dist/ui/templates/LoginTemplate.svelte +234 -234
- package/dist/ui/templates/SignUpTemplate.svelte +345 -345
- package/package.json +112 -111
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
ResendCodeButton Component
|
|
3
|
-
Button to resend verification code with countdown
|
|
4
|
-
-->
|
|
5
|
-
<script lang="ts">
|
|
6
|
-
import { createEventDispatcher, onDestroy } from 'svelte';
|
|
7
|
-
|
|
8
|
-
export let disabled = false;
|
|
9
|
-
export let remainingTime = 0;
|
|
10
|
-
export let text = 'Resend code';
|
|
11
|
-
export let loading = false;
|
|
12
|
-
let className = '';
|
|
13
|
-
export { className as class };
|
|
14
|
-
|
|
15
|
-
const dispatch = createEventDispatcher<{ click: void }>();
|
|
16
|
-
|
|
17
|
-
let displayTime = remainingTime;
|
|
18
|
-
let interval: ReturnType<typeof setInterval> | null = null;
|
|
19
|
-
|
|
20
|
-
// Reactive statement: Restart countdown when remainingTime prop changes from parent.
|
|
21
|
-
// This allows parent to reset the countdown (e.g., after successful resend).
|
|
22
|
-
// We always stop existing countdown first to prevent timer leaks.
|
|
23
|
-
$: {
|
|
24
|
-
stopCountdown();
|
|
25
|
-
displayTime = remainingTime;
|
|
26
|
-
if (remainingTime > 0) {
|
|
27
|
-
startCountdown();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function startCountdown() {
|
|
32
|
-
interval = setInterval(() => {
|
|
33
|
-
displayTime -= 1;
|
|
34
|
-
if (displayTime <= 0) stopCountdown();
|
|
35
|
-
}, 1000);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function stopCountdown() {
|
|
39
|
-
if (interval) {
|
|
40
|
-
clearInterval(interval);
|
|
41
|
-
interval = null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function handleClick() {
|
|
46
|
-
if (!disabled && displayTime <= 0 && !loading) {
|
|
47
|
-
dispatch('click');
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function formatTime(seconds: number): string {
|
|
52
|
-
const m = Math.floor(seconds / 60);
|
|
53
|
-
const s = seconds % 60;
|
|
54
|
-
return m > 0 ? `${m}:${s.toString().padStart(2, '0')}` : `${s}s`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
onDestroy(stopCountdown);
|
|
58
|
-
</script>
|
|
59
|
-
|
|
60
|
-
<button
|
|
61
|
-
type="button"
|
|
62
|
-
class="authrim-resend {className}"
|
|
63
|
-
class:authrim-resend--loading={loading}
|
|
64
|
-
disabled={disabled || displayTime > 0 || loading}
|
|
65
|
-
on:click={handleClick}
|
|
66
|
-
{...$$restProps}
|
|
67
|
-
>
|
|
68
|
-
{#if loading}
|
|
69
|
-
<span class="authrim-resend__spinner"></span>
|
|
70
|
-
{/if}
|
|
71
|
-
<span class="authrim-resend__text" class:authrim-resend__text--hidden={loading}>
|
|
72
|
-
{#if displayTime > 0}
|
|
73
|
-
{text} ({formatTime(displayTime)})
|
|
74
|
-
{:else}
|
|
75
|
-
{text}
|
|
76
|
-
{/if}
|
|
77
|
-
</span>
|
|
78
|
-
</button>
|
|
79
|
-
|
|
80
|
-
<style>
|
|
81
|
-
.authrim-resend {
|
|
82
|
-
position: relative;
|
|
83
|
-
display: inline-flex;
|
|
84
|
-
align-items: center;
|
|
85
|
-
justify-content: center;
|
|
86
|
-
gap: var(--authrim-space-2);
|
|
87
|
-
padding: var(--authrim-space-2) var(--authrim-space-3);
|
|
88
|
-
font-family: var(--authrim-font-sans);
|
|
89
|
-
font-size: var(--authrim-text-sm);
|
|
90
|
-
font-weight: 500;
|
|
91
|
-
color: var(--authrim-color-primary);
|
|
92
|
-
background: transparent;
|
|
93
|
-
border: none;
|
|
94
|
-
border-radius: var(--authrim-radius-sm);
|
|
95
|
-
cursor: pointer;
|
|
96
|
-
transition:
|
|
97
|
-
color var(--authrim-duration-fast) var(--authrim-ease-default),
|
|
98
|
-
background-color var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.authrim-resend:hover:not(:disabled) {
|
|
102
|
-
background: var(--authrim-color-primary-subtle);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.authrim-resend:focus-visible {
|
|
106
|
-
outline: none;
|
|
107
|
-
box-shadow: var(--authrim-shadow-focus);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.authrim-resend:disabled {
|
|
111
|
-
color: var(--authrim-color-text-muted);
|
|
112
|
-
cursor: not-allowed;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.authrim-resend:disabled:not(.authrim-resend--loading) {
|
|
116
|
-
opacity: 0.7;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.authrim-resend__spinner {
|
|
120
|
-
position: absolute;
|
|
121
|
-
width: 16px;
|
|
122
|
-
height: 16px;
|
|
123
|
-
border: 2px solid var(--authrim-color-primary-subtle);
|
|
124
|
-
border-top-color: var(--authrim-color-primary);
|
|
125
|
-
border-radius: 50%;
|
|
126
|
-
animation: spin 0.8s linear infinite;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
@keyframes spin {
|
|
130
|
-
to { transform: rotate(360deg); }
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.authrim-resend__text {
|
|
134
|
-
transition: opacity var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.authrim-resend__text--hidden {
|
|
138
|
-
opacity: 0;
|
|
139
|
-
}
|
|
140
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
ResendCodeButton Component
|
|
3
|
+
Button to resend verification code with countdown
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { createEventDispatcher, onDestroy } from 'svelte';
|
|
7
|
+
|
|
8
|
+
export let disabled = false;
|
|
9
|
+
export let remainingTime = 0;
|
|
10
|
+
export let text = 'Resend code';
|
|
11
|
+
export let loading = false;
|
|
12
|
+
let className = '';
|
|
13
|
+
export { className as class };
|
|
14
|
+
|
|
15
|
+
const dispatch = createEventDispatcher<{ click: void }>();
|
|
16
|
+
|
|
17
|
+
let displayTime = remainingTime;
|
|
18
|
+
let interval: ReturnType<typeof setInterval> | null = null;
|
|
19
|
+
|
|
20
|
+
// Reactive statement: Restart countdown when remainingTime prop changes from parent.
|
|
21
|
+
// This allows parent to reset the countdown (e.g., after successful resend).
|
|
22
|
+
// We always stop existing countdown first to prevent timer leaks.
|
|
23
|
+
$: {
|
|
24
|
+
stopCountdown();
|
|
25
|
+
displayTime = remainingTime;
|
|
26
|
+
if (remainingTime > 0) {
|
|
27
|
+
startCountdown();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function startCountdown() {
|
|
32
|
+
interval = setInterval(() => {
|
|
33
|
+
displayTime -= 1;
|
|
34
|
+
if (displayTime <= 0) stopCountdown();
|
|
35
|
+
}, 1000);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stopCountdown() {
|
|
39
|
+
if (interval) {
|
|
40
|
+
clearInterval(interval);
|
|
41
|
+
interval = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handleClick() {
|
|
46
|
+
if (!disabled && displayTime <= 0 && !loading) {
|
|
47
|
+
dispatch('click');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatTime(seconds: number): string {
|
|
52
|
+
const m = Math.floor(seconds / 60);
|
|
53
|
+
const s = seconds % 60;
|
|
54
|
+
return m > 0 ? `${m}:${s.toString().padStart(2, '0')}` : `${s}s`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onDestroy(stopCountdown);
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
class="authrim-resend {className}"
|
|
63
|
+
class:authrim-resend--loading={loading}
|
|
64
|
+
disabled={disabled || displayTime > 0 || loading}
|
|
65
|
+
on:click={handleClick}
|
|
66
|
+
{...$$restProps}
|
|
67
|
+
>
|
|
68
|
+
{#if loading}
|
|
69
|
+
<span class="authrim-resend__spinner"></span>
|
|
70
|
+
{/if}
|
|
71
|
+
<span class="authrim-resend__text" class:authrim-resend__text--hidden={loading}>
|
|
72
|
+
{#if displayTime > 0}
|
|
73
|
+
{text} ({formatTime(displayTime)})
|
|
74
|
+
{:else}
|
|
75
|
+
{text}
|
|
76
|
+
{/if}
|
|
77
|
+
</span>
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
<style>
|
|
81
|
+
.authrim-resend {
|
|
82
|
+
position: relative;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
gap: var(--authrim-space-2);
|
|
87
|
+
padding: var(--authrim-space-2) var(--authrim-space-3);
|
|
88
|
+
font-family: var(--authrim-font-sans);
|
|
89
|
+
font-size: var(--authrim-text-sm);
|
|
90
|
+
font-weight: 500;
|
|
91
|
+
color: var(--authrim-color-primary);
|
|
92
|
+
background: transparent;
|
|
93
|
+
border: none;
|
|
94
|
+
border-radius: var(--authrim-radius-sm);
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
transition:
|
|
97
|
+
color var(--authrim-duration-fast) var(--authrim-ease-default),
|
|
98
|
+
background-color var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.authrim-resend:hover:not(:disabled) {
|
|
102
|
+
background: var(--authrim-color-primary-subtle);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.authrim-resend:focus-visible {
|
|
106
|
+
outline: none;
|
|
107
|
+
box-shadow: var(--authrim-shadow-focus);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.authrim-resend:disabled {
|
|
111
|
+
color: var(--authrim-color-text-muted);
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.authrim-resend:disabled:not(.authrim-resend--loading) {
|
|
116
|
+
opacity: 0.7;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.authrim-resend__spinner {
|
|
120
|
+
position: absolute;
|
|
121
|
+
width: 16px;
|
|
122
|
+
height: 16px;
|
|
123
|
+
border: 2px solid var(--authrim-color-primary-subtle);
|
|
124
|
+
border-top-color: var(--authrim-color-primary);
|
|
125
|
+
border-radius: 50%;
|
|
126
|
+
animation: spin 0.8s linear infinite;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@keyframes spin {
|
|
130
|
+
to { transform: rotate(360deg); }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.authrim-resend__text {
|
|
134
|
+
transition: opacity var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.authrim-resend__text--hidden {
|
|
138
|
+
opacity: 0;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
PasskeyDeleteButton Component
|
|
3
|
-
Button to delete a passkey with confirmation
|
|
4
|
-
-->
|
|
5
|
-
<script lang="ts">
|
|
6
|
-
import { createEventDispatcher } from 'svelte';
|
|
7
|
-
import Spinner from '../shared/Spinner.svelte';
|
|
8
|
-
|
|
9
|
-
export let credentialId: string;
|
|
10
|
-
export let loading = false;
|
|
11
|
-
let className = '';
|
|
12
|
-
export { className as class };
|
|
13
|
-
|
|
14
|
-
const dispatch = createEventDispatcher<{
|
|
15
|
-
click: void;
|
|
16
|
-
confirm: void;
|
|
17
|
-
}>();
|
|
18
|
-
|
|
19
|
-
let showConfirm = false;
|
|
20
|
-
|
|
21
|
-
function handleClick() {
|
|
22
|
-
if (loading) return;
|
|
23
|
-
dispatch('click');
|
|
24
|
-
showConfirm = true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function handleConfirm() {
|
|
28
|
-
if (loading) return;
|
|
29
|
-
dispatch('confirm');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function handleCancel() {
|
|
33
|
-
showConfirm = false;
|
|
34
|
-
}
|
|
35
|
-
</script>
|
|
36
|
-
|
|
37
|
-
<div class="authrim-passkey-delete {className}" data-credential-id={credentialId}>
|
|
38
|
-
{#if showConfirm}
|
|
39
|
-
<div class="authrim-passkey-delete__confirm">
|
|
40
|
-
<span class="authrim-passkey-delete__confirm-text">Delete?</span>
|
|
41
|
-
<button
|
|
42
|
-
type="button"
|
|
43
|
-
class="authrim-passkey-delete__action authrim-passkey-delete__action--confirm"
|
|
44
|
-
disabled={loading}
|
|
45
|
-
on:click={handleConfirm}
|
|
46
|
-
>
|
|
47
|
-
{#if loading}
|
|
48
|
-
<Spinner size="sm" />
|
|
49
|
-
{:else}
|
|
50
|
-
Yes
|
|
51
|
-
{/if}
|
|
52
|
-
</button>
|
|
53
|
-
<button
|
|
54
|
-
type="button"
|
|
55
|
-
class="authrim-passkey-delete__action authrim-passkey-delete__action--cancel"
|
|
56
|
-
disabled={loading}
|
|
57
|
-
on:click={handleCancel}
|
|
58
|
-
>
|
|
59
|
-
No
|
|
60
|
-
</button>
|
|
61
|
-
</div>
|
|
62
|
-
{:else}
|
|
63
|
-
<button
|
|
64
|
-
type="button"
|
|
65
|
-
class="authrim-passkey-delete__btn"
|
|
66
|
-
disabled={loading}
|
|
67
|
-
on:click={handleClick}
|
|
68
|
-
aria-label="Delete passkey"
|
|
69
|
-
>
|
|
70
|
-
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
71
|
-
<path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 006 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 10.23 1.482l.149-.022.841 10.518A2.75 2.75 0 007.596 19h4.807a2.75 2.75 0 002.742-2.53l.841-10.52.149.023a.75.75 0 00.23-1.482A41.03 41.03 0 0014 4.193V3.75A2.75 2.75 0 0011.25 1h-2.5zM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4zM8.58 7.72a.75.75 0 00-1.5.06l.3 7.5a.75.75 0 101.5-.06l-.3-7.5zm4.34.06a.75.75 0 10-1.5-.06l-.3 7.5a.75.75 0 101.5.06l.3-7.5z" clip-rule="evenodd"/>
|
|
72
|
-
</svg>
|
|
73
|
-
</button>
|
|
74
|
-
{/if}
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<style>
|
|
78
|
-
.authrim-passkey-delete {
|
|
79
|
-
flex-shrink: 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.authrim-passkey-delete__btn {
|
|
83
|
-
display: flex;
|
|
84
|
-
align-items: center;
|
|
85
|
-
justify-content: center;
|
|
86
|
-
width: 36px;
|
|
87
|
-
height: 36px;
|
|
88
|
-
padding: 0;
|
|
89
|
-
background: transparent;
|
|
90
|
-
border: none;
|
|
91
|
-
border-radius: var(--authrim-radius-sm);
|
|
92
|
-
color: var(--authrim-color-text-muted);
|
|
93
|
-
cursor: pointer;
|
|
94
|
-
transition:
|
|
95
|
-
color var(--authrim-duration-fast) var(--authrim-ease-default),
|
|
96
|
-
background-color var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.authrim-passkey-delete__btn:hover:not(:disabled) {
|
|
100
|
-
color: var(--authrim-color-error);
|
|
101
|
-
background: var(--authrim-color-error-subtle);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.authrim-passkey-delete__btn:focus-visible {
|
|
105
|
-
outline: none;
|
|
106
|
-
box-shadow: var(--authrim-shadow-focus-error);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.authrim-passkey-delete__btn:disabled {
|
|
110
|
-
opacity: 0.5;
|
|
111
|
-
cursor: not-allowed;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.authrim-passkey-delete__btn svg {
|
|
115
|
-
width: 18px;
|
|
116
|
-
height: 18px;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.authrim-passkey-delete__confirm {
|
|
120
|
-
display: flex;
|
|
121
|
-
align-items: center;
|
|
122
|
-
gap: var(--authrim-space-2);
|
|
123
|
-
animation: fade-in var(--authrim-duration-fast) var(--authrim-ease-out);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
@keyframes fade-in {
|
|
127
|
-
from { opacity: 0; }
|
|
128
|
-
to { opacity: 1; }
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.authrim-passkey-delete__confirm-text {
|
|
132
|
-
font-size: var(--authrim-text-xs);
|
|
133
|
-
font-weight: 500;
|
|
134
|
-
color: var(--authrim-color-text-secondary);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.authrim-passkey-delete__action {
|
|
138
|
-
padding: var(--authrim-space-1) var(--authrim-space-2);
|
|
139
|
-
font-family: var(--authrim-font-sans);
|
|
140
|
-
font-size: var(--authrim-text-xs);
|
|
141
|
-
font-weight: 500;
|
|
142
|
-
border: none;
|
|
143
|
-
border-radius: var(--authrim-radius-sm);
|
|
144
|
-
cursor: pointer;
|
|
145
|
-
transition:
|
|
146
|
-
background-color var(--authrim-duration-fast) var(--authrim-ease-default),
|
|
147
|
-
transform var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.authrim-passkey-delete__action:active:not(:disabled) {
|
|
151
|
-
transform: scale(0.95);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.authrim-passkey-delete__action:disabled {
|
|
155
|
-
opacity: 0.5;
|
|
156
|
-
cursor: not-allowed;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.authrim-passkey-delete__action--confirm {
|
|
160
|
-
background: var(--authrim-color-error);
|
|
161
|
-
color: white;
|
|
162
|
-
min-width: 40px;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.authrim-passkey-delete__action--confirm:hover:not(:disabled) {
|
|
166
|
-
background: var(--authrim-color-error-hover);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.authrim-passkey-delete__action--cancel {
|
|
170
|
-
background: var(--authrim-color-bg-subtle);
|
|
171
|
-
color: var(--authrim-color-text-secondary);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.authrim-passkey-delete__action--cancel:hover:not(:disabled) {
|
|
175
|
-
background: var(--authrim-color-bg-muted);
|
|
176
|
-
}
|
|
177
|
-
</style>
|
|
1
|
+
<!--
|
|
2
|
+
PasskeyDeleteButton Component
|
|
3
|
+
Button to delete a passkey with confirmation
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { createEventDispatcher } from 'svelte';
|
|
7
|
+
import Spinner from '../shared/Spinner.svelte';
|
|
8
|
+
|
|
9
|
+
export let credentialId: string;
|
|
10
|
+
export let loading = false;
|
|
11
|
+
let className = '';
|
|
12
|
+
export { className as class };
|
|
13
|
+
|
|
14
|
+
const dispatch = createEventDispatcher<{
|
|
15
|
+
click: void;
|
|
16
|
+
confirm: void;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
let showConfirm = false;
|
|
20
|
+
|
|
21
|
+
function handleClick() {
|
|
22
|
+
if (loading) return;
|
|
23
|
+
dispatch('click');
|
|
24
|
+
showConfirm = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleConfirm() {
|
|
28
|
+
if (loading) return;
|
|
29
|
+
dispatch('confirm');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleCancel() {
|
|
33
|
+
showConfirm = false;
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<div class="authrim-passkey-delete {className}" data-credential-id={credentialId}>
|
|
38
|
+
{#if showConfirm}
|
|
39
|
+
<div class="authrim-passkey-delete__confirm">
|
|
40
|
+
<span class="authrim-passkey-delete__confirm-text">Delete?</span>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
class="authrim-passkey-delete__action authrim-passkey-delete__action--confirm"
|
|
44
|
+
disabled={loading}
|
|
45
|
+
on:click={handleConfirm}
|
|
46
|
+
>
|
|
47
|
+
{#if loading}
|
|
48
|
+
<Spinner size="sm" />
|
|
49
|
+
{:else}
|
|
50
|
+
Yes
|
|
51
|
+
{/if}
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
class="authrim-passkey-delete__action authrim-passkey-delete__action--cancel"
|
|
56
|
+
disabled={loading}
|
|
57
|
+
on:click={handleCancel}
|
|
58
|
+
>
|
|
59
|
+
No
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
{:else}
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
class="authrim-passkey-delete__btn"
|
|
66
|
+
disabled={loading}
|
|
67
|
+
on:click={handleClick}
|
|
68
|
+
aria-label="Delete passkey"
|
|
69
|
+
>
|
|
70
|
+
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
71
|
+
<path fill-rule="evenodd" d="M8.75 1A2.75 2.75 0 006 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 10.23 1.482l.149-.022.841 10.518A2.75 2.75 0 007.596 19h4.807a2.75 2.75 0 002.742-2.53l.841-10.52.149.023a.75.75 0 00.23-1.482A41.03 41.03 0 0014 4.193V3.75A2.75 2.75 0 0011.25 1h-2.5zM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4zM8.58 7.72a.75.75 0 00-1.5.06l.3 7.5a.75.75 0 101.5-.06l-.3-7.5zm4.34.06a.75.75 0 10-1.5-.06l-.3 7.5a.75.75 0 101.5.06l.3-7.5z" clip-rule="evenodd"/>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
{/if}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
.authrim-passkey-delete {
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.authrim-passkey-delete__btn {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
width: 36px;
|
|
87
|
+
height: 36px;
|
|
88
|
+
padding: 0;
|
|
89
|
+
background: transparent;
|
|
90
|
+
border: none;
|
|
91
|
+
border-radius: var(--authrim-radius-sm);
|
|
92
|
+
color: var(--authrim-color-text-muted);
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
transition:
|
|
95
|
+
color var(--authrim-duration-fast) var(--authrim-ease-default),
|
|
96
|
+
background-color var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.authrim-passkey-delete__btn:hover:not(:disabled) {
|
|
100
|
+
color: var(--authrim-color-error);
|
|
101
|
+
background: var(--authrim-color-error-subtle);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.authrim-passkey-delete__btn:focus-visible {
|
|
105
|
+
outline: none;
|
|
106
|
+
box-shadow: var(--authrim-shadow-focus-error);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.authrim-passkey-delete__btn:disabled {
|
|
110
|
+
opacity: 0.5;
|
|
111
|
+
cursor: not-allowed;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.authrim-passkey-delete__btn svg {
|
|
115
|
+
width: 18px;
|
|
116
|
+
height: 18px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.authrim-passkey-delete__confirm {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: var(--authrim-space-2);
|
|
123
|
+
animation: fade-in var(--authrim-duration-fast) var(--authrim-ease-out);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@keyframes fade-in {
|
|
127
|
+
from { opacity: 0; }
|
|
128
|
+
to { opacity: 1; }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.authrim-passkey-delete__confirm-text {
|
|
132
|
+
font-size: var(--authrim-text-xs);
|
|
133
|
+
font-weight: 500;
|
|
134
|
+
color: var(--authrim-color-text-secondary);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.authrim-passkey-delete__action {
|
|
138
|
+
padding: var(--authrim-space-1) var(--authrim-space-2);
|
|
139
|
+
font-family: var(--authrim-font-sans);
|
|
140
|
+
font-size: var(--authrim-text-xs);
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
border: none;
|
|
143
|
+
border-radius: var(--authrim-radius-sm);
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
transition:
|
|
146
|
+
background-color var(--authrim-duration-fast) var(--authrim-ease-default),
|
|
147
|
+
transform var(--authrim-duration-fast) var(--authrim-ease-default);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.authrim-passkey-delete__action:active:not(:disabled) {
|
|
151
|
+
transform: scale(0.95);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.authrim-passkey-delete__action:disabled {
|
|
155
|
+
opacity: 0.5;
|
|
156
|
+
cursor: not-allowed;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.authrim-passkey-delete__action--confirm {
|
|
160
|
+
background: var(--authrim-color-error);
|
|
161
|
+
color: white;
|
|
162
|
+
min-width: 40px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.authrim-passkey-delete__action--confirm:hover:not(:disabled) {
|
|
166
|
+
background: var(--authrim-color-error-hover);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.authrim-passkey-delete__action--cancel {
|
|
170
|
+
background: var(--authrim-color-bg-subtle);
|
|
171
|
+
color: var(--authrim-color-text-secondary);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.authrim-passkey-delete__action--cancel:hover:not(:disabled) {
|
|
175
|
+
background: var(--authrim-color-bg-muted);
|
|
176
|
+
}
|
|
177
|
+
</style>
|