@linktr.ee/linkapp 0.0.21 → 0.0.23

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.
Files changed (115) hide show
  1. package/dev-server/README.md +5 -5
  2. package/dev-server/components/SettingsPreview.tsx +330 -0
  3. package/dev-server/components/ui/dialog.tsx +6 -3
  4. package/dev-server/env.d.ts +8 -0
  5. package/dev-server/featured/main.tsx +48 -43
  6. package/dev-server/featured.html +21 -35
  7. package/dev-server/index.html +8 -1
  8. package/dev-server/lib/utils.ts +6 -0
  9. package/dev-server/package-lock.json +245 -287
  10. package/dev-server/package.json +7 -4
  11. package/dev-server/postcss/tailwind-source-fallback.js +94 -0
  12. package/dev-server/postcss.config.mjs +30 -0
  13. package/dev-server/preview/Preview.tsx +182 -381
  14. package/dev-server/preview/preview.css +22 -34
  15. package/dev-server/public/apple-touch-icon.png +0 -0
  16. package/dev-server/public/favicon-96x96.png +0 -0
  17. package/dev-server/public/favicon.ico +0 -0
  18. package/dev-server/public/favicon.svg +6 -0
  19. package/dev-server/public/site.webmanifest +21 -0
  20. package/dev-server/public/web-app-manifest-192x192.png +0 -0
  21. package/dev-server/public/web-app-manifest-512x512.png +0 -0
  22. package/dev-server/rsbuild.config.ts +45 -0
  23. package/dev-server/shared/theme-presets.ts +315 -0
  24. package/dev-server/shared/theme-utils.ts +38 -0
  25. package/dev-server/sheet/main.tsx +44 -0
  26. package/dev-server/sheet.html +56 -0
  27. package/dist/commands/add.d.ts.map +1 -1
  28. package/dist/commands/add.js +20 -12
  29. package/dist/commands/add.js.map +1 -1
  30. package/dist/commands/build.d.ts.map +1 -1
  31. package/dist/commands/build.js +55 -36
  32. package/dist/commands/build.js.map +1 -1
  33. package/dist/commands/deploy.d.ts.map +1 -1
  34. package/dist/commands/deploy.js +74 -59
  35. package/dist/commands/deploy.js.map +1 -1
  36. package/dist/commands/dev.d.ts.map +1 -1
  37. package/dist/commands/dev.js +564 -43
  38. package/dist/commands/dev.js.map +1 -1
  39. package/dist/commands/login.d.ts.map +1 -1
  40. package/dist/commands/login.js +19 -9
  41. package/dist/commands/login.js.map +1 -1
  42. package/dist/commands/logout.d.ts.map +1 -1
  43. package/dist/commands/logout.js +9 -4
  44. package/dist/commands/logout.js.map +1 -1
  45. package/dist/commands/test-url-match-rules.d.ts.map +1 -1
  46. package/dist/commands/test-url-match-rules.js +24 -13
  47. package/dist/commands/test-url-match-rules.js.map +1 -1
  48. package/dist/components/index.d.ts +0 -1
  49. package/dist/components/index.d.ts.map +1 -1
  50. package/dist/components/index.js +2 -1
  51. package/dist/components/index.js.map +1 -1
  52. package/dist/lib/auth/device-flow.d.ts +1 -6
  53. package/dist/lib/auth/device-flow.d.ts.map +1 -1
  54. package/dist/lib/auth/device-flow.js +3 -9
  55. package/dist/lib/auth/device-flow.js.map +1 -1
  56. package/dist/lib/auth/token-storage.d.ts +0 -5
  57. package/dist/lib/auth/token-storage.d.ts.map +1 -1
  58. package/dist/lib/auth/token-storage.js +16 -14
  59. package/dist/lib/auth/token-storage.js.map +1 -1
  60. package/dist/lib/build/detect-layouts.d.ts +1 -1
  61. package/dist/lib/build/detect-layouts.d.ts.map +1 -1
  62. package/dist/lib/build/detect-layouts.js +8 -7
  63. package/dist/lib/build/detect-layouts.js.map +1 -1
  64. package/dist/lib/deploy/generate-manifest-files.js +1 -1
  65. package/dist/lib/deploy/generate-manifest-files.js.map +1 -1
  66. package/dist/lib/deploy/pack-project.js +2 -2
  67. package/dist/lib/deploy/pack-project.js.map +1 -1
  68. package/dist/lib/deploy/test-url-match-rules.d.ts +2 -1
  69. package/dist/lib/deploy/test-url-match-rules.d.ts.map +1 -1
  70. package/dist/lib/deploy/test-url-match-rules.js +1 -1
  71. package/dist/lib/deploy/test-url-match-rules.js.map +1 -1
  72. package/dist/lib/deploy/upload.d.ts.map +1 -1
  73. package/dist/lib/deploy/upload.js +7 -3
  74. package/dist/lib/deploy/upload.js.map +1 -1
  75. package/dist/lib/deploy/validation.d.ts.map +1 -1
  76. package/dist/lib/deploy/validation.js +8 -5
  77. package/dist/lib/deploy/validation.js.map +1 -1
  78. package/dist/lib/rsbuild/config-factory.d.ts +24 -0
  79. package/dist/lib/rsbuild/config-factory.d.ts.map +1 -0
  80. package/dist/lib/rsbuild/config-factory.js +135 -0
  81. package/dist/lib/rsbuild/config-factory.js.map +1 -0
  82. package/dist/lib/rsbuild/plugins/asset-versioning.d.ts +11 -0
  83. package/dist/lib/rsbuild/plugins/asset-versioning.d.ts.map +1 -0
  84. package/dist/lib/rsbuild/plugins/asset-versioning.js +62 -0
  85. package/dist/lib/rsbuild/plugins/asset-versioning.js.map +1 -0
  86. package/dist/lib/rsbuild/plugins/copy-public.d.ts +11 -0
  87. package/dist/lib/rsbuild/plugins/copy-public.d.ts.map +1 -0
  88. package/dist/lib/rsbuild/plugins/copy-public.js +32 -0
  89. package/dist/lib/rsbuild/plugins/copy-public.js.map +1 -0
  90. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.d.ts +12 -0
  91. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.d.ts.map +1 -0
  92. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.js +60 -0
  93. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.js.map +1 -0
  94. package/dist/lib/utils/setup-runtime.d.ts.map +1 -1
  95. package/dist/lib/utils/setup-runtime.js +78 -17
  96. package/dist/lib/utils/setup-runtime.js.map +1 -1
  97. package/dist/lib/vite/config-factory.d.ts.map +1 -1
  98. package/dist/lib/vite/config-factory.js +8 -3
  99. package/dist/lib/vite/config-factory.js.map +1 -1
  100. package/dist/lib/vite/plugins/copy-public.d.ts +12 -0
  101. package/dist/lib/vite/plugins/copy-public.d.ts.map +1 -0
  102. package/dist/lib/vite/plugins/copy-public.js +31 -0
  103. package/dist/lib/vite/plugins/copy-public.js.map +1 -0
  104. package/dist/schema/config.schema.d.ts +121 -32
  105. package/dist/schema/config.schema.d.ts.map +1 -1
  106. package/dist/schema/config.schema.js +28 -17
  107. package/dist/schema/config.schema.js.map +1 -1
  108. package/dist/types.d.ts +6 -57
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/types.js.map +1 -1
  111. package/package.json +7 -4
  112. package/runtime/index.html +19 -36
  113. package/dev-server/classic/main.tsx +0 -62
  114. package/dev-server/classic.html +0 -70
  115. package/dev-server/vite.config.ts +0 -29
