@draftlab/auth 0.0.4 → 0.1.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/error.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * InvalidAuthorizationCodeError,
11
11
  * OauthError,
12
12
  * UnknownStateError
13
- * } from "@draftauth/core/error"
13
+ * } from "@draftlab/auth/error"
14
14
  *
15
15
  * try {
16
16
  * await client.exchange(code, redirectUri)
@@ -67,8 +67,8 @@ interface CodeProviderConfig<Claims extends Record<string, string> = Record<stri
67
67
  * } else {
68
68
  * return {
69
69
  * type: "invalid_claim",
70
- * key: "contact",
71
- * value: "No email or phone provided"
70
+ * key: "email",
71
+ * value: "Email or phone number is required"
72
72
  * }
73
73
  * }
74
74
  * } catch (error) {
@@ -152,7 +152,15 @@ interface CodeUserData<Claims extends Record<string, string> = Record<string, st
152
152
  * }
153
153
  * }
154
154
  *
155
- * await emailService.send(claims.email, `Your verification code: ${code}`)
155
+ * try {
156
+ * await emailService.send(claims.email, `Your verification code: ${code}`)
157
+ * } catch {
158
+ * return {
159
+ * type: "invalid_claim",
160
+ * key: "delivery",
161
+ * value: "Failed to send code"
162
+ * }
163
+ * }
156
164
  * }
157
165
  * })
158
166
  *
@@ -168,15 +176,23 @@ interface CodeUserData<Claims extends Record<string, string> = Record<string, st
168
176
  * }
169
177
  * },
170
178
  * sendCode: async (claims, code) => {
171
- * if (claims.email) {
172
- * await emailService.send(claims.email, `PIN: ${code}`)
173
- * } else if (claims.phone) {
174
- * await smsService.send(claims.phone, `PIN: ${code}`)
175
- * } else {
179
+ * try {
180
+ * if (claims.email) {
181
+ * await emailService.send(claims.email, `PIN: ${code}`)
182
+ * } else if (claims.phone) {
183
+ * await smsService.send(claims.phone, `PIN: ${code}`)
184
+ * } else {
185
+ * return {
186
+ * type: "invalid_claim",
187
+ * key: "email",
188
+ * value: "Provide either email or phone number"
189
+ * }
190
+ * }
191
+ * } catch {
176
192
  * return {
177
193
  * type: "invalid_claim",
178
- * key: "contact",
179
- * value: "Provide either email or phone number"
194
+ * key: "delivery",
195
+ * value: "Failed to send code"
180
196
  * }
181
197
  * }
182
198
  * }
@@ -30,7 +30,15 @@ import { generateUnbiasedDigits, timingSafeCompare } from "../random.js";
30
30
  * }
31
31
  * }
32
32
  *
33
- * await emailService.send(claims.email, `Your verification code: ${code}`)
33
+ * try {
34
+ * await emailService.send(claims.email, `Your verification code: ${code}`)
35
+ * } catch {
36
+ * return {
37
+ * type: "invalid_claim",
38
+ * key: "delivery",
39
+ * value: "Failed to send code"
40
+ * }
41
+ * }
34
42
  * }
35
43
  * })
36
44
  *
@@ -46,15 +54,23 @@ import { generateUnbiasedDigits, timingSafeCompare } from "../random.js";
46
54
  * }
47
55
  * },
48
56
  * sendCode: async (claims, code) => {
49
- * if (claims.email) {
50
- * await emailService.send(claims.email, `PIN: ${code}`)
51
- * } else if (claims.phone) {
52
- * await smsService.send(claims.phone, `PIN: ${code}`)
53
- * } else {
57
+ * try {
58
+ * if (claims.email) {
59
+ * await emailService.send(claims.email, `PIN: ${code}`)
60
+ * } else if (claims.phone) {
61
+ * await smsService.send(claims.phone, `PIN: ${code}`)
62
+ * } else {
63
+ * return {
64
+ * type: "invalid_claim",
65
+ * key: "email",
66
+ * value: "Provide either email or phone number"
67
+ * }
68
+ * }
69
+ * } catch {
54
70
  * return {
55
71
  * type: "invalid_claim",
56
- * key: "contact",
57
- * value: "Provide either email or phone number"
72
+ * key: "delivery",
73
+ * value: "Failed to send code"
58
74
  * }
59
75
  * }
60
76
  * }
@@ -124,18 +140,15 @@ const CodeProvider = (config) => {
124
140
  claims,
125
141
  code
126
142
  }, formData);
