@draftlab/auth 0.1.6 → 0.2.1

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/ui/base.js CHANGED
@@ -6,293 +6,293 @@ import { jsx, jsxs } from "preact/jsx-runtime";
6
6
  const css = `@import url("https://unpkg.com/tailwindcss@3.4.15/src/css/preflight.css");
7
7
 
8
8
  :root {
9
- --color-background-dark: #0e0e11;
10
- --color-background-light: #ffffff;
11
- --color-primary-dark: #6772e5;
12
- --color-primary-light: #6772e5;
13
-
14
- --color-background-success-dark: oklch(0.3 0.04 172);
15
- --color-background-success-light: oklch(
16
- from var(--color-background-success-dark) 0.83 c h
17
- );
18
- --color-success-dark: oklch(
19
- from var(--color-background-success-dark) 0.92 c h
20
- );
21
- --color-success-light: oklch(
22
- from var(--color-background-success-dark) 0.25 c h
23
- );
24
-
25
- --color-background-error-dark: oklch(0.32 0.07 15);
26
- --color-background-error-light: oklch(
27
- from var(--color-background-error-dark) 0.92 c h
28
- );
29
- --color-error-dark: oklch(from var(--color-background-error-dark) 0.92 c h);
30
- --color-error-light: oklch(from var(--color-background-error-dark) 0.25 c h);
31
-
32
- --border-radius: 0;
33
-
34
- --color-background: var(--color-background-dark);
35
- --color-primary: var(--color-primary-dark);
36
-
37
- --color-background-success: var(--color-background-success-dark);
38
- --color-success: var(--color-success-dark);
39
- --color-background-error: var(--color-background-error-dark);
40
- --color-error: var(--color-error-dark);
41
-
42
- @media (prefers-color-scheme: light) {
43
- --color-background: var(--color-background-light);
44
- --color-primary: var(--color-primary-light);
45
-
46
- --color-background-success: var(--color-background-success-light);
47
- --color-success: var(--color-success-light);
48
- --color-background-error: var(--color-background-error-light);
49
- --color-error: var(--color-error-light);
50
- }
51
-
52
- --color-high: oklch(
53
- from var(--color-background) clamp(0, calc((l - 0.714) * -1000), 1) 0 0
54
- );
55
- --color-low: oklch(
56
- from var(--color-background) clamp(0, calc((l - 0.714) * 1000), 1) 0 0
57
- );
58
- --lightness-high: color-mix(
59
- in oklch,
60
- var(--color-high) 0%,
61
- oklch(var(--color-high) 0 0)
62
- );
63
- --lightness-low: color-mix(
64
- in oklch,
65
- var(--color-low) 0%,
66
- oklch(var(--color-low) 0 0)
67
- );
68
- --font-family:
69
- ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
70
- "Segoe UI Symbol", "Noto Color Emoji";
71
- --font-scale: 1;
72
-
73
- --font-size-xs: calc(0.75rem * var(--font-scale));
74
- --font-size-sm: calc(0.875rem * var(--font-scale));
75
- --font-size-md: calc(1rem * var(--font-scale));
76
- --font-size-lg: calc(1.125rem * var(--font-scale));
77
- --font-size-xl: calc(1.25rem * var(--font-scale));
78
- --font-size-2xl: calc(1.5rem * var(--font-scale));
9
+ --color-background-dark: #0e0e11;
10
+ --color-background-light: #ffffff;
11
+ --color-primary-dark: #6772e5;
12
+ --color-primary-light: #6772e5;
13
+
14
+ --color-background-success-dark: oklch(0.3 0.04 172);
15
+ --color-background-success-light: oklch(from var(--color-background-success-dark) 0.83 c h);
16
+ --color-success-dark: oklch(from var(--color-background-success-dark) 0.92 c h);
17
+ --color-success-light: oklch(from var(--color-background-success-dark) 0.25 c h);
18
+
19
+ --color-background-error-dark: oklch(0.32 0.07 15);
20
+ --color-background-error-light: oklch(from var(--color-background-error-dark) 0.92 c h);
21
+ --color-error-dark: oklch(from var(--color-background-error-dark) 0.92 c h);
22
+ --color-error-light: oklch(from var(--color-background-error-dark) 0.25 c h);
23
+
24
+ --border-radius: 0;
25
+
26
+ --color-background: var(--color-background-dark);
27
+ --color-primary: var(--color-primary-dark);
28
+
29
+ --color-background-success: var(--color-background-success-dark);
30
+ --color-success: var(--color-success-dark);
31
+ --color-background-error: var(--color-background-error-dark);
32
+ --color-error: var(--color-error-dark);
33
+
34
+ @media (prefers-color-scheme: light) {
35
+ --color-background: var(--color-background-light);
36
+ --color-primary: var(--color-primary-light);
37
+
38
+ --color-background-success: var(--color-background-success-light);
39
+ --color-success: var(--color-success-light);
40
+ --color-background-error: var(--color-background-error-light);
41
+ --color-error: var(--color-error-light);
42
+ }
43
+
44
+ --color-high: oklch(
45
+ from var(--color-background) clamp(0, calc((l - 0.714) * -1000), 1) 0 0
46
+ );
47
+ --color-low: oklch(from var(--color-background) clamp(0, calc((l - 0.714) * 1000), 1) 0 0);
48
+ --lightness-high: color-mix(
49
+ in oklch,
50
+ var(--color-high) 0%,
51
+ oklch(var(--color-high) 0 0)
52
+ );
53
+ --lightness-low: color-mix(
54
+ in oklch,
55
+ var(--color-low) 0%,
56
+ oklch(var(--color-low) 0 0)
57
+ );
58
+ --font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
59
+ "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
60
+ --font-scale: 1;
61
+
62
+ --font-size-xs: calc(0.75rem * var(--font-scale));
63
+ --font-size-sm: calc(0.875rem * var(--font-scale));
64
+ --font-size-md: calc(1rem * var(--font-scale));
65
+ --font-size-lg: calc(1.125rem * var(--font-scale));
66
+ --font-size-xl: calc(1.25rem * var(--font-scale));
67
+ --font-size-2xl: calc(1.5rem * var(--font-scale));
79
68
  }
80
69
 
81
70
  [data-component="root"] {
82
- font-family: var(--font-family);
83
- background-color: var(--color-background);
84
- padding: 1rem;
85
- color: white;
86
- position: absolute;
87
- inset: 0;
88
- display: flex;
89
- align-items: center;
90
- justify-content: center;
91
- flex-direction: column;
92
- user-select: none;
93
- color: var(--color-high);
71
+ font-family: var(--font-family);
72
+ background-color: var(--color-background);
73
+ padding: 1rem;
74
+ color: white;
75
+ position: absolute;
76
+ inset: 0;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ flex-direction: column;
81
+ user-select: none;
82
+ color: var(--color-high);
94
83
  }
95
84
 
96
85
  [data-component="center"] {
97
- width: 380px;
98
- display: flex;
99
- flex-direction: column;
100
- gap: 1.5rem;
86
+ width: 380px;
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: 1.5rem;
101
90
  }
102
91
 
103
92
  [data-component="center"][data-size="small"] {
104
- width: 300px;
93
+ width: 300px;
105
94
  }
106
95
 
107
- [data-component="button"] {
108
- height: 2.5rem;
109
- cursor: pointer;
110
- border: 0;
111
- font-weight: 500;
112
- font-size: var(--font-size-sm);
113
- border-radius: calc(var(--border-radius) * 0.25rem);
114
- display: flex;
115
- gap: 0.75rem;
116
- align-items: center;
117
- justify-content: center;
118
- background: var(--color-primary);
119
- color: oklch(
120
- from var(--color-primary) clamp(0, calc((l - 0.714) * -1000), 1) 0 0
121
- );
96
+ [data-component="link"] {
97
+ text-decoration: underline;
98
+ text-underline-offset: 0.125rem;
99
+ font-weight: 600;
122
100
  }
123
101
 
124
- [data-component="button"][data-color="ghost"] {
125
- background: transparent;
126
- color: var(--color-high);
127
- border: 1px solid
128
- oklch(
129
- from var(--color-background)
130
- calc(
131
- clamp(
132
- 0.22,
133
- l +
134
- (-0.12 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.06),
135
- 0.88
136
- )
137
- )
138
- c h
139
- );
102
+ [data-component="label"] {
103
+ display: flex;
104
+ gap: 0.75rem;
105
+ flex-direction: column;
106
+ font-size: var(--font-size-xs);
140
107
  }
141
108
 
142
- [data-component="button"] [data-slot="icon"] {
143
- width: 16px;
144
- height: 16px;
109
+ [data-component="logo"] {
110
+ margin: 0 auto;
111
+ height: 2.5rem;
112
+ width: auto;
113
+ display: none;
145
114
  }
146
115
 
147
- [data-component="button"] [data-slot="icon"] svg {
148
- width: 100%;
149
- height: 100%;
116
+ [data-component="logo"][data-mode="light"] {
117
+ display: none;
150
118
  }
151
119
 
152
- [data-component="form"] {
153
- max-width: 100%;
154
- display: flex;
155
- flex-direction: column;
156
- gap: 1rem;
157
- margin: 0;
120
+ [data-component="logo"][data-mode="dark"] {
121
+ display: block;
158
122
  }
159
123
 
160
- [data-component="input"] {
161
- height: 2.5rem;
162
- border: 1px solid
163
- oklch(
164
- from var(--color-background)
165
- calc(
166
- clamp(
167
- 0.22,
168
- l +
169
- (-0.12 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.06),
170
- 0.88
171
- )
172
- )
173
- c h
174
- );
175
- border-radius: calc(var(--border-radius) * 0.25rem);
176
- background: var(--color-background);
177
- color: var(--color-high);
178
- padding: 0 0.75rem;
179
- font-size: var(--font-size-sm);
180
- font-family: var(--font-family);
124
+ @media (prefers-color-scheme: light) {
125
+ [data-component="logo"][data-mode="light"] {
126
+ display: block;
127
+ }
128
+
129
+ [data-component="logo"][data-mode="dark"] {
130
+ display: none;
131
+ }
181
132
  }
182
133
 
183
- [data-component="input"]:focus {
184
- outline: none;
185
- border-color: var(--color-primary);
134
+ @media (prefers-color-scheme: dark) {
135
+ [data-component="logo"][data-mode="dark"] {
136
+ display: block;
137
+ }
186
138
  }
187
139
 
188
- [data-component="input"]::placeholder {
189
- color: oklch(
190
- from var(--color-high)
191
- l
192
- c
193
- h /
194
- 0.6
195
- );
140
+ [data-component="logo-default"] {
141
+ margin: 0 auto;
142
+ height: 2.5rem;
143
+ width: auto;
196
144
  }
197
145
 
198
- [data-component="form-alert"] {
199
- padding: 0.75rem;
200
- border-radius: calc(var(--border-radius) * 0.25rem);
201
- display: flex;
202
- align-items: center;
203
- gap: 0.75rem;
204
- font-size: var(--font-size-sm);
146
+ @media (prefers-color-scheme: light) {
147
+ [data-component="logo-default"] {
148
+ color: var(--color-high);
149
+ }
205
150
  }
206
151
 
207
- [data-component="form-alert"][data-color="danger"] {
208
- background: var(--color-background-error);
209
- color: var(--color-error);
210
- border: 1px solid var(--color-error);
152
+ @media (prefers-color-scheme: dark) {
153
+ [data-component="logo-default"] {
154
+ color: var(--color-high);
155
+ }
211
156
  }
212
157
 
213
- [data-component="form-alert"][data-color="success"] {
214
- background: var(--color-background-success);
215
- color: var(--color-success);
216
- border: 1px solid var(--color-success);
158
+ [data-component="input"] {
159
+ width: 100%;
160
+ height: 2.5rem;
161
+ padding: 0 1rem;
162
+ border: 1px solid transparent;
163
+ --background: oklch(
164
+ from var(--color-background) calc(l + (-0.06 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.03)) c h
165
+ );
166
+ background: var(--background);
167
+ border-color: oklch(
168
+ from var(--color-background)
169
+ calc(clamp(0.22, l + (-0.12 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.06), 0.88)) c h
170
+ );
171
+ border-radius: calc(var(--border-radius) * 0.25rem);
172
+ font-size: var(--font-size-sm);
173
+ outline: none;
217
174
  }
218
175
 
219
- [data-component="form-alert"] [data-slot="icon-success"] {
220
- display: none;
176
+ [data-component="input"]:focus {
177
+ border-color: oklch(
178
+ from var(--color-background)
179
+ calc(clamp(0.3, l + (-0.2 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.1), 0.7)) c h
180
+ );
221
181
  }
222
182
 
223
- [data-component="form-alert"] [data-slot="icon-danger"] {
224
- display: none;
183
+ [data-component="input"]:user-invalid:not(:focus) {
184
+ border-color: oklch(0.4 0.09 7.91);
225
185
  }
226
186
 
227
- [data-component="form-alert"][data-color="success"] [data-slot="icon-success"] {
228
- display: block;
229
- width: 16px;
230
- height: 16px;
187
+ [data-component="button"] {
188
+ height: 2.5rem;
189
+ cursor: pointer;
190
+ border: 0;
191
+ font-weight: 500;
192
+ font-size: var(--font-size-sm);
193
+ border-radius: calc(var(--border-radius) * 0.25rem);
194
+ display: flex;
195
+ gap: 0.75rem;
196
+ align-items: center;
197
+ justify-content: center;
198
+ background: var(--color-primary);
199
+ color: oklch(from var(--color-primary) clamp(0, calc((l - 0.714) * -1000), 1) 0 0);
231
200
  }
232
201
 
233
- [data-component="form-alert"][data-color="danger"] [data-slot="icon-danger"] {
234
- display: block;
235
- width: 16px;
236
- height: 16px;
202
+ [data-component="button"][data-color="ghost"] {
203
+ background: transparent;
204
+ color: var(--color-high);
205
+ border: 1px solid
206
+ oklch(
207
+ from var(--color-background)
208
+ calc(clamp(0.22, l + (-0.12 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.06), 0.88)) c h
209
+ );
237
210
  }
238
211
 
239
- [data-component="link"] {
240
- color: var(--color-primary);
241
- text-decoration: none;
242
- font-size: var(--font-size-sm);
212
+ [data-component="button"] [data-slot="icon"] {
213
+ width: 16px;
214
+ height: 16px;
243
215
  }
244
216
 
245
- [data-component="link"]:hover {
246
- text-decoration: underline;
217
+ [data-component="button"] [data-slot="icon"] svg {
218
+ width: 100%;
219
+ height: 100%;
247
220
  }
248
221
 
249
- [data-component="form-footer"] {
250
- display: flex;
251
- gap: 1rem;
252
- font-size: 0.75rem;
253
- align-items: center;
254
- justify-content: center;
222
+ [data-component="form"] {
223
+ max-width: 100%;
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 1rem;
227
+ margin: 0;
255
228
  }
256
229
 
257
- [data-component="form-footer"]:has(> :nth-child(2)) {
258
- justify-content: space-between;
230
+ [data-component="form-alert"] {
231
+ height: 2.5rem;
232
+ display: flex;
233
+ align-items: center;
234
+ padding: 0 1rem;
235
+ border-radius: calc(var(--border-radius) * 0.25rem);
236
+ background: var(--color-background-error);
237
+ color: var(--color-error);
238
+ text-align: left;
239
+ font-size: 0.75rem;
240
+ gap: 0.5rem;
259
241
  }
260
242
 
261
- [data-component="logo-default"] {
262
- margin: 0 auto;
263
- height: 2.5rem;
264
- width: auto;
243
+ [data-component="form-alert"][data-color="success"] {
244
+ background: var(--color-background-success);
245
+ color: var(--color-success);
265
246
  }
266
247
 
267
- @media (prefers-color-scheme: light) {
268
- [data-component="logo-default"] {
269
- color: var(--color-high);
270
- }
248
+ [data-component="form-alert"][data-color="success"] [data-slot="icon-success"] {
249
+ display: block;
271
250
  }
272
251
 
273
- @media (prefers-color-scheme: dark) {
274
- [data-component="logo-default"] {
275
- color: var(--color-high);
276
- }
252
+ [data-component="form-alert"][data-color="success"] [data-slot="icon-danger"] {
253
+ display: none;
277
254
  }
278
255
 
279
- /* Logo theme switching */
280
- [data-component="logo"][data-mode="light"] {
281
- display: none;
256
+ [data-component="form-alert"]:has([data-slot="message"]:empty) {
257
+ display: none;
282
258
  }
283
259
 
284
- [data-component="logo"][data-mode="dark"] {
285
- display: block;
260
+ [data-component="form-alert"] [data-slot="icon-success"],
261
+ [data-component="form-alert"] [data-slot="icon-danger"] {
262
+ width: 1rem;
263
+ height: 1rem;
286
264
  }
287
265
 
288
- @media (prefers-color-scheme: light) {
289
- [data-component="logo"][data-mode="light"] {
290
- display: block;
291
- }
292
-
293
- [data-component="logo"][data-mode="dark"] {
294
- display: none;
295
- }
266
+ [data-component="form-alert"] [data-slot="icon-success"] {
267
+ display: none;
268
+ }
269
+
270
+ [data-component="form-footer"] {
271
+ display: flex;
272
+ gap: 1rem;
273
+ font-size: 0.75rem;
274
+ align-items: center;
275
+ justify-content: center;
276
+ }
277
+
278
+ [data-component="form-footer"]:has(> :nth-child(2)) {
279
+ justify-content: space-between;
280
+ }
281
+
282
+ [data-component="title"] {
283
+ font-size: var(--font-size-2xl);
284
+ font-weight: 600;
285
+ margin: 0 0 0.5rem 0;
286
+ color: var(--color-high);
287
+ text-align: center;
288
+ }
289
+
290
+ [data-component="description"] {
291
+ font-size: var(--font-size-sm);
292
+ color: var(--color-high);
293
+ margin: 0 0 1.5rem 0;
294
+ text-align: center;
295
+ opacity: 0.8;
296
296
  }
297
297
  `;