@@ -1,5 +1,6 @@
1
1
  import { Portal } from "@radix-ui/react-portal";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import IframeResizer, { IFrameComponent } from "iframe-resizer-react";
3
4
  import {
4
5
  Dialog,
5
6
  DialogContent,
@@ -12,389 +13,102 @@ import {
12
13
  TabsList,
13
14
  TabsTrigger,
14
15
  } from "../components/ui/tabs";
15
- import { cn } from "../lib/utils";
16
-
17
- // Theme presets for testing
18
- // Based on actual CSS variables from live Linktree profiles
19
- const THEME_PRESETS = {
20
- default: {
21
- name: "Default (Light)",
22
- variables: {
23
- "--button-style-text": "#000000",
24
- "--button-style-background": "#ffffff",
25
- "--button-style-background-hover":
26
- "color-mix(in srgb, #ffffff 93%, #000000 7%)",
27
- "--button-style-border": "none",
28
- "--button-style-border-color": "transparent",
29
- "--button-style-shadow": "none",
30
- "--button-style-shadow-color": "#000000",
31
- "--button-style-contrast-color": "#000000",
32
- "--button-style-radius": "8px",
33
- "--button-style-inner-radius": "min(8px, max(4px, calc(8px - 8px)))",
34
- "--button-style-skeleton-color": "rgba(0, 0, 0, 0.05)",
35
- "--link-gap": "14px",
36
- "--link-inner-padding": "7px",
37
- "--link-preview-thumbnail-width": "160px",
38
- "--linkRadius": "8px",
39
- "--profile-container-desktop-width": "580px",
40
- "--profileBackground": "#eceef1",
41
- "--background-contrast-color": "#000000",
42
- "--bodyText": "#000000",
43
- "--profileTitleText": "#000000",
44
- "--profileDescriptionText": "#000000",
45
- "--defaultAvatarBackground": "#000000",
46
- "--defaultAvatarText": "#ffffff",
47
- "--socialLinkFill": "#000000",
48
- "--linkBackground": "#ffffff",
49
- "--linkText": "#000000",
50
- "--linkHoverBackground": "color-mix(in srgb, #ffffff 93%, #000000 7%)",
51
- "--linkHoverText": "#000000",
52
- "--linkShadow": "#000000",
53
- "--bannerBackground": "#ffffff",
54
- "--bannerText": "#000000",
55
- "--desktop-frame-color": "color-mix(in srgb, #eceef1 88%, black 12%)",
56
- "--profileFontFamilyPrimary":
57
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
58
- "--profileFontFamilySecondary":
59
- 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
60
- "--header-font-family":
61
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
62
- "--profileFontWeightNormal": "500",
63
- "--profileFontWeightBold": "700",
64
- "--profileDescriptionFontWeight": "500",
65
- "--linkTextFontWeight": "500",
66
- "--headerFontWeight": "700",
67
- "--header-font-weight": "600",
68
- "--embedLinkTextFontWeight": "500",
69
- "--signupSubmitTextFontWeight": "700",
70
- "--bannerFontWeight": "700",
71
- "--headerFontSize": "normal",
72
- "--header-bio-font-size": "16px",
73
- "--embedLinkTextFontSize": "14px",
74
- "--signupSubmitTextFontSize": "14px",
75
- "--linkHeaderFontSize": "14px",
76
- "--bannerFontSize": "14px",
77
- "--profileDescriptionLineHeight": "1.5",
78
- "--headerLineHeight": "1.5",
79
- "--header-text-color": "#000000",
80
- },
81
- },
82
- dark: {
83
- name: "Dark",
84
- variables: {
85
- "--button-style-text": "#ffffff",
86
- "--button-style-background": "#1f2937",
87
- "--button-style-background-hover":
88
- "color-mix(in srgb, #1f2937 93%, #ffffff 7%)",
89
- "--button-style-border": "none",
90
- "--button-style-border-color": "transparent",
91
- "--button-style-shadow": "none",
92
- "--button-style-shadow-color": "#000000",
93
- "--button-style-contrast-color": "#ffffff",
94
- "--button-style-radius": "8px",
95
- "--button-style-inner-radius": "min(8px, max(4px, calc(8px - 8px)))",
96
- "--button-style-skeleton-color": "rgba(255, 255, 255, 0.1)",
97
- "--link-gap": "14px",
98
- "--link-inner-padding": "7px",
99
- "--link-preview-thumbnail-width": "160px",
100
- "--linkRadius": "8px",
101
- "--profile-container-desktop-width": "580px",
102
- "--profileBackground": "#111827",
103
- "--background-contrast-color": "#ffffff",
104
- "--bodyText": "#ffffff",
105
- "--profileTitleText": "#ffffff",
106
- "--profileDescriptionText": "#ffffff",
107
- "--defaultAvatarBackground": "#ffffff",
108
- "--defaultAvatarText": "#000000",
109
- "--socialLinkFill": "#ffffff",
110
- "--linkBackground": "#1f2937",
111
- "--linkText": "#ffffff",
112
- "--linkHoverBackground": "color-mix(in srgb, #1f2937 93%, #ffffff 7%)",
113
- "--linkHoverText": "#ffffff",
114
- "--linkShadow": "#000000",
115
- "--bannerBackground": "#1f2937",
116
- "--bannerText": "#ffffff",
117
- "--desktop-frame-color": "color-mix(in srgb, #111827 88%, white 12%)",
118
- "--profileFontFamilyPrimary":
119
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
120
- "--profileFontFamilySecondary":
121
- 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
122
- "--header-font-family":
123
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
124
- "--profileFontWeightNormal": "500",
125
- "--profileFontWeightBold": "700",
126
- "--profileDescriptionFontWeight": "500",
127
- "--linkTextFontWeight": "500",
128
- "--headerFontWeight": "700",
129
- "--header-font-weight": "600",
130
- "--embedLinkTextFontWeight": "500",
131
- "--signupSubmitTextFontWeight": "700",
132
- "--bannerFontWeight": "700",
133
- "--headerFontSize": "normal",
134
- "--header-bio-font-size": "16px",
135
- "--embedLinkTextFontSize": "14px",
136
- "--signupSubmitTextFontSize": "14px",
137
- "--linkHeaderFontSize": "14px",
138
- "--bannerFontSize": "14px",
139
- "--profileDescriptionLineHeight": "1.5",
140
- "--headerLineHeight": "1.5",
141
- "--header-text-color": "#ffffff",
142
- },
143
- },
144
- purple: {
145
- name: "Purple",
146
- variables: {
147
- "--button-style-text": "#ffffff",
148
- "--button-style-background": "#7c3aed",
149
- "--button-style-background-hover":
150
- "color-mix(in srgb, #7c3aed 93%, #ffffff 7%)",
151
- "--button-style-border": "none",
152
- "--button-style-border-color": "transparent",
153
- "--button-style-shadow": "none",
154
- "--button-style-shadow-color": "#000000",
155
- "--button-style-contrast-color": "#ede9fe",
156
- "--button-style-radius": "16px",
157
- "--button-style-inner-radius": "min(16px, max(4px, calc(16px - 8px)))",
158
- "--button-style-skeleton-color": "rgba(124, 58, 237, 0.1)",
159
- "--link-gap": "14px",
160
- "--link-inner-padding": "7px",
161
- "--link-preview-thumbnail-width": "160px",
162
- "--linkRadius": "16px",
163
- "--profile-container-desktop-width": "580px",
164
- "--profileBackground": "#faf5ff",
165
- "--background-contrast-color": "#1f2937",
166
- "--bodyText": "#1f2937",
167
- "--profileTitleText": "#1f2937",
168
- "--profileDescriptionText": "#1f2937",
169
- "--defaultAvatarBackground": "#7c3aed",
170
- "--defaultAvatarText": "#ffffff",
171
- "--socialLinkFill": "#1f2937",
172
- "--linkBackground": "#7c3aed",
173
- "--linkText": "#ffffff",
174
- "--linkHoverBackground": "color-mix(in srgb, #7c3aed 93%, #ffffff 7%)",
175
- "--linkHoverText": "#ffffff",
176
- "--linkShadow": "#7c3aed",
177
- "--bannerBackground": "#7c3aed",
178
- "--bannerText": "#ffffff",
179
- "--desktop-frame-color": "color-mix(in srgb, #faf5ff 88%, black 12%)",
180
- "--profileFontFamilyPrimary":
181
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
182
- "--profileFontFamilySecondary":
183
- 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
184
- "--header-font-family":
185
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
186
- "--profileFontWeightNormal": "500",
187
- "--profileFontWeightBold": "700",
188
- "--profileDescriptionFontWeight": "500",
189
- "--linkTextFontWeight": "500",
190
- "--headerFontWeight": "700",
191
- "--header-font-weight": "600",
192
- "--embedLinkTextFontWeight": "500",
193
- "--signupSubmitTextFontWeight": "700",
194
- "--bannerFontWeight": "700",
195
- "--headerFontSize": "normal",
196
- "--header-bio-font-size": "16px",
197
- "--embedLinkTextFontSize": "14px",
198
- "--signupSubmitTextFontSize": "14px",
199
- "--linkHeaderFontSize": "14px",
200
- "--bannerFontSize": "14px",
201
- "--profileDescriptionLineHeight": "1.5",
202
- "--headerLineHeight": "1.5",
203
- "--header-text-color": "#1f2937",
204
- },
205
- },
206
- outline: {
207
- name: "Outline Style",
208
- variables: {
209
- "--button-style-text": "#000000",
210
- "--button-style-background": "transparent",
211
- "--button-style-background-hover": "rgba(0, 0, 0, 0.05)",
212
- "--button-style-border": "2px solid currentColor",
213
- "--button-style-border-color": "#000000",
214
- "--button-style-shadow": "none",
215
- "--button-style-shadow-color": "#000000",
216
- "--button-style-contrast-color": "#ffffff",
217
- "--button-style-radius": "24px",
218
- "--button-style-inner-radius": "min(24px, max(4px, calc(24px - 8px)))",
219
- "--button-style-skeleton-color": "rgba(0, 0, 0, 0.05)",
220
- "--link-gap": "14px",
221
- "--link-inner-padding": "7px",
222
- "--link-preview-thumbnail-width": "160px",
223
- "--linkRadius": "24px",
224
- "--profile-container-desktop-width": "580px",
225
- "--profileBackground": "#ffffff",
226
- "--background-contrast-color": "#000000",
227
- "--bodyText": "#000000",
228
- "--profileTitleText": "#000000",
229
- "--profileDescriptionText": "#000000",
230
- "--defaultAvatarBackground": "#000000",
231
- "--defaultAvatarText": "#ffffff",
232
- "--socialLinkFill": "#000000",
233
- "--linkBackground": "transparent",
234
- "--linkText": "#000000",
235
- "--linkHoverBackground": "rgba(0, 0, 0, 0.05)",
236
- "--linkHoverText": "#000000",
237
- "--linkShadow": "#000000",
238
- "--bannerBackground": "#ffffff",
239
- "--bannerText": "#000000",
240
- "--desktop-frame-color": "color-mix(in srgb, #ffffff 88%, black 12%)",
241
- "--profileFontFamilyPrimary":
242
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
243
- "--profileFontFamilySecondary":
244
- 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
245
- "--header-font-family":
246
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
247
- "--profileFontWeightNormal": "500",
248
- "--profileFontWeightBold": "700",
249
- "--profileDescriptionFontWeight": "500",
250
- "--linkTextFontWeight": "500",
251
- "--headerFontWeight": "700",
252
- "--header-font-weight": "600",
253
- "--embedLinkTextFontWeight": "500",
254
- "--signupSubmitTextFontWeight": "700",
255
- "--bannerFontWeight": "700",
256
- "--headerFontSize": "normal",
257
- "--header-bio-font-size": "16px",
258
- "--embedLinkTextFontSize": "14px",
259
- "--signupSubmitTextFontSize": "14px",
260
- "--linkHeaderFontSize": "14px",
261
- "--bannerFontSize": "14px",
262
- "--profileDescriptionLineHeight": "1.5",
263
- "--headerLineHeight": "1.5",
264
- "--header-text-color": "#000000",
265
- },
266
- },
267
- rounded: {
268
- name: "Fully Rounded",
269
- variables: {
270
- "--button-style-text": "#ffffff",
271
- "--button-style-background": "#059669",
272
- "--button-style-background-hover":
273
- "color-mix(in srgb, #059669 93%, #ffffff 7%)",
274
- "--button-style-border": "none",
275
- "--button-style-border-color": "transparent",
276
- "--button-style-shadow": "none",
277
- "--button-style-shadow-color": "#000000",
278
- "--button-style-contrast-color": "#d1fae5",
279
- "--button-style-radius": "9999px",
280
- "--button-style-inner-radius": "9999px",
281
- "--button-style-skeleton-color": "rgba(5, 150, 105, 0.1)",
282
- "--link-gap": "14px",
283
- "--link-inner-padding": "7px",
284
- "--link-preview-thumbnail-width": "160px",
285
- "--linkRadius": "9999px",
286
- "--profile-container-desktop-width": "580px",
287
- "--profileBackground": "#ecfdf5",
288
- "--background-contrast-color": "#1f2937",
289
- "--bodyText": "#1f2937",
290
- "--profileTitleText": "#1f2937",
291
- "--profileDescriptionText": "#1f2937",
292
- "--defaultAvatarBackground": "#059669",
293
- "--defaultAvatarText": "#ffffff",
294
- "--socialLinkFill": "#1f2937",
295
- "--linkBackground": "#059669",
296
- "--linkText": "#ffffff",
297
- "--linkHoverBackground": "color-mix(in srgb, #059669 93%, #ffffff 7%)",
298
- "--linkHoverText": "#ffffff",
299
- "--linkShadow": "#059669",
300
- "--bannerBackground": "#059669",
301
- "--bannerText": "#ffffff",
302
- "--desktop-frame-color": "color-mix(in srgb, #ecfdf5 88%, black 12%)",
303
- "--profileFontFamilyPrimary":
304
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
305
- "--profileFontFamilySecondary":
306
- 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
307
- "--header-font-family":
308
- 'Link Sans Product, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
309
- "--profileFontWeightNormal": "500",
310
- "--profileFontWeightBold": "700",
311
- "--profileDescriptionFontWeight": "500",
312
- "--linkTextFontWeight": "500",
313
- "--headerFontWeight": "700",
314
- "--header-font-weight": "600",
315
- "--embedLinkTextFontWeight": "500",
316
- "--signupSubmitTextFontWeight": "700",
317
- "--bannerFontWeight": "700",
318
- "--headerFontSize": "normal",
319
- "--header-bio-font-size": "16px",
320
- "--embedLinkTextFontSize": "14px",
321
- "--signupSubmitTextFontSize": "14px",
322
- "--linkHeaderFontSize": "14px",
323
- "--bannerFontSize": "14px",
324
- "--profileDescriptionLineHeight": "1.5",
325
- "--headerLineHeight": "1.5",
326
- "--header-text-color": "#1f2937",
327
- },
328
- },
329
- };
16
+ import { cn, renderCssVariables } from "../lib/utils";
17
+ import { SettingsPreview } from "../components/SettingsPreview";
18
+ import { THEME_PRESETS } from "../shared/theme-presets";
330
19
 
331
20
  export default function Preview() {
332
- const [selectedTab, setSelectedTab] = useState<"classic" | "featured">(
333
- "classic",
334
- );
335
- const [selectedTheme, setSelectedTheme] =
336
- useState<keyof typeof THEME_PRESETS>("default");
337
- const classicIframeRef = useRef<HTMLIFrameElement>(null);
338
- const featuredIframeRef = useRef<HTMLIFrameElement>(null);
21
+ // Initialize state from localStorage
22
+ const [selectedTab, setSelectedTab] = useState<"sheet" | "featured" | "settings">(() => {
23
+ const saved = localStorage.getItem("linkapp-preview-tab");
24
+ return (saved as "sheet" | "featured" | "settings") || "sheet";
25
+ });
26
+ const [selectedTheme, setSelectedTheme] = useState<keyof typeof THEME_PRESETS>(() => {
27
+ const saved = localStorage.getItem("linkapp-preview-theme");
28
+ return (saved as keyof typeof THEME_PRESETS) || "default";
29
+ });
30
+ const [selectedGroupLayoutOption, setSelectedGroupLayoutOption] = useState<string | undefined>(() => {
31
+ const saved = localStorage.getItem("linkapp-preview-groupLayoutOption");
32
+ return saved || undefined;
33
+ });
339
34
 
340
- // Send theme update to iframe
341
- const sendThemeUpdate = useCallback(
342
- (
343
- iframe: HTMLIFrameElement | null,
344
- themeName: keyof typeof THEME_PRESETS,
345
- ) => {
346
- if (!iframe || !iframe.contentWindow) return;
35
+ // Popup dialog state
36
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
347
37
 
348
- const theme = THEME_PRESETS[themeName];
349
- const message = {
350
- type: "THEME_UPDATE",
351
- payload: {
352
- name: themeName,
353
- variables: theme.variables,
354
- },
355
- };
38
+ // Generate unique IDs for iframes using timestamp
39
+ const sheetIframeId = useMemo(() => `preview-iframe-sheet-${Date.now()}`, []);
40
+ const featuredIframeId = useMemo(() => `preview-iframe-featured-${Date.now()}`, []);
41
+ const popupIframeId = useMemo(() => `preview-iframe-popup-${Date.now()}`, []);
356
42
 
357
- iframe.contentWindow.postMessage(message, "*");
358
- },
359
- [],
360
- );
43
+ // Save selected tab to localStorage
44
+ useEffect(() => {
45
+ localStorage.setItem("linkapp-preview-tab", selectedTab);
46
+ }, [selectedTab]);
47
+
48
+ // Save selected theme to localStorage
49
+ useEffect(() => {
50
+ localStorage.setItem("linkapp-preview-theme", selectedTheme);
51
+ }, [selectedTheme]);
361
52
 
362
- // Send theme update when theme changes or iframes load
53
+ // Save selected groupLayoutOption to localStorage
363
54
  useEffect(() => {
364
- const classicIframe = classicIframeRef.current;
365
- const featuredIframe = featuredIframeRef.current;
55
+ if (selectedGroupLayoutOption) {
56
+ localStorage.setItem("linkapp-preview-groupLayoutOption", selectedGroupLayoutOption);
57
+ } else {
58
+ localStorage.removeItem("linkapp-preview-groupLayoutOption");
59
+ }
60
+ }, [selectedGroupLayoutOption]);
61
+
62
+ // Handle iframe resize events
63
+ const handleResized = useCallback(
64
+ (ev: { iframe: IFrameComponent; height: number; width: number; type: string }): void => {
65
+ // IframeResizer automatically handles height adjustments
66
+ // This callback is available for debugging if needed
67
+ },
68
+ []
69
+ );
366
70
 
367
- const handleClassicLoad = () =>
368
- sendThemeUpdate(classicIframe, selectedTheme);
369
- const handleFeaturedLoad = () =>
370
- sendThemeUpdate(featuredIframe, selectedTheme);
71
+ // Handle postMessage from featured iframe for OPEN_POPUP
72
+ const handleMessage = useCallback((event: MessageEvent) => {
73
+ if (
74
+ event.data &&
75
+ typeof event.data === 'object' &&
76
+ event.data.source === 'linkapp' &&
77
+ event.data.type === 'OPEN_POPUP'
78
+ ) {
79
+ setIsPopupOpen(true);
80
+ }
81
+ }, []);
371
82
 
372
- // Send to already loaded iframes
373
- sendThemeUpdate(classicIframe, selectedTheme);
374
- sendThemeUpdate(featuredIframe, selectedTheme);
83
+ // Add message listener
84
+ useEffect(() => {
85
+ window.addEventListener('message', handleMessage);
86
+ return () => window.removeEventListener('message', handleMessage);
87
+ }, [handleMessage]);
375
88
 
376
- // Listen for iframe load events
377
- classicIframe?.addEventListener("load", handleClassicLoad);
378
- featuredIframe?.addEventListener("load", handleFeaturedLoad);
379
89
 
380
- return () => {
381
- classicIframe?.removeEventListener("load", handleClassicLoad);
382
- featuredIframe?.removeEventListener("load", handleFeaturedLoad);
383
- };
384
- }, [selectedTheme, sendThemeUpdate]);
90
+ const renderedCssVariables = useMemo(() => {
91
+ const themeVariables = THEME_PRESETS[selectedTheme] || THEME_PRESETS.default
92
+ return renderCssVariables(themeVariables.variables);
93
+ }, [selectedTheme])
385
94
 
