@insymetri/styleguide 0.1.26 → 0.1.28
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/dist/IIButton/IIButton.svelte +1 -1
- package/dist/IICombobox/IICombobox.svelte +7 -0
- package/dist/IICombobox/IICombobox.svelte.d.ts +1 -0
- package/dist/IIDateInput/IIDateInput.svelte +2 -2
- package/dist/IIIconButton/IIIconButton.svelte +1 -1
- package/dist/IIInput/IIInput.svelte +2 -2
- package/dist/IISegmentedControl/IISegmentedControl.svelte +73 -19
- package/dist/IISegmentedControl/IISegmentedControl.svelte.d.ts +0 -1
- package/dist/IISegmentedControl/IISegmentedControlStories.svelte +10 -26
- package/dist/IISwitch/IISwitch.svelte +1 -1
- package/dist/IIToggle/IIToggle.svelte +1 -1
- package/dist/MobileOnboarding/LoginFlowBare.svelte +13 -2
- package/dist/MobileOnboarding/LoginFlowBareAlt.svelte +968 -0
- package/dist/MobileOnboarding/LoginFlowBareAlt.svelte.d.ts +3 -0
- package/dist/MobileOnboarding/LoginScreenBare.svelte +84 -67
- package/dist/MobileOnboarding/LoginScreenBare.svelte.d.ts +4 -0
- package/dist/MobileOnboarding/OTPScreenBare.svelte +196 -125
- package/dist/MobileOnboarding/OTPScreenBare.svelte.d.ts +4 -0
- package/dist/style/themes.css +145 -0
- package/package.json +1 -1
|
@@ -13,9 +13,13 @@
|
|
|
13
13
|
stepIndicator?: Snippet
|
|
14
14
|
/** Hide the built-in step indicator (when managed externally) */
|
|
15
15
|
hideStepIndicator?: boolean
|
|
16
|
+
/** When true, content above step indicator fades out */
|
|
17
|
+
transitioning?: boolean
|
|
18
|
+
/** Hide the illustration banner on mobile/tablet */
|
|
19
|
+
hideBanner?: boolean
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
let {onContinue, illustration, stepIndicator, hideStepIndicator = false}: Props = $props()
|
|
22
|
+
let {onContinue, illustration, stepIndicator, hideStepIndicator = false, transitioning = false, hideBanner = false}: Props = $props()
|
|
19
23
|
|
|
20
24
|
let email = $state('')
|
|
21
25
|
let showError = $state(false)
|
|
@@ -23,61 +27,67 @@
|
|
|
23
27
|
</script>
|
|
24
28
|
|
|
25
29
|
<!-- Outer wrapper: grey bg on tablet+, full bleed on mobile -->
|
|
26
|
-
<div class="
|
|
30
|
+
<div class="flex h-screen flex-col md:h-auto md:min-h-screen md:items-center md:justify-center md:bg-background md:p-24">
|
|
27
31
|
<!-- Card on tablet+, split on desktop -->
|
|
28
32
|
<DensityProvider density="mobile">
|
|
29
|
-
<div class="flex flex-col lg:flex-row lg:w-[860px] md:w-[440px] md:rounded-16 md:
|
|
33
|
+
<div class="flex min-w-0 flex-1 flex-col bg-white md:flex-initial md:h-[694px] lg:h-[595px] lg:flex-row lg:w-[860px] md:w-[440px] md:rounded-16 md:shadow-card md:overflow-hidden">
|
|
30
34
|
<!-- Illustration banner — mobile & tablet only -->
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{#each Array(
|
|
36
|
-
|
|
35
|
+
{#if !hideBanner}
|
|
36
|
+
<div class="lg:hidden relative h-[100px] md:h-[90px] overflow-hidden shrink-0" style="background: linear-gradient(135deg, var(--ii-primary), var(--ii-primary-hover))">
|
|
37
|
+
<svg class="absolute inset-0 w-full h-full" viewBox="0 0 600 120" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">
|
|
38
|
+
<!-- Background texture -->
|
|
39
|
+
{#each Array(2) as _, row}
|
|
40
|
+
{#each Array(12) as _, col}
|
|
41
|
+
<circle cx={30 + col * 50} cy={25 + row * 50} r="1.5" fill="white" opacity="0.1" />
|
|
42
|
+
{/each}
|
|
37
43
|
{/each}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</
|
|
51
|
-
|
|
44
|
+
<!-- Steps path -->
|
|
45
|
+
<line x1="120" y1="60" x2="240" y2="60" stroke="white" stroke-width="2" opacity="0.3" />
|
|
46
|
+
<line x1="240" y1="60" x2="360" y2="60" stroke="white" stroke-width="2" opacity="0.15" />
|
|
47
|
+
<line x1="360" y1="60" x2="480" y2="60" stroke="white" stroke-width="2" opacity="0.08" />
|
|
48
|
+
<!-- Step nodes -->
|
|
49
|
+
<circle cx="120" cy="60" r="12" fill="white" opacity="0.3" />
|
|
50
|
+
<circle cx="120" cy="60" r="6" fill="white" opacity="0.4" />
|
|
51
|
+
<circle cx="240" cy="60" r="12" fill="white" opacity="0.15" />
|
|
52
|
+
<circle cx="240" cy="60" r="4" fill="white" opacity="0.3" />
|
|
53
|
+
<circle cx="360" cy="60" r="10" fill="white" opacity="0.12" />
|
|
54
|
+
<circle cx="480" cy="60" r="8" fill="white" opacity="0.08" />
|
|
55
|
+
</svg>
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
52
58
|
|
|
53
59
|
<!-- Form side -->
|
|
54
|
-
<div class="flex
|
|
55
|
-
<!--
|
|
56
|
-
<div class="flex
|
|
57
|
-
|
|
60
|
+
<div class="flex w-full min-w-0 flex-1 flex-col px-24 md:p-40">
|
|
61
|
+
<!-- Content that fades between steps -->
|
|
62
|
+
<div class="step-content flex flex-1 flex-col" class:fade-out={transitioning}>
|
|
63
|
+
<!-- Row 1: Logo — fixed height so headers align between steps -->
|
|
64
|
+
<div class="flex h-56 shrink-0 items-center justify-center gap-8 pt-24 md:pt-0">
|
|
65
|
+
<svg width="28" height="28" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
58
66
|
<path d="M16 2L4 9v14l12 7 12-7V9L16 2z" fill="var(--ii-primary)" />
|
|
59
67
|
<path d="M16 6l-8 4.5v9L16 24l8-4.5v-9L16 6z" fill="white" />
|
|
60
68
|
<path d="M16 10l-4 2.25v4.5L16 19l4-2.25v-4.5L16 10z" fill="var(--ii-primary)" />
|
|
61
69
|
</svg>
|
|
62
|
-
<span class="text-
|
|
70
|
+
<span class="text-h3 text-body">Meridian Finance</span>
|
|
63
71
|
</div>
|
|
64
72
|
|
|
65
|
-
<!-- Header -->
|
|
73
|
+
<!-- Row 2: Header -->
|
|
66
74
|
<div class="pt-24 pb-48">
|
|
67
|
-
<
|
|
75
|
+
<div class="text-h2 text-body m-0">Your Loan Application</div>
|
|
68
76
|
<p class="text-m3-emphasis text-tertiary mt-4 m-0">
|
|
69
77
|
Get started or continue your application
|
|
70
78
|
</p>
|
|
71
79
|
</div>
|
|
72
80
|
|
|
73
|
-
<!--
|
|
74
|
-
<
|
|
81
|
+
<!-- Row 3: Input -->
|
|
82
|
+
<form id="login-email-form" class="flex flex-col" onsubmit={(e) => { e.preventDefault(); loading = true; setTimeout(() => { loading = false; onContinue?.() }, 800) }}>
|
|
75
83
|
{#if showError}
|
|
76
|
-
<
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
<div class="mb-16">
|
|
85
|
+
<IIAlert variant="error" dismissible onDismiss={() => (showError = false)}>
|
|
86
|
+
{#snippet children()}
|
|
87
|
+
We couldn't find an account with that email address.
|
|
88
|
+
{/snippet}
|
|
89
|
+
</IIAlert>
|
|
90
|
+
</div>
|
|
81
91
|
{/if}
|
|
82
92
|
|
|
83
93
|
<IIInput
|
|
@@ -89,35 +99,37 @@
|
|
|
89
99
|
autocomplete="email"
|
|
90
100
|
error={showError}
|
|
91
101
|
/>
|
|
102
|
+
</form>
|
|
103
|
+
|
|
104
|
+
<!-- Info section — pushed to bottom on mobile -->
|
|
105
|
+
<div class="mb-24 mt-auto">
|
|
106
|
+
<p class="text-emphasis text-body m-0 mb-12">Here's what happens next:</p>
|
|
107
|
+
<ul class="m-0 list-none p-0 flex flex-col gap-8">
|
|
108
|
+
<li class="relative pl-20 text-default text-secondary">
|
|
109
|
+
<span class="absolute left-0 text-accent">✓</span>
|
|
110
|
+
New here? We'll help you get started
|
|
111
|
+
</li>
|
|
112
|
+
<li class="relative pl-20 text-default text-secondary">
|
|
113
|
+
<span class="absolute left-0 text-accent">✓</span>
|
|
114
|
+
Already applied? Pick up right where you left off
|
|
115
|
+
</li>
|
|
116
|
+
<li class="relative pl-20 text-default text-secondary">
|
|
117
|
+
<span class="absolute left-0 text-accent">✓</span>
|
|
118
|
+
All done? Check your loan status
|
|
119
|
+
</li>
|
|
120
|
+
</ul>
|
|
121
|
+
</div>
|
|
92
122
|
</div>
|
|
93
123
|
|
|
94
|
-
<!--
|
|
95
|
-
<div class="
|
|
96
|
-
<!-- Info Section -->
|
|
97
|
-
<div class="mb-24">
|
|
98
|
-
<p class="text-emphasis text-body m-0 mb-12">Here's what happens next:</p>
|
|
99
|
-
<ul class="m-0 list-none p-0 flex flex-col gap-8">
|
|
100
|
-
<li class="relative pl-20 text-default text-secondary">
|
|
101
|
-
<span class="absolute left-0 text-accent">✓</span>
|
|
102
|
-
New here? We'll help you get started
|
|
103
|
-
</li>
|
|
104
|
-
<li class="relative pl-20 text-default text-secondary">
|
|
105
|
-
<span class="absolute left-0 text-accent">✓</span>
|
|
106
|
-
Already applied? Pick up right where you left off
|
|
107
|
-
</li>
|
|
108
|
-
<li class="relative pl-20 text-default text-secondary">
|
|
109
|
-
<span class="absolute left-0 text-accent">✓</span>
|
|
110
|
-
All done? Check your loan status
|
|
111
|
-
</li>
|
|
112
|
-
</ul>
|
|
113
|
-
</div>
|
|
124
|
+
<!-- Persistent bottom area — step indicator + button (doesn't fade) -->
|
|
125
|
+
<div class="pb-24 md:pb-0">
|
|
114
126
|
{#if stepIndicator}
|
|
115
|
-
<div class="
|
|
127
|
+
<div class="mb-24 mt-24">
|
|
116
128
|
{@render stepIndicator()}
|
|
117
129
|
</div>
|
|
118
130
|
{:else if !hideStepIndicator}
|
|
119
131
|
<!-- Step indicator -->
|
|
120
|
-
<div class="flex items-center justify-center gap-8
|
|
132
|
+
<div class="flex items-center justify-center gap-8 mb-24 mt-24">
|
|
121
133
|
<div class="h-6 rounded-full" style="background: var(--ii-primary); width: 36px; transition: width 0.4s ease, background-color 0.4s ease"></div>
|
|
122
134
|
<div class="h-6 rounded-full" style="background: var(--ii-gray-300); width: 16px; transition: width 0.4s ease, background-color 0.4s ease"></div>
|
|
123
135
|
</div>
|
|
@@ -125,15 +137,9 @@
|
|
|
125
137
|
<IIButton
|
|
126
138
|
variant="primary"
|
|
127
139
|
class="w-full"
|
|
140
|
+
type="submit"
|
|
141
|
+
form="login-email-form"
|
|
128
142
|
{loading}
|
|
129
|
-
disabled={!email}
|
|
130
|
-
onclick={() => {
|
|
131
|
-
loading = true
|
|
132
|
-
setTimeout(() => {
|
|
133
|
-
loading = false
|
|
134
|
-
onContinue?.()
|
|
135
|
-
}, 800)
|
|
136
|
-
}}
|
|
137
143
|
>
|
|
138
144
|
{#snippet children()}
|
|
139
145
|
Continue
|
|
@@ -198,3 +204,14 @@
|
|
|
198
204
|
</div>
|
|
199
205
|
</DensityProvider>
|
|
200
206
|
</div>
|
|
207
|
+
|
|
208
|
+
<style>
|
|
209
|
+
.step-content {
|
|
210
|
+
opacity: 1;
|
|
211
|
+
transition: opacity 200ms ease;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.step-content.fade-out {
|
|
215
|
+
opacity: 0;
|
|
216
|
+
}
|
|
217
|
+
</style>
|
|
@@ -7,6 +7,10 @@ type Props = {
|
|
|
7
7
|
stepIndicator?: Snippet;
|
|
8
8
|
/** Hide the built-in step indicator (when managed externally) */
|
|
9
9
|
hideStepIndicator?: boolean;
|
|
10
|
+
/** When true, content above step indicator fades out */
|
|
11
|
+
transitioning?: boolean;
|
|
12
|
+
/** Hide the illustration banner on mobile/tablet */
|
|
13
|
+
hideBanner?: boolean;
|
|
10
14
|
};
|
|
11
15
|
declare const LoginScreenBare: import("svelte").Component<Props, {}, "">;
|
|
12
16
|
type LoginScreenBare = ReturnType<typeof LoginScreenBare>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import DensityProvider from '../DensityProvider/DensityProvider.svelte'
|
|
3
3
|
import IIButton from '../IIButton/IIButton.svelte'
|
|
4
|
+
import IIAlert from '../IIAlert/IIAlert.svelte'
|
|
4
5
|
|
|
5
6
|
import type {Snippet} from 'svelte'
|
|
6
7
|
|
|
@@ -9,28 +10,36 @@
|
|
|
9
10
|
onBack?: () => void
|
|
10
11
|
stepIndicator?: Snippet
|
|
11
12
|
hideStepIndicator?: boolean
|
|
13
|
+
/** When true, content above step indicator fades out */
|
|
14
|
+
transitioning?: boolean
|
|
15
|
+
/** Hide the illustration banner on mobile/tablet */
|
|
16
|
+
hideBanner?: boolean
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
let {onContinue, onBack, stepIndicator, hideStepIndicator = false}: Props = $props()
|
|
19
|
+
let {onContinue, onBack, stepIndicator, hideStepIndicator = false, transitioning = false, hideBanner = false}: Props = $props()
|
|
15
20
|
|
|
16
21
|
let digits = $state<string[]>(['', '', '', '', '', ''])
|
|
17
22
|
let inputs: HTMLInputElement[] = []
|
|
18
23
|
let showError = $state(false)
|
|
19
|
-
let resendTimer = $state(30)
|
|
20
24
|
let verifying = $state(false)
|
|
25
|
+
let isResending = $state(false)
|
|
21
26
|
|
|
22
|
-
let deliveryStatus = $state<'sending' | 'sent' | 'delivered'>('delivered')
|
|
27
|
+
let deliveryStatus = $state<'idle' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'failed'>('delivered')
|
|
28
|
+
let deliveryError = $state('')
|
|
23
29
|
|
|
24
30
|
const code = $derived(digits.join(''))
|
|
25
31
|
const isComplete = $derived(code.length === 6 && digits.every((d) => d !== ''))
|
|
32
|
+
const hasFailed = $derived(deliveryStatus === 'bounced' || deliveryStatus === 'failed')
|
|
33
|
+
const sentComplete = $derived(deliveryStatus !== 'idle')
|
|
34
|
+
const deliveredComplete = $derived(deliveryStatus === 'delivered')
|
|
35
|
+
const isInFlight = $derived(deliveryStatus === 'sent' || deliveryStatus === 'sending')
|
|
26
36
|
|
|
27
37
|
function handleInput(index: number, event: Event) {
|
|
28
38
|
const target = event.target as HTMLInputElement
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (value && index < 5) {
|
|
39
|
+
const val = target.value.replace(/[^a-zA-Z0-9]/g, '').toUpperCase()
|
|
40
|
+
digits[index] = val.slice(-1)
|
|
41
|
+
target.value = digits[index]
|
|
42
|
+
if (digits[index] && index < 5) {
|
|
34
43
|
inputs[index + 1]?.focus()
|
|
35
44
|
}
|
|
36
45
|
}
|
|
@@ -43,7 +52,11 @@
|
|
|
43
52
|
|
|
44
53
|
function handlePaste(event: ClipboardEvent) {
|
|
45
54
|
event.preventDefault()
|
|
46
|
-
const paste = event.clipboardData
|
|
55
|
+
const paste = event.clipboardData
|
|
56
|
+
?.getData('text')
|
|
57
|
+
?.replace(/[^a-zA-Z0-9]/g, '')
|
|
58
|
+
?.toUpperCase()
|
|
59
|
+
?.slice(0, 6)
|
|
47
60
|
if (paste) {
|
|
48
61
|
for (let i = 0; i < 6; i++) {
|
|
49
62
|
digits[i] = paste[i] || ''
|
|
@@ -52,20 +65,11 @@
|
|
|
52
65
|
inputs[nextEmpty]?.focus()
|
|
53
66
|
}
|
|
54
67
|
}
|
|
55
|
-
|
|
56
|
-
$effect(() => {
|
|
57
|
-
if (resendTimer > 0) {
|
|
58
|
-
const interval = setInterval(() => {
|
|
59
|
-
resendTimer--
|
|
60
|
-
}, 1000)
|
|
61
|
-
return () => clearInterval(interval)
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
68
|
</script>
|
|
65
69
|
|
|
66
|
-
<div class="
|
|
70
|
+
<div class="flex h-screen flex-col md:h-auto md:min-h-screen md:items-center md:justify-center md:bg-background md:p-24">
|
|
67
71
|
<DensityProvider density="mobile">
|
|
68
|
-
<div class="flex flex-col lg:flex-row lg:w-[860px] md:w-[440px] md:rounded-16 md:
|
|
72
|
+
<div class="flex min-w-0 flex-1 flex-col bg-white md:flex-initial md:h-[694px] lg:h-[595px] lg:flex-row lg:w-[860px] md:w-[440px] md:rounded-16 md:shadow-card md:overflow-hidden">
|
|
69
73
|
<!-- Illustration banner — mobile & tablet only -->
|
|
70
74
|
<div class="lg:hidden relative h-[100px] md:h-[90px] overflow-hidden shrink-0" style="background: linear-gradient(135deg, var(--ii-primary), var(--ii-primary-hover))">
|
|
71
75
|
<svg class="absolute inset-0 w-full h-full" viewBox="0 0 600 120" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -88,9 +92,11 @@
|
|
|
88
92
|
</div>
|
|
89
93
|
|
|
90
94
|
<!-- Form side -->
|
|
91
|
-
<div class="flex
|
|
92
|
-
<!--
|
|
93
|
-
<div class="
|
|
95
|
+
<div class="flex w-full min-w-0 flex-1 flex-col px-24 md:p-40">
|
|
96
|
+
<!-- Content that fades between steps -->
|
|
97
|
+
<div class="step-content flex flex-1 flex-col" class:fade-out={transitioning}>
|
|
98
|
+
<!-- Row 1: Back button — fixed height so headers align with email screen -->
|
|
99
|
+
<div class="flex h-56 shrink-0 items-center gap-10 pt-24 md:pt-0">
|
|
94
100
|
<button
|
|
95
101
|
class="text-small text-secondary bg-transparent border-0 cursor-default p-0 hover:text-body"
|
|
96
102
|
onclick={onBack}
|
|
@@ -99,137 +105,191 @@
|
|
|
99
105
|
</button>
|
|
100
106
|
</div>
|
|
101
107
|
|
|
102
|
-
<!-- Header -->
|
|
103
|
-
<div class="
|
|
104
|
-
<
|
|
108
|
+
<!-- Row 2: Header -->
|
|
109
|
+
<div class="pt-24 pb-48">
|
|
110
|
+
<div class="text-h2 text-body m-0">Enter verification code</div>
|
|
105
111
|
<p class="text-m3-emphasis text-tertiary mt-4 m-0">
|
|
106
112
|
We sent a 6-digit code to <strong class="text-body">john@example.com</strong>
|
|
107
113
|
</p>
|
|
108
114
|
</div>
|
|
109
115
|
|
|
110
|
-
<!--
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
<div
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<svg width="12" height="12" viewBox="0 0 256 256" fill="currentColor">
|
|
120
|
-
<path d="m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32" />
|
|
121
|
-
</svg>
|
|
122
|
-
{/if}
|
|
116
|
+
<!-- Row 3: OTP Input — pt-20 matches the email input's label height -->
|
|
117
|
+
<form id="login-otp-form" class="flex flex-col pt-20" onsubmit={(e) => { e.preventDefault(); verifying = true; setTimeout(() => { verifying = false; onContinue?.() }, 800) }}>
|
|
118
|
+
{#if showError}
|
|
119
|
+
<div class="mb-16">
|
|
120
|
+
<IIAlert variant="error" dismissible onDismiss={() => (showError = false)}>
|
|
121
|
+
{#snippet children()}
|
|
122
|
+
Invalid verification code. Please try again.
|
|
123
|
+
{/snippet}
|
|
124
|
+
</IIAlert>
|
|
123
125
|
</div>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<div class="
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
126
|
+
{/if}
|
|
127
|
+
|
|
128
|
+
<div class="mb-16">
|
|
129
|
+
<div class="flex justify-center gap-8" role="group" aria-label="Verification code">
|
|
130
|
+
{#each digits as digit, i}
|
|
131
|
+
<input
|
|
132
|
+
bind:this={inputs[i]}
|
|
133
|
+
bind:value={digits[i]}
|
|
134
|
+
type="text"
|
|
135
|
+
inputmode="text"
|
|
136
|
+
autocomplete={i === 0 ? 'one-time-code' : 'off'}
|
|
137
|
+
maxlength="1"
|
|
138
|
+
pattern="[a-zA-Z0-9]"
|
|
139
|
+
class="w-48 h-48 text-center !text-m1 font-medium text-body bg-input-bg border-2 rounded-8 outline-none transition-all duration-fast
|
|
140
|
+
{showError
|
|
141
|
+
? 'border-error'
|
|
142
|
+
: digit
|
|
143
|
+
? 'border-primary'
|
|
144
|
+
: 'border-input-border'}
|
|
145
|
+
focus:border-primary focus:ring-2 focus:ring-primary/20"
|
|
146
|
+
oninput={(e) => handleInput(i, e)}
|
|
147
|
+
onkeydown={(e) => handleKeydown(i, e)}
|
|
148
|
+
onpaste={handlePaste}
|
|
149
|
+
aria-label="Digit {i + 1}"
|
|
150
|
+
/>
|
|
151
|
+
{/each}
|
|
141
152
|
</div>
|
|
142
153
|
</div>
|
|
143
|
-
</div>
|
|
144
154
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
<!-- Delivery Status -->
|
|
156
|
+
<div class="mb-16 flex justify-center">
|
|
157
|
+
<div class="flex justify-center gap-0" role="status" aria-label="Code delivery status">
|
|
158
|
+
<!-- Sent -->
|
|
159
|
+
<div class="flex flex-col items-center" style="min-width: 56px">
|
|
160
|
+
<div
|
|
161
|
+
class="w-24 h-24 rounded-full flex items-center justify-center shrink-0
|
|
162
|
+
{hasFailed
|
|
163
|
+
? 'bg-error text-inverse'
|
|
164
|
+
: sentComplete
|
|
165
|
+
? 'bg-primary text-inverse'
|
|
166
|
+
: 'bg-muted text-secondary'}
|
|
167
|
+
{deliveryStatus === 'sending' ? ' animate-pulse' : ''}"
|
|
168
|
+
>
|
|
169
|
+
{#if sentComplete && !hasFailed}
|
|
170
|
+
<svg width="12" height="12" viewBox="0 0 256 256" fill="currentColor">
|
|
171
|
+
<path d="m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32" />
|
|
172
|
+
</svg>
|
|
173
|
+
{:else if hasFailed}
|
|
174
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
|
175
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
176
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
177
|
+
</svg>
|
|
178
|
+
{/if}
|
|
179
|
+
</div>
|
|
180
|
+
<span class="mt-4 text-[10px] text-secondary">Sent</span>
|
|
181
|
+
</div>
|
|
170
182
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
<!-- Connector -->
|
|
184
|
+
<div class="flex items-start pt-12">
|
|
185
|
+
<div
|
|
186
|
+
class="mx-6 h-[2px] w-80 transition-colors duration-500
|
|
187
|
+
{hasFailed ? 'bg-error' : deliveredComplete ? 'bg-primary' : 'bg-muted'}"
|
|
188
|
+
></div>
|
|
189
|
+
</div>
|
|
174
190
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
<!-- Delivered -->
|
|
192
|
+
<div class="flex flex-col items-center" style="min-width: 56px">
|
|
193
|
+
<div
|
|
194
|
+
class="w-24 h-24 rounded-full flex items-center justify-center shrink-0
|
|
195
|
+
{hasFailed
|
|
196
|
+
? 'bg-error text-inverse'
|
|
197
|
+
: deliveredComplete
|
|
198
|
+
? 'bg-primary text-inverse'
|
|
199
|
+
: isInFlight
|
|
200
|
+
? 'bg-primary text-inverse animate-pulse'
|
|
201
|
+
: 'bg-muted text-secondary'}"
|
|
202
|
+
>
|
|
203
|
+
{#if deliveredComplete}
|
|
204
|
+
<svg width="12" height="12" viewBox="0 0 256 256" fill="currentColor">
|
|
205
|
+
<path d="m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32" />
|
|
206
|
+
</svg>
|
|
207
|
+
{:else if hasFailed}
|
|
208
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
|
209
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
210
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
211
|
+
</svg>
|
|
212
|
+
{/if}
|
|
213
|
+
</div>
|
|
214
|
+
<span class="mt-4 text-[10px] text-secondary">
|
|
215
|
+
{#if hasFailed}Failed{:else}Delivered{/if}
|
|
216
|
+
</span>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{#if deliveryError}
|
|
222
|
+
<p class="mt-0 mb-16 p-0 text-center text-default text-error">{deliveryError}</p>
|
|
223
|
+
{/if}
|
|
224
|
+
</form>
|
|
225
|
+
|
|
226
|
+
<!-- Help text — pushed to bottom on mobile -->
|
|
227
|
+
<div class="mb-24 mt-auto">
|
|
228
|
+
<p class="m-0 text-tiny text-tertiary">
|
|
229
|
+
Didn't receive the code?
|
|
182
230
|
<button
|
|
183
|
-
|
|
231
|
+
type="button"
|
|
232
|
+
class="cursor-default border-0 bg-transparent p-0 text-tiny text-accent underline"
|
|
233
|
+
disabled={isResending}
|
|
184
234
|
onclick={() => {
|
|
185
|
-
|
|
235
|
+
isResending = true
|
|
186
236
|
deliveryStatus = 'sending'
|
|
187
237
|
setTimeout(() => (deliveryStatus = 'sent'), 800)
|
|
188
|
-
setTimeout(() =>
|
|
238
|
+
setTimeout(() => { deliveryStatus = 'delivered'; isResending = false }, 2000)
|
|
189
239
|
}}
|
|
190
240
|
>
|
|
191
|
-
Resend code
|
|
241
|
+
{isResending ? 'Sending...' : 'Resend code'}
|
|
192
242
|
</button>
|
|
193
|
-
|
|
243
|
+
or
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
class="cursor-default border-0 bg-transparent p-0 text-tiny text-accent underline"
|
|
247
|
+
onclick={onBack}
|
|
248
|
+
>
|
|
249
|
+
try a different email
|
|
250
|
+
</button>
|
|
251
|
+
</p>
|
|
252
|
+
</div>
|
|
194
253
|
</div>
|
|
195
254
|
|
|
196
|
-
<!--
|
|
197
|
-
<div class="
|
|
198
|
-
<!-- Help text -->
|
|
199
|
-
<div class="mb-24">
|
|
200
|
-
<p class="text-tiny text-tertiary m-0">
|
|
201
|
-
Didn't receive the code? Check your spam folder or
|
|
202
|
-
<button class="text-accent bg-transparent border-0 cursor-default text-tiny p-0 underline">try a different email</button>
|
|
203
|
-
</p>
|
|
204
|
-
</div>
|
|
255
|
+
<!-- Persistent bottom area — step indicator + button (doesn't fade) -->
|
|
256
|
+
<div class="pb-24 md:pb-0">
|
|
205
257
|
{#if stepIndicator}
|
|
206
|
-
<div class="
|
|
258
|
+
<div class="mb-24 mt-24">
|
|
207
259
|
{@render stepIndicator()}
|
|
208
260
|
</div>
|
|
209
261
|
{:else if !hideStepIndicator}
|
|
210
262
|
<!-- Step indicator -->
|
|
211
|
-
<div class="flex items-center justify-center gap-8
|
|
263
|
+
<div class="flex items-center justify-center gap-8 mb-24 mt-24">
|
|
212
264
|
<div class="h-6 rounded-full" style="background: var(--ii-gray-300); width: 16px; transition: width 0.4s ease, background-color 0.4s ease"></div>
|
|
213
265
|
<div class="h-6 rounded-full" style="background: var(--ii-primary); width: 36px; transition: width 0.4s ease, background-color 0.4s ease"></div>
|
|
214
266
|
</div>
|
|
215
267
|
{/if}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
268
|
+
{#if hasFailed}
|
|
269
|
+
<IIButton
|
|
270
|
+
variant="primary"
|
|
271
|
+
class="w-full"
|
|
272
|
+
type="button"
|
|
273
|
+
onclick={onBack}
|
|
274
|
+
>
|
|
275
|
+
{#snippet children()}
|
|
276
|
+
Use Different Email
|
|
277
|
+
{/snippet}
|
|
278
|
+
</IIButton>
|
|
279
|
+
{:else}
|
|
280
|
+
<IIButton
|
|
281
|
+
variant="primary"
|
|
282
|
+
class="w-full"
|
|
283
|
+
type="submit"
|
|
284
|
+
form="login-otp-form"
|
|
285
|
+
disabled={!isComplete || verifying}
|
|
286
|
+
loading={verifying}
|
|
287
|
+
>
|
|
288
|
+
{#snippet children()}
|
|
289
|
+
Verify
|
|
290
|
+
{/snippet}
|
|
291
|
+
</IIButton>
|
|
292
|
+
{/if}
|
|
233
293
|
</div>
|
|
234
294
|
</div>
|
|
235
295
|
|
|
@@ -283,3 +343,14 @@
|
|
|
283
343
|
</div>
|
|
284
344
|
</DensityProvider>
|
|
285
345
|
</div>
|
|
346
|
+
|
|
347
|
+
<style>
|
|
348
|
+
.step-content {
|
|
349
|
+
opacity: 1;
|
|
350
|
+
transition: opacity 200ms ease;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.step-content.fade-out {
|
|
354
|
+
opacity: 0;
|
|
355
|
+
}
|
|
356
|
+
</style>
|