298
298
  /**
package/dist/ui/code.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Layout, renderToHTML } from "./base.js";
2
+ import { FormAlert } from "./form.js";
2
3
  import { jsx, jsxs } from "preact/jsx-runtime";
3
4
 
4
5
  //#region src/ui/code.tsx
@@ -18,74 +19,6 @@ const DEFAULT_COPY = {
18
19
  code_resend: "Resend"
19
20
  };
20
21
  /**
21
- * FormAlert component for displaying messages
22
- */
23
- const FormAlert = ({ message, color = "danger" }) => {
24
- if (!message) return null;
25
- return /* @__PURE__ */ jsxs("div", {
26
- "data-component": "form-alert",
27
- "data-color": color,
28
- children: [/* @__PURE__ */ jsx("i", {
29
- "data-slot": color === "success" ? "icon-success" : "icon-danger",
30
- children: color === "success" ? /* @__PURE__ */ jsx("svg", {
31
- fill: "none",
32
- stroke: "currentColor",
33
- viewBox: "0 0 24 24",
34
- xmlns: "http://www.w3.org/2000/svg",
35
- "aria-label": "Success",
36
- role: "img",
37
- children: /* @__PURE__ */ jsx("path", {
38
- strokeLinecap: "round",
39
- strokeLinejoin: "round",
40
- strokeWidth: 2,
41
- d: "M5 13l4 4L19 7"
42
- })
43
- }) : /* @__PURE__ */ jsx("svg", {
44
- fill: "none",
45
- stroke: "currentColor",
46
- viewBox: "0 0 24 24",
47
- xmlns: "http://www.w3.org/2000/svg",
48
- "aria-label": "Error",
49
- role: "img",
50
- children: /* @__PURE__ */ jsx("path", {
51
- strokeLinecap: "round",
52
- strokeLinejoin: "round",
53
- strokeWidth: 2,
54
- d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.232 16.5c-.77.833.192 2.5 1.732 2.5z"
55
- })
56
- })
57
- }), /* @__PURE__ */ jsx("span", {
58
- "data-slot": "message",
59
- children: message
60
- })]
61
- });
62
- };
63
- /**
64
- * Input component with consistent styling
65
- */
66
- const Input = ({ type, name, placeholder, value, required, autoComplete, autoFocus,...props }) => /* @__PURE__ */ jsx("input", {
67
- type,
68
- name,
69
- placeholder,
70
- value,
71
- required,
72
- autoComplete,
73
- "data-component": "input",
74
- ...props
75
- });
76
- /**
77
- * Button component with consistent styling
78
- */
79
- const Button = ({ type = "submit", children,...props }) => /* @__PURE__ */ jsx("button", {
80
- type,
81
- "data-component": "button",
82
- ...props,
83
- children
84
- });
85
- /**
86
- * Link component with consistent styling
87
- */
88
- /**
89
22
  * Gets the appropriate error message for display
90
23
  */