386
95
  return (
96
+ <>
97
+ <style>{`:root {
98
+ ${renderedCssVariables}
99
+ }`}</style>
100
+
387
101
  <div
388
102
  className={cn("min-h-screen", {
389
103
  "bg-black/50": selectedTab === "classic",
390
- "bg-gray-200": selectedTab === "featured",
104
+ "bg-linktree-frame": selectedTab === "featured",
391
105
  })}
392
106
  id="preview"
393
107
  >
394
108
  <Tabs
395
109
  value={selectedTab}
396
110
  onValueChange={(value) =>
397
- setSelectedTab(value as "classic" | "featured")
111
+ setSelectedTab(value as "sheet" | "featured" | "settings")
398
112
  }
399
113
  >
400
114
  <Portal>
@@ -403,8 +117,9 @@ export default function Preview() {
403
117
  style={{ zIndex: 1000000 }}
404
118
  >
405
119
  <TabsList>
406
- <TabsTrigger value="classic">Classic</TabsTrigger>
120
+ <TabsTrigger value="sheet">Sheet</TabsTrigger>
407
121
  <TabsTrigger value="featured">Featured</TabsTrigger>
122
+ <TabsTrigger value="settings">Settings</TabsTrigger>
408
123
  </TabsList>
409
124
 
410
125
  {/* Theme Switcher */}
@@ -427,29 +142,66 @@ export default function Preview() {
427
142
  ))}