127
- }
128
- if (action === "verify" && currentState?.type === "code") {
143
+ } else if (action === "verify" && currentState?.type === "code") {
129
144
  const enteredCode = formData.get("code")?.toString();
130
145
  if (!(currentState.code && enteredCode && timingSafeCompare(currentState.code, enteredCode))) return transition(c, {
131
146
  ...currentState,
132
147
  resend: false
133
148
  }, formData, { type: "invalid_code" });
134
149
  await ctx.unset(c, "provider");
135
- const successResponse = await ctx.success(c, { claims: currentState.claims });
136
- return ctx.forward(c, successResponse);
150
+ return await ctx.success(c, { claims: currentState.claims });
137
151
  }
138
- return transition(c, { type: "start" }, formData);
139
152
  });
140
153
  }
141
154
  };
@@ -10,8 +10,8 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
10
10
  * ## Quick Setup
11
11
  *
12
12
  * ```ts
13
- * import { PasswordUI } from "@draftauth/core/ui/password"
14
- * import { PasswordProvider } from "@draftauth/core/provider/password"
13
+ * import { PasswordUI } from "@draftlab/auth/ui/password"
14
+ * import { PasswordProvider } from "@draftlab/auth/provider/password"
15
15
  *
16
16
  * export default issuer({
17
17
  * providers: {
@@ -9,7 +9,7 @@ import { StorageAdapter } from "./storage.js";
9
9
  *
10
10
  * ### Basic in-memory storage (development only)
11
11
  * ```ts
12
- * import { MemoryStorage } from "@draftauth/core/storage/memory"
12
+ * import { MemoryStorage } from "@draftlab/auth/storage/memory"
13
13
  *
14
14
  * const storage = MemoryStorage()
15
15
  *
@@ -13,7 +13,7 @@ import { Driver } from "unstorage";
13
13
  *
14
14
  * @example
15
15
  * ```ts
16
- * import { UnStorage } from "@draftauth/core/storage/unstorage"
16
+ * import { UnStorage } from "@draftlab/auth/storage/unstorage"
17
17
  * import redisDriver from "unstorage/drivers/redis"
18
18
  *
19
19
  * // Using Redis driver
@@ -12,7 +12,7 @@ import { createStorage } from "unstorage";
12
12
  *
13
13
  * @example
14
14
  * ```ts
15
- * import { UnStorage } from "@draftauth/core/storage/unstorage"
15
+ * import { UnStorage } from "@draftlab/auth/storage/unstorage"
16
16
  * import redisDriver from "unstorage/drivers/redis"
17
17
  *
18
18
  * // Using Redis driver
@@ -5,7 +5,7 @@
5
5
  * @example
6
6
  *
7
7
  * ```ts
8
- * import { THEME_SST } from "@draftauth/core/themes/theme"
8
+ * import { THEME_SST } from "@draftlab/auth/themes/theme"
9
9
  *
10
10
  * export default issuer({
11
11
  * theme: THEME_SST,
@@ -16,7 +16,7 @@
16
16
  * Or define your own.
17
17
  *
18
18
  * ```ts
19
- * import type { Theme } from "@draftauth/core/themes/theme"
19
+ * import type { Theme } from "@draftlab/auth/themes/theme"
20
20
  *