91
24
  const getErrorMessage = (error, copy) => {
@@ -129,7 +62,8 @@ const CodeUI = (options) => {
129
62
  message: success.message,
130
63
  color: "success"
131
64
  }) : /* @__PURE__ */ jsx(FormAlert, { message: getErrorMessage(error, copy) }),
132
- /* @__PURE__ */ jsx(Input, {
65
+ /* @__PURE__ */ jsx("input", {
66
+ "data-component": "input",
133
67
  type: mode === "email" ? "email" : "tel",
134
68
  name: mode,
135
69
  placeholder: copy.email_placeholder,
@@ -142,17 +76,13 @@ const CodeUI = (options) => {
142
76
  name: "action",
143
77
  value: "request"
144
78
  }),
145
- /* @__PURE__ */ jsx(Button, {
79
+ /* @__PURE__ */ jsx("button", {
80
+ "data-component": "button",
146
81
  type: "submit",
147
82
  children: copy.button_continue
148
83
  }),
149
84
  /* @__PURE__ */ jsx("p", {
150
- style: {
151
- fontSize: "0.875rem",
152
- color: "var(--color-high)",
153
- textAlign: "center",
154
- margin: "1rem 0 0 0"
155
- },
85
+ "data-component": "description",
156
86
  children: copy.code_info
157
87
  })
158
88
  ]
@@ -177,7 +107,8 @@ const CodeUI = (options) => {
177
107
  type: "hidden",
178
108
  value: "verify"
179
109
  }),
180
- /* @__PURE__ */ jsx(Input, {
110
+ /* @__PURE__ */ jsx("input", {
111
+ "data-component": "input",
181
112
  type: "text",
182
113
  name: "code",
183
114
  placeholder: copy.code_placeholder,
@@ -187,10 +118,10 @@ const CodeUI = (options) => {
187
118
  maxLength: 6,
188
119
  minLength: 6,
189
120
  pattern: "[0-9]{6}",
190
- autoFocus: true,
191
121
  required: true
192
122
  }),
193
- /* @__PURE__ */ jsx(Button, {
123
+ /* @__PURE__ */ jsx("button", {
124
+ "data-component": "button",
194
125
  type: "submit",
195
126
  children: copy.button_continue
196
127
  })
@@ -208,16 +139,17 @@ const CodeUI = (options) => {
208
139
  type: "hidden",
209
140
  value: contact
210
141
  }),
211
- /* @__PURE__ */ jsxs("div", {
142
+ /* @__PURE__ */ jsx("div", {
212
143
  "data-component": "form-footer",
213
- children: [/* @__PURE__ */ jsx("span", {
214
- style: { fontSize: "0.875rem" },
215
- children: copy.code_didnt_get
216
- }), /* @__PURE__ */ jsx(Button, {
217
- type: "submit",
218
- "data-component": "link",
219
- children: copy.code_resend
220
- })]
144
+ children: /* @__PURE__ */ jsxs("span", { children: [
145
+ copy.code_didnt_get,
146
+ " ",
147
+ /* @__PURE__ */ jsx("button", {
148
+ type: "submit",
149
+ "data-component": "link",
150
+ children: copy.code_resend
151
+ })
152
+ ] })
221
153
  })
222
154
  ]
223
155
  })] });
package/dist/ui/form.d.ts CHANGED
@@ -23,7 +23,6 @@ interface FormAlertProps {
23
23
  }
24
24
  /**
25
25
  * Form alert component that displays error or success messages.
26
- * Returns a Preact component or null if no message.
27
26
  */
28
27
  declare const FormAlert: ({
29
28
  message,
package/dist/ui/form.js CHANGED
@@ -2,56 +2,46 @@ import { jsx, jsxs } from "preact/jsx-runtime";
2
2
 
3
3
  //#region src/ui/form.tsx
4
4
  /**
5
- * Success icon component showing a checkmark in a circle.
6
- * Used for positive feedback messages.
7
- */
8
- const SuccessIcon = () => /* @__PURE__ */ jsx("svg", {
9
- "aria-hidden": "true",
10
- "data-slot": "icon-success",
11
- fill: "none",
12
- stroke: "currentColor",
13
- strokeWidth: "1.5",
14
- viewBox: "0 0 24 24",
15
- xmlns: "http://www.w3.org/2000/svg",
16
- children: /* @__PURE__ */ jsx("path", {
17
- d: "M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
18
- strokeLinecap: "round",
19
- strokeLinejoin: "round"
20
- })
21
- });
22
- /**
23
- * Danger icon component showing an exclamation mark in a circle.
24
- * Used for error and warning messages.
25
- */
26
- const DangerIcon = () => /* @__PURE__ */ jsx("svg", {
27
- "aria-hidden": "true",
28
- "data-slot": "icon-danger",
29
- fill: "none",
30
- stroke: "currentColor",
31
- strokeWidth: "1.5",
32
- viewBox: "0 0 24 24",
33
- xmlns: "http://www.w3.org/2000/svg",
34
- children: /* @__PURE__ */ jsx("path", {
35
- d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z",
36
- strokeLinecap: "round",
37
- strokeLinejoin: "round"
38
- })
39
- });
40
- /**
41
5
  * Form alert component that displays error or success messages.
42
- * Returns a Preact component or null if no message.
43
6
  */
44
7
  const FormAlert = ({ message, color = "danger" }) => {
45
- if (!message) return null;
46
8
  return /* @__PURE__ */ jsxs("div", {
47
- "aria-live": "polite",
48
- "data-color": color,
49
9
  "data-component": "form-alert",
50
- role: "alert",
51
- children: [color === "success" ? /* @__PURE__ */ jsx(SuccessIcon, {}) : /* @__PURE__ */ jsx(DangerIcon, {}), /* @__PURE__ */ jsx("span", {
52
- "data-slot": "message",
53
- children: message
54
- })]
10
+ "data-color": color,
11
+ children: [
12
+ /* @__PURE__ */ jsx("svg", {
13
+ "aria-hidden": "true",
14
+ "data-slot": "icon-success",
15
+ xmlns: "http://www.w3.org/2000/svg",
16
+ fill: "none",
17
+ viewBox: "0 0 24 24",
18
+ "stroke-width": "1.5",
19
+ stroke: "currentColor",
20
+ children: /* @__PURE__ */ jsx("path", {
21
+ "stroke-linecap": "round",
22
+ "stroke-linejoin": "round",
23
+ d: "M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
24
+ })
25
+ }),
26
+ /* @__PURE__ */ jsx("svg", {
27
+ "aria-hidden": "true",
28
+ "data-slot": "icon-danger",
29
+ xmlns: "http://www.w3.org/2000/svg",
30
+ fill: "none",
31
+ viewBox: "0 0 24 24",
32
+ "stroke-width": "1.5",
33
+ stroke: "currentColor",
34
+ children: /* @__PURE__ */ jsx("path", {
35
+ "stroke-linecap": "round",
36
+ "stroke-linejoin": "round",
37
+ d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
38
+ })
39
+ }),
40
+ /* @__PURE__ */ jsx("span", {
41
+ "data-slot": "message",
42
+ children: message
43
+ })
44
+ ]
55
45
  });
56
46
  };
57
47
 
@@ -6,17 +6,12 @@ import { PasskeyProviderConfig } from "../provider/passkey.js";
6
6
  * Strongly typed copy text configuration for passkey UI
7
7
  */
8
8
  interface PasskeyUICopy {
9
- readonly authorize_title: string;
10
- readonly authorize_description: string;
11
- readonly register_title: string;
12
- readonly register_description: string;
13
9
  readonly register: string;
14
- readonly register_with_passkey: string;
10
+ readonly button_continue: string;
15
11
  readonly register_other_device: string;
16
12
  readonly register_prompt: string;
17
13
  readonly login_prompt: string;
18
14
  readonly login: string;
19
- readonly login_with_passkey: string;
20
15
  readonly change_prompt: string;
21
16
  readonly code_resend: string;
22
17
  readonly code_return: string;
@@ -4,17 +4,12 @@ import { jsx, jsxs } from "preact/jsx-runtime";
4
4
 
5
5
  //#region src/ui/passkey.tsx
6
6
  const DEFAULT_COPY = {
7
- authorize_title: "Sign in with Passkey",
8
- authorize_description: "Passkeys are a simple and more secure alternative to passwords. With passkeys, you can log in with your PIN, biometric sensor, or hardware security key.",
9
- register_title: "Create a Passkey",
10
- register_description: "Create a passkey to enable secure, passwordless authentication for your account.",
11
7
  register: "Register",
12
- register_with_passkey: "Register With Passkey",
13
8
  register_other_device: "Use another device",
14
9
  register_prompt: "Don't have an account?",
15
10
  login_prompt: "Already have an account?",
16
11
  login: "Login",
17
- login_with_passkey: "Login With Passkey",
12
+ button_continue: "Continue",
18
13
  change_prompt: "Forgot password?",
19
14
  code_resend: "Resend code",
20
15
  code_return: "Back to",
@@ -33,37 +28,16 @@ const PasskeyUI = (options) => {
33
28
  window.addEventListener("load", async () => {
34
29
  const { startAuthentication } = SimpleWebAuthnBrowser;
35
30
  const authorizeForm = document.getElementById("authorizeForm");
31
+ const message = document.querySelector("[data-slot='message']");
36
32
  const origin = window.location.origin;
37
33
  const rpID = window.location.hostname;
38
34
 
39
- const showMessage = (msg) => {
40
- const messageEl = document.querySelector("[data-slot='message']");
41
- if (messageEl) {
42
- messageEl.innerHTML = msg;
43
- } else {
44
- // Create alert if it doesn't exist
45
- const alertDiv = document.createElement("div");
46
- alertDiv.setAttribute("data-component", "form-alert");
47
- alertDiv.setAttribute("role", "alert");
48
- alertDiv.setAttribute("aria-live", "polite");
49
- alertDiv.setAttribute("data-color", "error");
50
- alertDiv.innerHTML = '<span data-slot="message">' + msg + '</span>';
51
- authorizeForm.insertBefore(alertDiv, authorizeForm.firstChild);
52
- }
53
- };
54
-
55
- const clearMessage = () => {
56
- const alertDiv = document.querySelector("[data-component='form-alert']");
57
- if (alertDiv) {
58
- alertDiv.remove();
59
- }
60
- };
61
-
62
35
  authorizeForm.addEventListener("submit", async (e) => {
63
36
  e.preventDefault();
64
37
  const formData = new FormData(authorizeForm);
65
38
  const email = formData.get("email");
66
- clearMessage();
39
+
40
+ message.textContent = "";
67
41
 
68
42
  // GET authentication options from the endpoint that calls
69
43
  // @simplewebauthn/server -> generateAuthenticationOptions()
@@ -74,7 +48,7 @@ const PasskeyUI = (options) => {
74
48
  const optionsJSON = await resp.json();
75
49
 
76
50
  if (optionsJSON.error) {
77
- showMessage(optionsJSON.error);
51
+ message.textContent = optionsJSON.error;
78
52
  return;
79
53
  }
80
54
 
@@ -83,7 +57,7 @@ const PasskeyUI = (options) => {
83
57
  // Pass the options to the authenticator and wait for a response
84
58
  attResp = await startAuthentication({ optionsJSON });
85
59
  } catch (error) {
86
- showMessage(error);
60
+ message.textContent = error;
87
61
  throw error;
88
62
  }
89
63
 
@@ -116,25 +90,27 @@ const PasskeyUI = (options) => {
116
90
  );
117
91
  try {
118
92
  const errorData = await verificationResp.json();
119
- showMessage(errorData.error);
93
+ message.textContent = errorData.error;
120
94
  } catch (error) {
121
- showMessage("Something went wrong");
95
+ message.textContent = "Something went wrong";
122
96
  }
123
97
  }
124
98
  });
125
99
  });
126
100
  ` } }),
127
- /* @__PURE__ */ jsx("h1", { children: copy.authorize_title }),
128
- /* @__PURE__ */ jsx("p", { children: copy.authorize_description }),
129
101
  /* @__PURE__ */ jsxs("form", {
130
102
  id: "authorizeForm",
131
103
  "data-component": "form",
132
104
  children: [
133
105
  /* @__PURE__ */ jsx(FormAlert, {}),
134
106
  /* @__PURE__ */ jsx("input", {
107
+ id: "auth-email",
135
108
  "data-component": "input",
136
109
  type: "email",
137
110
  name: "email",
111
+ "aria-required": "true",
112
+ "aria-describedby": "auth-email-help",
113
+ autoComplete: "email",
138
114
  required: true,
139
115
  placeholder: copy.input_email
140
116
  }),
@@ -142,7 +118,7 @@ const PasskeyUI = (options) => {
142
118
  type: "submit",
143
119
  id: "btnLogin",
144
120
  "data-component": "button",
145
- children: copy.login_with_passkey
121
+ children: copy.button_continue
146
122
  }),
147
123
  /* @__PURE__ */ jsx("div", {
148
124
  "data-component": "form-footer",
@@ -171,37 +147,15 @@ const PasskeyUI = (options) => {
171
147
  window.addEventListener("load", async () => {
172
148
  const { startRegistration } = SimpleWebAuthnBrowser;
173
149
  const registerForm = document.getElementById("registerForm");
150
+ const message = document.querySelector("[data-slot='message']");
174
151
  const origin = window.location.origin;
175
152
  const rpID = window.location.hostname;
176
153
 
177
- const showMessage = (msg) => {
178
- const messageEl = document.querySelector("[data-slot='message']");
179
- if (messageEl) {
180
- messageEl.innerHTML = msg;
181
- } else {
182
- // Create alert if it doesn't exist
183
- const alertDiv = document.createElement("div");
184
- alertDiv.setAttribute("data-component", "form-alert");
185
- alertDiv.setAttribute("role", "alert");
186
- alertDiv.setAttribute("aria-live", "polite");
187
- alertDiv.setAttribute("data-color", "error");
188
- alertDiv.innerHTML = '<span data-slot="message">' + msg + '</span>';
189
- registerForm.insertBefore(alertDiv, registerForm.firstChild);
190
- }
191
- };
192
-
193
- const clearMessage = () => {
194
- const alertDiv = document.querySelector("[data-component='form-alert']");
195
- if (alertDiv) {
196
- alertDiv.remove();
197
- }
198
- };
199
-
200
154
  // Start registration when the user clicks a button
201
155
  const register = async (otherDevice = false) => {
202
156
  const formData = new FormData(registerForm);
203
157
  const email = formData.get("email");
204
- clearMessage();
158
+ message.textContent = "";
205
159
 
206
160
  // GET registration options from the endpoint that calls
207
161
  // @simplewebauthn/server -> generateRegistrationOptions()
@@ -218,7 +172,7 @@ const PasskeyUI = (options) => {
218
172
  const optionsJSON = await resp.json();
219
173
 
220
174
  if (optionsJSON.error) {
221
- showMessage(optionsJSON.error);
175
+ message.textContent = optionsJSON.error;
222
176
  return;
223
177
  }
224
178
 
@@ -227,7 +181,7 @@ const PasskeyUI = (options) => {
227
181
  // Pass the options to the authenticator and wait for a response
228
182
  attResp = await startRegistration({ optionsJSON });
229
183
  } catch (error) {
230
- showMessage(error);
184
+ message.textContent = error;
231
185
  throw error;
232
186
  }
233
187
 
@@ -263,14 +217,14 @@ const PasskeyUI = (options) => {
263
217
  );
264
218
  try {
265
219
  const errorData = await verificationResp.json();
266
- showMessage(errorData.error);
220
+ message.textContent = errorData.error;
267
221
  } catch (error) {
268
- showMessage("Something went wrong");
222
+ message.textContent = "Something went wrong";
269
223
  }
270
224
  }
271
225
  } catch (error) {
272
226
  console.error(error);
273
- showMessage("Something went wrong");
227
+ message.textContent = "Something went wrong";
274
228
  }
275
229
  };
276
230
 
@@ -280,17 +234,19 @@ const PasskeyUI = (options) => {
280
234
  });
281
235
  });
282
236
  ` } }),
283
- /* @__PURE__ */ jsx("h1", { children: copy.register_title }),
284
- /* @__PURE__ */ jsx("p", { children: copy.register_description }),
285
237
  /* @__PURE__ */ jsxs("form", {
286
238
  id: "registerForm",
287
239
  "data-component": "form",
288
240
  children: [
289
241
  /* @__PURE__ */ jsx(FormAlert, {}),
290
242
  /* @__PURE__ */ jsx("input", {
243
+ id: "reg-email",
291
244
  "data-component": "input",
292
245
  type: "email",
293
246
  name: "email",
247
+ "aria-required": "true",
248
+ "aria-describedby": "reg-email-help",
249
+ autoComplete: "email",
294
250
  required: true,
295
251
  placeholder: copy.input_email
296
252
  }),
@@ -298,7 +254,7 @@ const PasskeyUI = (options) => {
298
254
  "data-component": "button",
299
255
  type: "submit",
300
256
  id: "btnRegister",
301
- children: copy.register_with_passkey
257
+ children: copy.button_continue
302
258
  }),
303
259
  /* @__PURE__ */ jsx("button", {
304
260
  "data-component": "button",
@@ -333,7 +333,7 @@ const PasswordUI = (options) => {
333
333
  " ",
334
334
  /* @__PURE__ */ jsx(Link, {
335
335
  href: "./authorize",
336
- children: copy.login.toLowerCase()
336
+ children: copy.login
337
337
  })
338
338
  ] }), /* @__PURE__ */ jsx(Button, {
339
339
  type: "submit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draftlab/auth",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Core implementation for @draftlab/auth",
6
6
  "author": "Matheus Pergoli",