428
143
  </select>
429
144
  </div>
145
+
146
+ {/* Group Layout Option Switcher - Only show for Featured tab */}
147
+ {selectedTab === "featured" && (
148
+ <div className="flex items-center gap-2">
149
+ <label htmlFor="groupLayoutOption-select" className="text-sm font-medium">
150
+ Layout:
151
+ </label>
152
+ <select
153
+ id="groupLayoutOption-select"
154
+ value={selectedGroupLayoutOption || "default"}
155
+ onChange={(e) =>
156
+ setSelectedGroupLayoutOption(e.target.value === "default" ? undefined : e.target.value)
157
+ }
158
+ className="h-9 px-3 py-1 text-sm border border-input bg-background rounded-md focus:outline-none focus:ring-2 focus:ring-ring"
159
+ >
160
+ <option value="default">Default</option>
161
+ <option value="carousel">Carousel</option>
162
+ </select>
163
+ </div>
164
+ )}
430
165
  </div>
431
166
  </Portal>
432
167
 
433
168
  {/* Main Content with Padding for Fixed Tabs */}
434
169
  <div className="pt-20">
435
- <TabsContent value="classic" className="m-0">
436
- {/* Classic Modal - Always Open */}
170
+ <TabsContent value="sheet" className="m-0">
171
+ {/* Sheet Modal - Always Open */}
437
172
  <Dialog open={true} modal={false}>
