@linktr.ee/linkapp 0.0.46 → 0.0.48

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.
@@ -0,0 +1,62 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+ import { Label } from "./label"
5
+
6
+ interface FieldProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ orientation?: "vertical" | "horizontal"
8
+ }
9
+
10
+ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
11
+ ({ className, orientation = "vertical", ...props }, ref) => (
12
+ <div
13
+ ref={ref}
14
+ role="group"
15
+ className={cn(
16
+ "space-y-2",
17
+ orientation === "horizontal" && "flex items-start gap-3 space-y-0",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ )
23
+ )
24
+ Field.displayName = "Field"
25
+
26
+ const FieldLabel = React.forwardRef<
27
+ React.ElementRef<typeof Label>,
28
+ React.ComponentPropsWithoutRef<typeof Label>
29
+ >(({ className, ...props }, ref) => (
30
+ <Label
31
+ ref={ref}
32
+ className={cn("text-sm font-medium text-gray-900", className)}
33
+ {...props}
34
+ />
35
+ ))
36
+ FieldLabel.displayName = "FieldLabel"
37
+
38
+ const FieldDescription = React.forwardRef<
39
+ HTMLParagraphElement,
40
+ React.HTMLAttributes<HTMLParagraphElement>
41
+ >(({ className, ...props }, ref) => (
42
+ <p
43
+ ref={ref}
44
+ className={cn("text-sm text-gray-500", className)}
45
+ {...props}
46
+ />
47
+ ))
48
+ FieldDescription.displayName = "FieldDescription"
49
+
50
+ const FieldError = React.forwardRef<
51
+ HTMLParagraphElement,
52
+ React.HTMLAttributes<HTMLParagraphElement>
53
+ >(({ className, ...props }, ref) => (
54
+ <p
55
+ ref={ref}
56
+ className={cn("text-sm text-red-500", className)}
57
+ {...props}
58
+ />
59
+ ))
60
+ FieldError.displayName = "FieldError"
61
+
62
+ export { Field, FieldLabel, FieldDescription, FieldError }
@@ -0,0 +1,25 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Input = React.forwardRef<
6
+ HTMLInputElement,
7
+ React.InputHTMLAttributes<HTMLInputElement>
8
+ >(({ className, type, ...props }, ref) => (
9
+ <input
10
+ type={type}
11
+ className={cn(
12
+ "flex h-9 w-full rounded-md border border-gray-300 bg-white px-3 py-1 text-sm shadow-sm transition-colors",
13
+ "file:border-0 file:bg-transparent file:text-sm file:font-medium",
14
+ "placeholder:text-gray-400",
15
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500 focus-visible:border-blue-500",
16
+ "disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500",
17
+ className
18
+ )}
19
+ ref={ref}
20
+ {...props}
21
+ />
22
+ ))
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
@@ -0,0 +1,21 @@
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const Label = React.forwardRef<
7
+ React.ElementRef<typeof LabelPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <LabelPrimitive.Root
11
+ ref={ref}
12
+ className={cn(
13
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ ))
19
+ Label.displayName = LabelPrimitive.Root.displayName
20
+
21
+ export { Label }
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Textarea = React.forwardRef<
6
+ HTMLTextAreaElement,
7
+ React.TextareaHTMLAttributes<HTMLTextAreaElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <textarea
10
+ className={cn(
11
+ "flex min-h-[80px] w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm transition-colors",
12
+ "placeholder:text-gray-400",
13
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500 focus-visible:border-blue-500",
14
+ "disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500 disabled:resize-none",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ ))
21
+ Textarea.displayName = "Textarea"
22
+
23
+ export { Textarea }
@@ -15,7 +15,7 @@ import {
15
15
  TabsList,
16
16
  TabsTrigger,
17
17
  } from "../components/ui/tabs";
18
- import { cn, renderCssVariables } from "../lib/utils";
18
+ import { cn } from "../lib/utils";
19
19
  import { THEME_PRESETS } from "../shared/theme-presets";
20
20
 
21
21
  function Chin({ title }: { title?: string }) {
@@ -36,28 +36,31 @@ function Chin({ title }: { title?: string }) {
36
36
 
37
37
  export default function Preview() {
38
38
  // These are injected by the dev server
39
- // @ts-expect-error - injected by dev server
40
- const hasFeatured = typeof __HAS_FEATURED__ !== "undefined" && __HAS_FEATURED__;
41
- // @ts-expect-error - injected by dev server
42
- const hasCarousel =
43
- typeof __HAS_CAROUSEL__ !== "undefined" && __HAS_CAROUSEL__;
39
+ // @ts-expect-error - injected by dev server
40
+ const hasFeatured =
41
+ typeof __HAS_FEATURED__ !== "undefined" && __HAS_FEATURED__;
42
+ // @ts-expect-error - injected by dev server
43
+ const hasCarousel =
44
+ typeof __HAS_CAROUSEL__ !== "undefined" && __HAS_CAROUSEL__;
44
45
 
45
- // Initialize state from localStorage
46
+ // Initialize state from localStorage
46
47
  const [selectedTab, setSelectedTab] = useState<
47
- "expanded" | "featured" | "carousel" | "settings"
48
- >(() => {
49
- const saved = localStorage.getItem("linkapp-preview-tab");
50
- if (saved === "featured" && !hasFeatured) {
51
- return "expanded";
52
- }
53
- if (saved === "carousel" && !hasCarousel) {
54
- return "expanded";
55
- }
56
- if (saved === "sheet") {
57
- return "expanded";
58
- }
59
- return (saved as "expanded" | "featured" | "carousel" | "settings") || "expanded";
60
- });
48
+ "expanded" | "featured" | "carousel" | "settings"
49
+ >(() => {
50
+ const saved = localStorage.getItem("linkapp-preview-tab");
51
+ if (saved === "featured" && !hasFeatured) {
52
+ return "expanded";
53
+ }
54
+ if (saved === "carousel" && !hasCarousel) {
55
+ return "expanded";
56
+ }
57
+ if (saved === "sheet") {
58
+ return "expanded";
59
+ }
60
+ return (
61
+ (saved as "expanded" | "featured" | "carousel" | "settings") || "expanded"
62
+ );
63
+ });
61
64
  const [selectedTheme, setSelectedTheme] = useState<
62
65
  keyof typeof THEME_PRESETS
63
66
  >(() => {
@@ -131,50 +134,49 @@ const hasCarousel =
131
134
  return () => window.removeEventListener("message", handleMessage);
132
135
  }, [handleMessage]);
133
136
 
134
- const renderedCssVariables = useMemo(() => {
135
- const themeVariables =
136
- THEME_PRESETS[selectedTheme] || THEME_PRESETS.default;
137
- return renderCssVariables(themeVariables.variables);
137
+ const themeVariables = useMemo(() => {
138
+ const theme = THEME_PRESETS[selectedTheme] || THEME_PRESETS.default;
139
+ return theme.variables;
138
140
  }, [selectedTheme]);
139
141
 
140
142
  return (
141
143
  <>
142
- <style>{`:root {
143
- ${renderedCssVariables}
144
- }`}</style>
145
-
146
144
  <div
147
145
  className={cn("min-h-screen", {
148
146
  "bg-black/50": selectedTab === "expanded",
149
- "bg-linktree-frame": selectedTab === "featured" || selectedTab === "carousel",
147
+ "bg-linktree-frame":
148
+ selectedTab === "featured" || selectedTab === "carousel",
150
149
  })}
151
150
  id="preview"
152
151
  >
153
- <Tabs
154
- value={selectedTab}
155
- onValueChange={(value) =>
156
- setSelectedTab(
157
- (value === "sheet" ? "expanded" : value) as
158
- | "expanded"
159
- | "featured"
160
- | "carousel"
161
- | "settings",
162
- )
163
- }
164
- >
152
+ <div style={themeVariables}>
153
+ <Tabs
154
+ value={selectedTab}
155
+ onValueChange={(value) =>
156
+ setSelectedTab(
157
+ (value === "sheet" ? "expanded" : value) as
158
+ | "expanded"
159
+ | "featured"
160
+ | "carousel"
161
+ | "settings",
162
+ )
163
+ }
164
+ >
165
165
  <Portal>
166
166
  <div
167
167
  className="fixed top-0 left-0 right-0 p-4 flex justify-center gap-4 bg-background border-b"
168
168
  style={{ zIndex: 1000000 }}
169
169
  >
170
170
  <TabsList>
171
- <TabsTrigger value="expanded">Expanded</TabsTrigger>
172
- {hasFeatured && <TabsTrigger value="featured">Featured</TabsTrigger>}
173
- {hasCarousel && (
174
- <TabsTrigger value="carousel">Carousel</TabsTrigger>
175
- )}
176
- <TabsTrigger value="settings">Settings</TabsTrigger>
177
- </TabsList>
171
+ <TabsTrigger value="expanded">Expanded</TabsTrigger>
172
+ {hasFeatured && (
173
+ <TabsTrigger value="featured">Featured</TabsTrigger>
174
+ )}
175
+ {hasCarousel && (
176
+ <TabsTrigger value="carousel">Carousel</TabsTrigger>
177
+ )}
178
+ <TabsTrigger value="settings">Settings</TabsTrigger>
179
+ </TabsList>
178
180
 
179
181
  {/* Theme Switcher */}
180
182
  <div className="flex items-center gap-2">
@@ -206,7 +208,6 @@ const hasCarousel =
206
208
  <TabsContent value="expanded" className="m-0">
207
209
  {/* Expanded Modal - Always Open */}
208
210
  <Dialog open={true} modal={false}>
209
- <DialogOverlay />
210
211
  <DialogContent
211
212
  className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
212
213
  showCloseButton={false}
@@ -252,216 +253,212 @@ const hasCarousel =
252
253
  ></path>
253
254
  </svg>
254
255
  </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={`expanded-${selectedTheme}`}
262
- id={expandedIframeId}
263
- src={`/expanded?theme=${selectedTheme}`}
264
- style={{
265
- height: "0px",
266
- width: "1px",
267
- minWidth: "100%",
268
- border: 0,
269
- }}
270
- checkOrigin={false}
271
- onResized={handleResized}
272
- heightCalculationMethod="max"
273
- />
274
- </div>
275
- </div>
276
- </DialogContent>
277
- </Dialog>
278
- </TabsContent>
279
-
280
- {hasFeatured && (
281
- <TabsContent
282
- value="featured"
283
- className="m-0 flex justify-center p-8"
284
- >
285
- <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-gray-100">
286
- <div className="w-full max-w-[524px]">
287
- {chinPosition === "above" && <Chin title={chinTitle} />}
288
-
289
- <div
290
- className={cn(
291
- "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",
292
- isOverlay && "relative",
293
- )}
294
- style={
295
- !__SETTINGS_CONFIG__?.featured_head_allow_unlocked_aspect_ratio
296
- ? { aspectRatio: "16 / 9" }
297
- : undefined
298
- }
299
- >
300
- {chinPosition === "overlay_above" && (
301
- <div className="absolute top-0 left-0 right-0 z-10">
302
- <Chin title={chinTitle} />
303
- </div>
304
- )}
305
-
306
- <IframeResizer
307
- key={`featured-${selectedTheme}`}
308
- id={featuredIframeId}
309
- src={`/featured?theme=${selectedTheme}`}
310
- style={{
311
- height: "0px",
312
- width: "1px",
313
- minWidth: "100%",
314
- border: 0,
315
- }}
316
- checkOrigin={false}
317
- onResized={handleResized}
318
- heightCalculationMethod="max"
319
- />
320
-
321
- {chinPosition === "overlayBelow" && (
322
- <div className="absolute bottom-0 left-0 right-0 z-10">
323
- <Chin title={chinTitle} />
324
- </div>
325
- )}
326
- </div>
327
-
328
- {chinPosition === "below" && <Chin title={chinTitle} />}
329
- </div>
330
- </div>
331
- </TabsContent>
332
- )}
333
-
334
- {hasCarousel && (
335
- <TabsContent
336
- value="carousel"
337
- className="m-0 flex justify-center p-8"
338
- >
339
- <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-gray-100">
340
- <div className="w-full max-w-[524px]">
341
- {chinPosition === "above" && <Chin title={chinTitle} />}
342
-
343
- <div
344
- className={cn(
345
- "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",
346
- isOverlay && "relative",
347
- )}
348
- style={
349
- !__SETTINGS_CONFIG__?.featured_head_allow_unlocked_aspect_ratio
350
- ? { aspectRatio: "11 / 8" }
351
- : undefined
352
- }
353
- >
354
- {chinPosition === "overlay_above" && (
355
- <div className="absolute top-0 left-0 right-0 z-10">
356
- <Chin title={chinTitle} />
357
- </div>
358
- )}
359
-
360
- <IframeResizer
361
- key={`carousel-${selectedTheme}`}
362
- id={carouselIframeId}
363
- src={`/featured-carousel?theme=${selectedTheme}`}
364
- style={{
365
- height: "0px",
366
- width: "1px",
367
- minWidth: "100%",
368
- border: 0,
369
- }}
370
- checkOrigin={false}
371
- onResized={handleResized}
372
- heightCalculationMethod="max"
373
- />
374
-
375
- {chinPosition === "overlayBelow" && (
376
- <div className="absolute bottom-0 left-0 right-0 z-10">
377
- <Chin title={chinTitle} />
378
- </div>
379
- )}
380
- </div>
381
-
382
- {chinPosition === "below" && <Chin title={chinTitle} />}
383
- </div>
384
- </div>
385
- </TabsContent>
386
- )}
387
-
388
- <TabsContent value="settings" className="m-0">
389
- <SettingsPreview settings={__SETTINGS_CONFIG__} />
390
- </TabsContent>
391
- </div>
392
- </Tabs>
393
-
394
- {/* Popup Dialog for EXPAND_LINK_APP message */}
395
- <Dialog open={isPopupOpen} onOpenChange={setIsPopupOpen} modal={false}>
396
- <DialogContent
397
- className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
398
- showCloseButton={false}
399
- >
400
- <DialogHeader className="sticky top-0 bg-white px-4">
401
- <div className="grid h-16 grid-cols-[32px_auto_32px] items-center gap-4">
402
- <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
403
- <svg
404
- width="16"
405
- height="16"
406
- viewBox="0 0 16 16"
407
- fill="none"
408
- xmlns="http://www.w3.org/2000/svg"
409
- className=" "
410
- role="img"
411
- aria-hidden="true"
412
- >
413
- <path
414
- fill="currentColor"
415
- 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"
416
- ></path>
417
- </svg>
418
- </button>
419
-
420
- <DialogTitle className="self-center truncate py-3 text-center">
421
- Expanded
422
- </DialogTitle>
423
-
424
- <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
425
- <svg
426
- width="16"
427
- height="16"
428
- viewBox="0 0 16 16"
429
- fill="none"
430
- xmlns="http://www.w3.org/2000/svg"
431
- className=" "
432
- role="img"
433
- aria-hidden="true"
434
- >
435
- <path
436
- fill="currentColor"
437
- 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"
438
- ></path>
439
- </svg>
440
- </button>
441
- </div>
442
- </DialogHeader>
443
-
444
- <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
445
- <div className="h-full overflow-y-auto overflow-x-hidden">
446
- <IframeResizer
447
- key={`popup-${selectedTheme}`}
448
- id={popupIframeId}
449
- src={`/expanded?theme=${selectedTheme}`}
450
- style={{
451
- height: "0px",
452
- width: "1px",
453
- minWidth: "100%",
454
- border: 0,
455
- }}
456
- checkOrigin={false}
457
- onResized={handleResized}
458
- heightCalculationMethod="max"
459
- />
460
- </div>
461
- </div>
462
- </DialogContent>
463
- </Dialog>
464
- </div>
465
- </>
466
- );
467
- }
256
+ </div>
257
+ </DialogHeader>
258
+
259
+ <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
260
+ <div className="h-full overflow-y-auto overflow-x-hidden">
261
+ <IframeResizer
262
+ key={`expanded-${selectedTheme}`}
263
+ id={expandedIframeId}
264
+ src={`/expanded?theme=${selectedTheme}`}
265
+ style={{
266
+ height: "0px",
267
+ width: "1px",
268
+ minWidth: "100%",
269
+ border: 0,
270
+ }}
271
+ checkOrigin={false}
272
+ onResized={handleResized}
273
+ heightCalculationMethod="max"
274
+ />
275
+ </div>
276
+ </div>
277
+ </DialogContent>
278
+ </Dialog>
279
+ </TabsContent>
280
+
281
+ {hasFeatured && (
282
+ <TabsContent
283
+ value="featured"
284
+ className="m-0 flex justify-center p-8"
285
+ >
286
+ <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-gray-100">
287
+ <div className="w-full max-w-[524px]">
288
+ {chinPosition === "above" && <Chin title={chinTitle} />}
289
+
290
+ <div
291
+ className={cn(
292
+ "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",
293
+ isOverlay && "relative",
294
+ )}
295
+ >
296
+ {chinPosition === "overlay_above" && (
297
+ <div className="absolute top-0 left-0 right-0 z-10">
298
+ <Chin title={chinTitle} />
299
+ </div>
300
+ )}
301
+
302
+ <IframeResizer
303
+ key={`featured-${selectedTheme}`}
304
+ id={featuredIframeId}
305
+ src={`/featured?theme=${selectedTheme}`}
306
+ style={{
307
+ height: "0px",
308
+ width: "1px",
309
+ minWidth: "100%",
310
+ border: 0,
311
+ }}
312
+ checkOrigin={false}
313
+ onResized={handleResized}
314
+ heightCalculationMethod="max"
315
+ />
316
+
317
+ {chinPosition === "overlayBelow" && (
318
+ <div className="absolute bottom-0 left-0 right-0 z-10">
319
+ <Chin title={chinTitle} />
320
+ </div>
321
+ )}
322
+ </div>
323
+
324
+ {chinPosition === "below" && <Chin title={chinTitle} />}
325
+ </div>
326
+ </div>
327
+ </TabsContent>
328
+ )}
329
+
330
+ {hasCarousel && (
331
+ <TabsContent
332
+ value="carousel"
333
+ className="m-0 flex justify-center p-8"
334
+ >
335
+ <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-gray-100">
336
+ <div className="w-full max-w-[524px]">
337
+ {chinPosition === "above" && <Chin title={chinTitle} />}
338
+
339
+ <div
340
+ className={cn(
341
+ "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",
342
+ isOverlay && "relative",
343
+ )}
344
+ style={
345
+ !__SETTINGS_CONFIG__?.featured_head_allow_unlocked_aspect_ratio
346
+ ? { aspectRatio: "11 / 8" }
347
+ : undefined
348
+ }
349
+ >
350
+ {chinPosition === "overlay_above" && (
351
+ <div className="absolute top-0 left-0 right-0 z-10">
352
+ <Chin title={chinTitle} />
353
+ </div>
354
+ )}
355
+
356
+ <IframeResizer
357
+ key={`carousel-${selectedTheme}`}
358
+ id={carouselIframeId}
359
+ src={`/featured-carousel?theme=${selectedTheme}`}
360
+ style={{
361
+ height: "0px",
362
+ width: "1px",
363
+ minWidth: "100%",
364
+ border: 0,
365
+ }}
366
+ checkOrigin={false}
367
+ onResized={handleResized}
368
+ heightCalculationMethod="max"
369
+ />
370
+
371
+ {chinPosition === "overlayBelow" && (
372
+ <div className="absolute bottom-0 left-0 right-0 z-10">
373
+ <Chin title={chinTitle} />
374
+ </div>
375
+ )}
376
+ </div>
377
+
378
+ {chinPosition === "below" && <Chin title={chinTitle} />}
379
+ </div>
380
+ </div>
381
+ </TabsContent>
382
+ )}
383
+
384
+ <TabsContent value="settings" className="m-0">
385
+ <SettingsPreview settings={__SETTINGS_CONFIG__} />
386
+ </TabsContent>
387
+ </div>
388
+ </Tabs>
389
+ </div>
390
+
391
+ {/* Popup Dialog for EXPAND_LINK_APP message */}
392
+ <Dialog open={isPopupOpen} onOpenChange={setIsPopupOpen}>
393
+ <DialogContent
394
+ className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
395
+ showCloseButton={false}
396
+ >
397
+ <DialogHeader className="sticky top-0 bg-white px-4">
398
+ <div className="grid h-16 grid-cols-[32px_auto_32px] items-center gap-4">
399
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
400
+ <svg
401
+ width="16"
402
+ height="16"
403
+ viewBox="0 0 16 16"
404
+ fill="none"
405
+ xmlns="http://www.w3.org/2000/svg"
406
+ className=" "
407
+ role="img"
408
+ aria-hidden="true"
409
+ >
410
+ <path
411
+ fill="currentColor"
412
+ 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"
413
+ ></path>
414
+ </svg>
415
+ </button>
416
+
417
+ <DialogTitle className="self-center truncate py-3 text-center">
418
+ Expanded
419
+ </DialogTitle>
420
+
421
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
422
+ <svg
423
+ width="16"
424
+ height="16"
425
+ viewBox="0 0 16 16"
426
+ fill="none"
427
+ xmlns="http://www.w3.org/2000/svg"
428
+ className=" "
429
+ role="img"
430
+ aria-hidden="true"
431
+ >
432
+ <path
433
+ fill="currentColor"
434
+ 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"
435
+ ></path>
436
+ </svg>
437
+ </button>
438
+ </div>
439
+ </DialogHeader>
440
+
441
+ <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
442
+ <div className="h-full overflow-y-auto overflow-x-hidden">
443
+ <IframeResizer
444
+ key={`popup-${selectedTheme}`}
445
+ id={popupIframeId}
446
+ src={`/expanded?theme=${selectedTheme}`}
447
+ style={{
448
+ height: "0px",
449
+ width: "1px",
450
+ minWidth: "100%",
451
+ border: 0,
452
+ }}
453
+ checkOrigin={false}
454
+ onResized={handleResized}
455
+ heightCalculationMethod="max"
456
+ />
457
+ </div>
458
+ </div>
459
+ </DialogContent>
460
+ </Dialog>
461
+ </div>
462
+ </>
463
+ );
464
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"setup-runtime.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/setup-runtime.ts"],"names":[],"mappings":"AAkQA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAyBtD"}
1
+ {"version":3,"file":"setup-runtime.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/setup-runtime.ts"],"names":[],"mappings":"AAuQA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAyBtD"}
@@ -125,25 +125,26 @@ function App({ data }: { data: ExtensionData }) {
125
125
  const baseLayout = data.__layout || params.get('layout') || 'classic'
126
126
  const groupLayoutOption = data.groupLayoutOption || params.get('groupLayoutOption')
127
127
 
128
- // Simple layout selection: expanded.tsx (or legacy sheet.tsx) OR featured.tsx OR featured-carousel.tsx
129
- let layoutName: string
128
+ // Simple layout selection with context-aware fallbacks
129
+ let LayoutComponent
130
130
 
131
- if (baseLayout === 'classic' || baseLayout === 'expanded' || baseLayout === 'sheet') {
132
- layoutName = 'expanded'
133
- } else if (baseLayout === 'featured' && groupLayoutOption === 'carousel') {
134
- layoutName = 'featured-carousel'
131
+ if (baseLayout === 'featured' && groupLayoutOption === 'carousel') {
132
+ // Featured carousel: try carousel → featured → expanded → sheet
133
+ LayoutComponent = layouts['featured-carousel'] || layouts.featured || layouts.expanded || layouts.sheet
134
+ } else if (baseLayout === 'featured') {
135
+ // Featured: try featured → expanded → sheet
136
+ LayoutComponent = layouts.featured || layouts.expanded || layouts.sheet
135
137
  } else {
136
- layoutName = 'featured'
138
+ // Classic/expanded: try expanded → sheet
139
+ LayoutComponent = layouts.expanded || layouts.sheet
137
140
  }
138
141
 
139
- const LayoutComponent = layouts[layoutName] || layouts.expanded || layouts.sheet
140
-
141
142
  // Debug logging (only in development or when component is missing)
142
143
  if (!isInIframe || !LayoutComponent) {
143
144
  console.log('[LinkApp] Layout selection:', {
144
145
  __layout: data.__layout,
145
146
  groupLayoutOption: data.groupLayoutOption,
146
- selectedLayout: layoutName,
147
+ baseLayout,
147
148
  availableLayouts: Object.keys(layouts),
148
149
  })
149
150
  }
@@ -152,7 +153,7 @@ function App({ data }: { data: ExtensionData }) {
152
153
  return (
153
154
  <div style={{ padding: '20px', fontFamily: 'system-ui' }}>
154
155
  <h1>Error: Layout not found</h1>
155
- <p>Layout "{layoutName}" does not exist.</p>
156
+ <p>No suitable layout component could be found.</p>
156
157
  <p>Available layouts: {Object.keys(layouts).join(', ')}</p>
157
158
  </div>
158
159
  )
@@ -164,15 +165,19 @@ ${renderLogic}
164
165
  }
165
166
 
166
167
  const postExtensionReadyMessage = () => {
167
- const height = rootElement.clientHeight
168
- const message = {
169
- type: 'extension-ready',
170
- data: {
171
- ready: true,
172
- height: height,
173
- },
174
- }
175
- window.parent.postMessage(message, '*')
168
+ // Use requestAnimationFrame to batch layout reads with browser paint cycle
169
+ // This prevents forced synchronous layout and improves scroll performance
170
+ requestAnimationFrame(() => {
171
+ const height = rootElement.clientHeight
172
+ const message = {
173
+ type: 'extension-ready',
174
+ data: {
175
+ ready: true,
176
+ height: height,
177
+ },
178
+ }
179
+ window.parent.postMessage(message, '*')
180
+ })
176
181
  }
177
182
 
178
183
  type RootInstance = ReturnType<typeof createRoot>
@@ -237,7 +242,7 @@ if (isInIframe) {
237
242
  { type: 'interaction-event', data: customEvent.detail },
238
243
  '*'
239
244
  )
240
- })
245
+ }, { passive: true })
241
246
  } else {
242
247
  // Development mode: render immediately with mock data
243
248
  renderApp(mockContext)
@@ -1 +1 @@
1
- {"version":3,"file":"setup-runtime.js","sourceRoot":"","sources":["../../../src/lib/utils/setup-runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup-runtime.js","sourceRoot":"","sources":["../../../src/lib/utils/setup-runtime.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/linkapp",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "Development, build, and deployment tooling for LinkApps",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,8 +39,13 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@clack/prompts": "^0.8.2",
42
+ "@radix-ui/react-checkbox": "^1.1.4",
42
43
  "@radix-ui/react-dialog": "^1.1.3",
44
+ "@radix-ui/react-label": "^2.1.2",
43
45
  "@radix-ui/react-portal": "^1.1.3",
46
+ "@radix-ui/react-radio-group": "^1.2.2",
47
+ "@radix-ui/react-select": "^2.1.6",
48
+ "@radix-ui/react-switch": "^1.1.2",
44
49
  "@radix-ui/react-tabs": "^1.1.3",
45
50
  "@rsbuild/core": "^1.6.6",
46
51
  "@rsbuild/plugin-react": "^1.4.2",
@@ -26,6 +26,34 @@
26
26
  }
27
27
  </style>
28
28
 
29
+ <!-- iOS Safari scroll momentum fix -->
30
+ <!-- Prevents iframe from creating scroll context that interrupts parent momentum -->
31
+ <style id="ios-scroll-fix">
32
+ html {
33
+ overflow: hidden;
34
+ height: 100%;
35
+ /* Force GPU compositor layer - reduces main thread sync */
36
+ transform: translateZ(0);
37
+ will-change: transform;
38
+ /* Contain layout to prevent parent reflows */
39
+ contain: layout style paint;
40
+ }
41
+
42
+ body {
43
+ overflow: hidden;
44
+ height: 100%;
45
+ min-height: 100%;
46
+ /* Prevent overscroll behavior */
47
+ overscroll-behavior: none;
48
+ }
49
+
50
+ #root {
51
+ /* Allow taps but not scroll gestures */
52
+ touch-action: manipulation;
53
+ overflow: visible;
54
+ }
55
+ </style>
56
+
29
57
  <script>
30
58
  /**
31
59
  * Helper function to convert CSS variables object to CSS string