21
21
  * const MY_THEME: Theme = {
22
22
  * title: "Acne",
package/dist/ui/base.d.ts CHANGED
@@ -1,43 +1,30 @@
1
+ import { Theme } from "../themes/theme.js";
2
+ import * as preact6 from "preact";
3
+ import { ComponentChildren } from "preact";
4
+
1
5
  //#region src/ui/base.d.ts
2
- type PropsWithChildren<P = {}> = P & {
3
- children?: string;
4
- };
6
+
5
7
  /**
6
- * Base layout component for Draft Auth UI.
7
- * Provides theming, responsive design, and consistent styling across all auth screens.
8
- *
9
- * ## Features
10
- *
11
- * - **Theme Support**: Light/dark mode with custom colors and fonts
12
- * - **Responsive Design**: Adapts to different screen sizes
13
- * - **Custom Branding**: Support for custom logos and styling
14
- * - **CSS Variables**: Uses CSS custom properties for theming
15
- *
16
- * @example
17
- * ```tsx
18
- * <Layout size="small">
19
- *   <div>Your auth form content</div>
20
- * </Layout>
21
- * ```
22
- */
23
- /**
24
- * Props for the Layout component.
8
+ * Props for the Layout component
25
9
  */
26
10
  interface LayoutProps {
27
- /**
28
- * Optional size variant for the layout container.
29
- *
30
- * @default undefined (normal size)
31
- */
32
- readonly size?: "small";
11
+ children: ComponentChildren;
12
+ theme?: Theme;
13
+ title?: string;
14
+ size?: "small";
33
15
  }
34
16
  /**
35
- * Main layout component that wraps all authentication UI screens.
36
- * Handles theming, logo display, and provides consistent styling.
37
- *
38
- * @param props - Layout props including children and optional size
39
- * @returns Complete HTML document as a string with theming and branding applied
17
+ * Base Layout component that provides the foundational structure for all auth UIs
18
+ */
19
+ declare const Layout: ({
20
+ children,
21
+ theme,
22
+ title,
23
+ size
24
+ }: LayoutProps) => preact6.JSX.Element;
25
+ /**
26
+ * Helper function to render a Preact component to HTML string
40
27
  */
41
- declare const Layout: (props: PropsWithChildren<LayoutProps>) => string;
28
+ declare const renderToHTML: (component: ComponentChildren) => string;
42
29
  //#endregion
43
- export { Layout, LayoutProps };
30
+ export { Layout, LayoutProps, renderToHTML };
package/dist/ui/base.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { getTheme } from "../themes/theme.js";
2
+ import { render } from "preact-render-to-string";
3
+ import { jsx, jsxs } from "preact/jsx-runtime";
2
4
 
3
- //#region src/ui/base.ts
5
+ //#region src/ui/base.tsx
4
6
  const css = `@import url("https://unpkg.com/tailwindcss@3.4.15/src/css/preflight.css");
5
7
 
6
8
  :root {
@@ -102,102 +104,6 @@ const css = `@import url("https://unpkg.com/tailwindcss@3.4.15/src/css/preflight
102
104
  width: 300px;
103
105
  }
104
106
 
105
- [data-component="link"] {
106
- text-decoration: underline;
107
- text-underline-offset: 0.125rem;
108
- font-weight: 600;
109
- }
110
-
111
- [data-component="label"] {
112
- display: flex;
113
- gap: 0.75rem;
114
- flex-direction: column;
115
- font-size: var(--font-size-xs);
116
- }
117
-
118
- [data-component="logo"] {
119
- margin: 0 auto;
120
- height: 2.5rem;
121
- width: auto;
122
- display: none;
123
- }
124
-
125
- @media (prefers-color-scheme: light) {
126
- [data-component="logo"][data-mode="light"] {
127
- display: block;
128
- }
129
- }
130
-
131
- @media (prefers-color-scheme: dark) {
132
- [data-component="logo"][data-mode="dark"] {
133
- display: block;
134
- }
135
- }
136
-
137
- [data-component="logo-default"] {
138
- margin: 0 auto;
139
- height: 2.5rem;
140
- width: auto;
141
- }
142
-
143
- @media (prefers-color-scheme: light) {
144
- [data-component="logo-default"] {
145
- color: var(--color-high);
146
- }
147
- }
148
-
149
- @media (prefers-color-scheme: dark) {
150
- [data-component="logo-default"] {
151
- color: var(--color-high);
152
- }
153
- }
154
-
155
- [data-component="input"] {
156
- width: 100%;
157
- height: 2.5rem;
158
- padding: 0 1rem;
159
- border: 1px solid transparent;
160
- --background: oklch(
161
- from var(--color-background)
162
- calc(l + (-0.06 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.03)) c h
163
- );
164
- background: var(--background);
165
- border-color: oklch(
166
- from var(--color-background)
167
- calc(
168
- clamp(
169
- 0.22,
170
- l +
171
- (-0.12 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.06),
172
- 0.88
173
- )
174
- )
175
- c h
176
- );
177
- border-radius: calc(var(--border-radius) * 0.25rem);
178
- font-size: var(--font-size-sm);
179
- outline: none;
180
- }
181
-
182
- [data-component="input"]:focus {
183
- border-color: oklch(
184
- from var(--color-background)
185
- calc(
186
- clamp(
187
- 0.3,
188
- l +
189
- (-0.2 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.1),
190
- 0.7
191
- )
192
- )
193
- c h
194
- );
195
- }
196
-
197
- [data-component="input"]:user-invalid:not(:focus) {
198
- border-color: oklch(0.4 0.09 7.91);
199
- }
200
-
201
107
  [data-component="button"] {
202
108
  height: 2.5rem;
203
109
  cursor: pointer;
@@ -251,42 +157,93 @@ const css = `@import url("https://unpkg.com/tailwindcss@3.4.15/src/css/preflight
251
157
  margin: 0;
252
158
  }
253
159
 
254
- [data-component="form-alert"] {
160
+ [data-component="input"] {
255
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);
181
+ }
182
+
183
+ [data-component="input"]:focus {
184
+ outline: none;
185
+ border-color: var(--color-primary);
186
+ }
187
+
188
+ [data-component="input"]::placeholder {
189
+ color: oklch(
190
+ from var(--color-high)
191
+ l
192
+ c
193
+ h /
194
+ 0.6
195
+ );
196
+ }
197
+
198
+ [data-component="form-alert"] {
199
+ padding: 0.75rem;
200
+ border-radius: calc(var(--border-radius) * 0.25rem);
256
201
  display: flex;
257
202
  align-items: center;
258
- padding: 0 1rem;
259
- border-radius: calc(var(--border-radius) * 0.25rem);
203
+ gap: 0.75rem;
204
+ font-size: var(--font-size-sm);
205
+ }
206
+
207
+ [data-component="form-alert"][data-color="danger"] {
260
208
  background: var(--color-background-error);
261
209
  color: var(--color-error);
262
- text-align: left;
263
- font-size: 0.75rem;
264
- gap: 0.5rem;
210
+ border: 1px solid var(--color-error);
265
211
  }
266
212
 
267
213
  [data-component="form-alert"][data-color="success"] {
268
214
  background: var(--color-background-success);
269
215
  color: var(--color-success);
216
+ border: 1px solid var(--color-success);
270
217
  }
271
218
 
272
- [data-component="form-alert"][data-color="success"] [data-slot="icon-success"] {
273
- display: block;
274
- }
275
- [data-component="form-alert"][data-color="success"] [data-slot="icon-danger"] {
219
+ [data-component="form-alert"] [data-slot="icon-success"] {
276
220
  display: none;
277
221
  }
278
222
 
279
- [data-component="form-alert"]:has([data-slot="message"]:empty) {
223
+ [data-component="form-alert"] [data-slot="icon-danger"] {
280
224
  display: none;
281
225
  }
282
226
 
283
- [data-component="form-alert"] [data-slot="icon-success"],
284
- [data-component="form-alert"] [data-slot="icon-danger"] {
285
- width: 1rem;
286
- height: 1rem;
227
+ [data-component="form-alert"][data-color="success"] [data-slot="icon-success"] {
228
+ display: block;
229
+ width: 16px;
230
+ height: 16px;
287
231
  }
288
- [data-component="form-alert"] [data-slot="icon-success"] {
289
- display: none;
232
+
233
+ [data-component="form-alert"][data-color="danger"] [data-slot="icon-danger"] {
234
+ display: block;
235
+ width: 16px;
236
+ height: 16px;
237
+ }
238
+
239
+ [data-component="link"] {
240
+ color: var(--color-primary);
241
+ text-decoration: none;
242
+ font-size: var(--font-size-sm);
243
+ }
244
+
245
+ [data-component="link"]:hover {
246
+ text-decoration: underline;
290
247
  }
291
248
 
292
249
  [data-component="form-footer"] {
@@ -300,30 +257,62 @@ const css = `@import url("https://unpkg.com/tailwindcss@3.4.15/src/css/preflight
300
257
  [data-component="form-footer"]:has(> :nth-child(2)) {
301
258
  justify-content: space-between;
302
259
  }
260
+
261
+ [data-component="logo-default"] {
262
+ margin: 0 auto;
263
+ height: 2.5rem;
264
+ width: auto;
265
+ }
266
+
267
+ @media (prefers-color-scheme: light) {
268
+ [data-component="logo-default"] {
269
+ color: var(--color-high);
270
+ }
271
+ }
272
+
273
+ @media (prefers-color-scheme: dark) {
274
+ [data-component="logo-default"] {
275
+ color: var(--color-high);
276
+ }
277
+ }
278
+
279
+ /* Logo theme switching */
280
+ [data-component="logo"][data-mode="light"] {
281
+ display: none;
282
+ }
283
+
284
+ [data-component="logo"][data-mode="dark"] {
285
+ display: block;
286
+ }
287
+
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
+ }
296
+ }
303
297
  `;
304
298
  /**
305
- * Main layout component that wraps all authentication UI screens.
306
- * Handles theming, logo display, and provides consistent styling.
307
- *
308
- * @param props - Layout props including children and optional size
309
- * @returns Complete HTML document as a string with theming and branding applied
299
+ * Base Layout component that provides the foundational structure for all auth UIs
310
300
  */
311
- const Layout = (props) => {
312
- const theme = getTheme();
301
+ const Layout = ({ children, theme, title, size }) => {
302
+ const currentTheme = theme || getTheme();
313
303
  /**
314
304
  * Gets a theme value for a specific key and color mode.
315
- * Handles both string values and light/dark object configurations.
316
305
  */
317
306
  const getThemeValue = (key, mode) => {
318
- if (!theme?.[key]) return;
319
- if (typeof theme[key] === "string") return theme[key];
320
- return theme[key][mode];
307
+ if (!currentTheme?.[key]) return;
308
+ if (typeof currentTheme[key] === "string") return currentTheme[key];
309
+ return currentTheme[key][mode];
321
310
  };
322
311
  /**
323
312
  * Calculates border radius value based on theme configuration.
324
313
  */
325
314
  const getBorderRadius = () => {
326
- switch (theme?.radius) {
315
+ switch (currentTheme?.radius) {
327
316
  case "none": return "0";
328
317
  case "sm": return "1";
329
318
  case "md": return "1.25";
@@ -344,11 +333,11 @@ const Layout = (props) => {
344
333
  `--color-background-dark: ${getThemeValue("background", "dark") || ""}`,
345
334
  `--color-primary-light: ${getThemeValue("primary", "light") || ""}`,
346
335
  `--color-primary-dark: ${getThemeValue("primary", "dark") || ""}`,
347
- `--font-family: ${theme?.font?.family || ""}`,
348
- `--font-scale: ${theme?.font?.scale || ""}`,
336
+ `--font-family: ${currentTheme?.font?.family || ""}`,
337
+ `--font-scale: ${currentTheme?.font?.scale || ""}`,
349
338
  `--border-radius: ${getBorderRadius()}`
350
339
  ].join("; ");
351
- const faviconHtml = theme?.favicon ? `<link href="${theme.favicon}" rel="icon" />` : `
340
+ const faviconHtml = currentTheme?.favicon ? `<link href="${currentTheme.favicon}" rel="icon" />` : `
352
341
  <link href="https://openauth.js.org/favicon.ico" rel="icon" sizes="48x48" />
353
342
  <link href="https://openauth.js.org/favicon.svg" media="(prefers-color-scheme: light)" rel="icon" />
354
343
  <link href="https://openauth.js.org/favicon-dark.svg" media="(prefers-color-scheme: dark)" rel="icon" />
@@ -367,56 +356,53 @@ const Layout = (props) => {
367
356
  data-mode="dark"
368
357
  src="${getThemeValue("logo", "dark") || ""}"
369
358
  />
370
- ` : DefaultDraftAuthLogo();
371
- const childrenHtml = props.children || "";
372
- return `
373
- <!DOCTYPE html>
374
- <html lang="en" style="${themeStyles}">
375
- <head>
376
- <title>${theme?.title || "Draft Auth"}</title>
377
- <meta charset="utf-8" />
378
- <meta content="width=device-width, initial-scale=1" name="viewport" />
379
-
380
- ${faviconHtml}
381
-
382
- <!-- Base CSS styles -->
383
- <style>${css}</style>
384
-
385
- <!-- Custom theme CSS if provided -->
386
- ${theme?.css ? `<style>${theme.css}</style>` : ""}
387
- </head>
388
- <body>
389
- <div data-component="root">
390
- <div data-component="center" data-size="${props.size || ""}">
391
- ${logoHtml}
392
- ${childrenHtml}
393
- </div>
394
- </div>
395
- </body>
396
- </html>
397
- `;
359
+ ` : `
360
+ <svg
361
+ aria-label="Draft Auth Logo"
362
+ data-component="logo-default"
363
+ fill="none"
364
+ height="51"
365
+ viewBox="0 0 51 51"
366
+ width="51"
367
+ xmlns="http://www.w3.org/2000/svg"
368
+ >
369
+ <title>Draft Auth Logo</title>
370
+ <path
371
+ d="M0 50.2303V0.12854H50.1017V50.2303H0ZM3.08002 11.8326H11.7041V3.20856H3.08002V11.8326ZM14.8526 11.8326H23.4766V3.20856H14.8526V11.8326ZM26.5566 11.8326H35.1807V3.20856H26.5566V11.8326ZM38.3292 11.8326H47.0217V3.20856H38.3292V11.8326ZM3.08002 23.6052H11.7041V14.9811H3.08002V23.6052ZM14.8526 23.6052H23.4766V14.9811H14.8526V23.6052ZM26.5566 23.6052H35.1807V14.9811H26.5566V23.6052ZM38.3292 23.6052H47.0217V14.9811H38.3292V23.6052ZM3.08002 35.3092H11.7041V26.6852H3.08002V35.3092ZM14.8526 35.3092H23.4766V26.6852H14.8526V35.3092ZM26.5566 35.3092H35.1807V26.6852H26.5566V35.3092ZM38.3292 35.3092H47.0217V26.6852H38.3292V35.3092ZM3.08002 47.1502H11.7041V38.3893H3.08002V47.1502ZM14.8526 47.1502H23.4766V38.3893H14.8526V47.1502ZM26.5566 47.1502H35.1807V38.3893H26.5566V47.1502ZM38.3292 47.1502H47.0217V38.3893H38.3292V47.1502Z"
372
+ fill="currentColor"
373
+ />
374
+ </svg>
375
+ `;
376
+ return /* @__PURE__ */ jsxs("html", {
377
+ lang: "en",
378
+ style: themeStyles,
379
+ children: [/* @__PURE__ */ jsxs("head", { children: [
380
+ /* @__PURE__ */ jsx("title", { children: title || currentTheme?.title || "Draft Auth" }),
381
+ /* @__PURE__ */ jsx("meta", { charset: "utf-8" }),
382
+ /* @__PURE__ */ jsx("meta", {
383
+ content: "width=device-width, initial-scale=1",
384
+ name: "viewport"
385
+ }),
386
+ /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: faviconHtml } }),
387
+ /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } }),
388
+ currentTheme?.css && /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: currentTheme.css } })
389
+ ] }), /* @__PURE__ */ jsx("body", { children: /* @__PURE__ */ jsx("div", {
390
+ "data-component": "root",
391
+ children: /* @__PURE__ */ jsxs("div", {
392
+ "data-component": "center",
393
+ "data-size": size || "",
394
+ children: [/* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: logoHtml } }), children]
395
+ })
396
+ }) })]
397
+ });
398
398
  };
399
399
  /**
400
- * Default Draft Auth logo component.
401
- * Used when no custom logo is provided in the theme configuration.
400
+ * Helper function to render a Preact component to HTML string
402
401
  */
403
- const DefaultDraftAuthLogo = () => `
404
- <svg
405
- aria-label="Draft Auth Logo"
406
- data-component="logo-default"
407
- fill="none"
408
- height="51"
409
- viewBox="0 0 51 51"
410
- width="51"
411
- xmlns="http://www.w3.org/2000/svg"
412
- >
413
- <title>Draft Auth Logo</title>
414
- <path
415
- d="M0 50.2303V0.12854H50.1017V50.2303H0ZM3.08002 11.8326H11.7041V3.20856H3.08002V11.8326ZM14.8526 11.8326H23.4766V3.20856H14.8526V11.8326ZM26.5566 11.8326H35.1807V3.20856H26.5566V11.8326ZM38.3292 11.8326H47.0217V3.20856H38.3292V11.8326ZM3.08002 23.6052H11.7041V14.9811H3.08002V23.6052ZM14.8526 23.6052H23.4766V14.9811H14.8526V23.6052ZM26.5566 23.6052H35.1807V14.9811H26.5566V23.6052ZM38.3292 23.6052H47.0217V14.9811H38.3292V23.6052ZM3.08002 35.3092H11.7041V26.6852H3.08002V35.3092ZM14.8526 35.3092H23.4766V26.6852H14.8526V35.3092ZM26.5566 35.3092H35.1807V26.6852H26.5566V35.3092ZM38.3292 35.3092H47.0217V26.6852H38.3292V35.3092ZM3.08002 47.1502H11.7041V38.3893H3.08002V47.1502ZM14.8526 47.1502H23.4766V38.3893H14.8526V47.1502ZM26.5566 47.1502H35.1807V38.3893H26.5566V47.1502ZM38.3292 47.1502H47.0217V38.3893H38.3292V47.1502Z"
416
- fill="currentColor"
417
- />
418
- </svg>
419
- `;
402
+ const renderToHTML = (component) => {
403
+ if (!component) return "";
404
+ return render(component);
405
+ };
420
406
 
421
407
  //#endregion
422
- export { Layout };
408
+ export { Layout, renderToHTML };