438
173
  <DialogContent
439
- className="max-w-[512px] max-h-[calc(100%-64px)] overflow-auto"
174
+ className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
440
175
  showCloseButton={false}
441
176
  >
442
177
  <DialogHeader className="sticky top-0 bg-white px-4">
443
- <DialogTitle>Classic Layout Preview</DialogTitle>
178
+ <div className="grid h-16 grid-cols-[32px_auto_32px] items-center gap-4">
179
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
180
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className=" " role="img" aria-hidden="true"><path fill="currentColor" d="m10.65 3.85.35.36.7-.71-.35-.35-3-3h-.7l-3 3-.36.35.71.7.35-.35L7.5 1.71V10h1V1.7l2.15 2.15ZM1 5.5l.5-.5H4v1H2v9h12V6h-2V5h2.5l.5.5v10l-.5.5h-13l-.5-.5v-10Z"></path></svg>
181
+ </button>
182
+
183
+ <DialogTitle className="self-center truncate py-3 text-center">
184
+ Sheet
185
+ </DialogTitle>
186
+
187
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
188
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className=" " role="img" aria-hidden="true"><path fill="currentColor" d="m13.63 3.12.37-.38-.74-.74-.38.37.75.75ZM2.37 12.89l-.37.37.74.74.38-.37-.75-.75Zm.75-10.52L2.74 2 2 2.74l.37.38.75-.75Zm9.76 11.26.38.37.74-.74-.37-.38-.75.75Zm0-11.26L2.38 12.9l.74.74 10.5-10.51-.74-.75Zm-10.5.75 10.5 10.5.75-.73L3.12 2.37l-.75.75Z"></path></svg>
189
+ </button>
190
+ </div>
444
191
  </DialogHeader>
445
192
 
446
- <div className="mt-4">
447
- <iframe
448
- ref={classicIframeRef}
449
- src="/classic"
450
- className="w-full h-[600px] border-0 rounded-lg"
451
- title="Classic Layout Preview"
452
- />
193
+ <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
194
+ <div className="h-full overflow-y-auto overflow-x-hidden">
195
+ <IframeResizer
196
+ key={`sheet-${selectedTheme}`}
197
+ id={sheetIframeId}
198
+ src={`/sheet?theme=${selectedTheme}`}
199
+ style={{ height: '0px', width: '1px', minWidth: '100%', border: 0 }}
200
+ checkOrigin={false}
201
+ onResized={handleResized}
202
+ heightCalculationMethod="max"
203
+ />
204
+ </div>
453
205
  </div>
454
206
  </DialogContent>
455
207
  </Dialog>
@@ -457,19 +209,68 @@ export default function Preview() {
457
209
 
458
210
  <TabsContent value="featured" className="m-0 flex justify-center p-8">
459
211
  {/* Featured Iframe - Max Width 524px */}
460
- <div className="w-full max-w-[580px] shadow-xl flex flex-col rounded-3xl px-[28px] py-7 items-center bg-gray-100">
212
+ <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-linktree-profile-bg">
213
+
461
214
  <div className="w-full max-w-[524px]">
462
- <iframe
463
- ref={featuredIframeRef}
464
- src="/featured"
465
- className="w-full border-0 rounded-lg"
466
- title="Featured Layout Preview"
467
- />
215
+ <div className="bg-linktree-button-bg hover:bg-linktree-button-bg-hover border-linktree-button-border text-linktree-button-text rounded-linktree-button shadow-linktree-button overflow-hidden">
216
+ <IframeResizer
217
+ key={`featured-${selectedTheme}-${selectedGroupLayoutOption || 'default'}`}
218
+ id={featuredIframeId}
219
+ src={`/featured?theme=${selectedTheme}${selectedGroupLayoutOption ? `&groupLayoutOption=${selectedGroupLayoutOption}` : ''}`}
220
+ style={{ height: '0px', width: '1px', minWidth: '100%', border: 0 }}
221
+ checkOrigin={false}
222
+ onResized={handleResized}
223
+ heightCalculationMethod="max"
224
+ />
225
+ </div>
468
226
  </div>
469
227
  </div>
470
228
  </TabsContent>
229
+
230
+ <TabsContent value="settings" className="m-0">
231
+ <SettingsPreview settings={__SETTINGS_CONFIG__} />
232
+ </TabsContent>
471
233
  </div>
472
234
  </Tabs>
235
+
236
+ {/* Popup Dialog for OPEN_POPUP message */}
237
+ <Dialog open={isPopupOpen} onOpenChange={setIsPopupOpen} modal={false}>
238
+ <DialogContent
239
+ className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
240
+ showCloseButton={false}
241
+ >
242
+ <DialogHeader className="sticky top-0 bg-white px-4">
243
+ <div className="grid h-16 grid-cols-[32px_auto_32px] items-center gap-4">
244
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
245
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className=" " role="img" aria-hidden="true"><path fill="currentColor" d="m10.65 3.85.35.36.7-.71-.35-.35-3-3h-.7l-3 3-.36.35.71.7.35-.35L7.5 1.71V10h1V1.7l2.15 2.15ZM1 5.5l.5-.5H4v1H2v9h12V6h-2V5h2.5l.5.5v10l-.5.5h-13l-.5-.5v-10Z"></path></svg>
246
+ </button>
247
+
248
+ <DialogTitle className="self-center truncate py-3 text-center">
249
+ Sheet
250
+ </DialogTitle>
251
+
252
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
253
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className=" " role="img" aria-hidden="true"><path fill="currentColor" d="m13.63 3.12.37-.38-.74-.74-.38.37.75.75ZM2.37 12.89l-.37.37.74.74.38-.37-.75-.75Zm.75-10.52L2.74 2 2 2.74l.37.38.75-.75Zm9.76 11.26.38.37.74-.74-.37-.38-.75.75Zm0-11.26L2.38 12.9l.74.74 10.5-10.51-.74-.75Zm-10.5.75 10.5 10.5.75-.73L3.12 2.37l-.75.75Z"></path></svg>
254
+ </button>
255
+ </div>
256
+ </DialogHeader>
257
+
258
+ <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
259
+ <div className="h-full overflow-y-auto overflow-x-hidden">
260
+ <IframeResizer
261
+ key={`popup-${selectedTheme}`}
262
+ id={popupIframeId}
263
+ src={`/sheet?theme=${selectedTheme}`}
264
+ style={{ height: '0px', width: '1px', minWidth: '100%', border: 0 }}
265
+ checkOrigin={false}
266
+ onResized={handleResized}
267
+ heightCalculationMethod="max"
268
+ />
269
+ </div>
270
+ </div>
271
+ </DialogContent>
272
+ </Dialog>
473
273
  </div>
274
+ </>
474
275
  );
475
276
  }