@linktr.ee/linkapp 0.0.48 → 0.0.49

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 (203) hide show
  1. package/README.md +1 -1
  2. package/dev-server/components/form/array-field.tsx +115 -0
  3. package/dev-server/components/form/file-field.tsx +48 -0
  4. package/dev-server/components/form/form-element.tsx +304 -0
  5. package/dev-server/components/form/link-behavior-field.tsx +68 -0
  6. package/dev-server/components/form/location-field.tsx +60 -0
  7. package/dev-server/components/settings-preview.tsx +138 -302
  8. package/dev-server/components/ui/checkbox.tsx +29 -0
  9. package/dev-server/components/ui/dialog.tsx +2 -10
  10. package/dev-server/components/ui/field.tsx +24 -49
  11. package/dev-server/components/ui/input.tsx +20 -21
  12. package/dev-server/components/ui/label.tsx +4 -4
  13. package/dev-server/components/ui/radio-group.tsx +37 -0
  14. package/dev-server/components/ui/select.tsx +153 -0
  15. package/dev-server/components/ui/switch.tsx +31 -0
  16. package/dev-server/components/ui/tabs.tsx +1 -1
  17. package/dev-server/components/ui/textarea.tsx +18 -19
  18. package/dev-server/env.d.ts +4 -1
  19. package/dev-server/expanded/main.tsx +20 -22
  20. package/dev-server/expanded.html +0 -1
  21. package/dev-server/featured/main.tsx +29 -36
  22. package/dev-server/featured-carousel.html +0 -1
  23. package/dev-server/featured.html +0 -1
  24. package/dev-server/index.html +1 -7
  25. package/dev-server/lib/utils.ts +3 -3
  26. package/dev-server/package.json +3 -3
  27. package/dev-server/postcss/tailwind-source-fallback.js +2 -7
  28. package/dev-server/postcss.config.mjs +2 -2
  29. package/dev-server/preview/Preview.tsx +310 -350
  30. package/dev-server/preview/main.tsx +8 -8
  31. package/dev-server/preview/preview.css +0 -1
  32. package/dev-server/public/site.webmanifest +1 -1
  33. package/dev-server/rsbuild.config.ts +1 -1
  34. package/dev-server/shared/dev-parent-simulator.ts +219 -0
  35. package/dev-server/shared/theme-presets.ts +71 -75
  36. package/dev-server/shared/theme-utils.ts +11 -11
  37. package/dist/cli.js +18 -12
  38. package/dist/cli.js.map +1 -1
  39. package/dist/commands/add.d.ts.map +1 -1
  40. package/dist/commands/add.js +27 -42
  41. package/dist/commands/add.js.map +1 -1
  42. package/dist/commands/build.d.ts.map +1 -1
  43. package/dist/commands/build.js +26 -16
  44. package/dist/commands/build.js.map +1 -1
  45. package/dist/commands/deploy.d.ts +1 -11
  46. package/dist/commands/deploy.d.ts.map +1 -1
  47. package/dist/commands/deploy.js +3 -13
  48. package/dist/commands/deploy.js.map +1 -1
  49. package/dist/commands/dev.d.ts.map +1 -1
  50. package/dist/commands/dev.js +132 -388
  51. package/dist/commands/dev.js.map +1 -1
  52. package/dist/commands/login.d.ts.map +1 -1
  53. package/dist/commands/login.js +17 -29
  54. package/dist/commands/login.js.map +1 -1
  55. package/dist/commands/logout.d.ts.map +1 -1
  56. package/dist/commands/logout.js +6 -11
  57. package/dist/commands/logout.js.map +1 -1
  58. package/dist/commands/rollback.d.ts +10 -0
  59. package/dist/commands/rollback.d.ts.map +1 -0
  60. package/dist/commands/rollback.js +148 -0
  61. package/dist/commands/rollback.js.map +1 -0
  62. package/dist/commands/status.d.ts +8 -0
  63. package/dist/commands/status.d.ts.map +1 -0
  64. package/dist/commands/status.js +96 -0
  65. package/dist/commands/status.js.map +1 -0
  66. package/dist/commands/test-url-match-rules.d.ts.map +1 -1
  67. package/dist/commands/test-url-match-rules.js +20 -26
  68. package/dist/commands/test-url-match-rules.js.map +1 -1
  69. package/dist/lib/auth/device-flow.d.ts +1 -1
  70. package/dist/lib/auth/device-flow.d.ts.map +1 -1
  71. package/dist/lib/auth/device-flow.js +3 -3
  72. package/dist/lib/auth/device-flow.js.map +1 -1
  73. package/dist/lib/auth/token-storage.d.ts.map +1 -1
  74. package/dist/lib/auth/token-storage.js +14 -37
  75. package/dist/lib/auth/token-storage.js.map +1 -1
  76. package/dist/lib/build/detect-layouts.d.ts.map +1 -1
  77. package/dist/lib/build/detect-layouts.js +27 -13
  78. package/dist/lib/build/detect-layouts.js.map +1 -1
  79. package/dist/lib/config/load-config.d.ts.map +1 -1
  80. package/dist/lib/config/load-config.js +0 -2
  81. package/dist/lib/config/load-config.js.map +1 -1
  82. package/dist/lib/deploy/deploy-output.d.ts +2 -1
  83. package/dist/lib/deploy/deploy-output.d.ts.map +1 -1
  84. package/dist/lib/deploy/deploy-output.js +9 -1
  85. package/dist/lib/deploy/deploy-output.js.map +1 -1
  86. package/dist/lib/deploy/deploy-phases.d.ts +2 -0
  87. package/dist/lib/deploy/deploy-phases.d.ts.map +1 -1
  88. package/dist/lib/deploy/deploy-phases.js +9 -23
  89. package/dist/lib/deploy/deploy-phases.js.map +1 -1
  90. package/dist/lib/deploy/deploy-utils.d.ts +15 -7
  91. package/dist/lib/deploy/deploy-utils.d.ts.map +1 -1
  92. package/dist/lib/deploy/deploy-utils.js +49 -36
  93. package/dist/lib/deploy/deploy-utils.js.map +1 -1
  94. package/dist/lib/deploy/generate-manifest-files.d.ts.map +1 -1
  95. package/dist/lib/deploy/generate-manifest-files.js +13 -39
  96. package/dist/lib/deploy/generate-manifest-files.js.map +1 -1
  97. package/dist/lib/deploy/pack-project.d.ts.map +1 -1
  98. package/dist/lib/deploy/pack-project.js +34 -20
  99. package/dist/lib/deploy/pack-project.js.map +1 -1
  100. package/dist/lib/deploy/slot-manager.d.ts +54 -0
  101. package/dist/lib/deploy/slot-manager.d.ts.map +1 -0
  102. package/dist/lib/deploy/slot-manager.js +72 -0
  103. package/dist/lib/deploy/slot-manager.js.map +1 -0
  104. package/dist/lib/deploy/test-url-match-rules.d.ts +10 -2
  105. package/dist/lib/deploy/test-url-match-rules.d.ts.map +1 -1
  106. package/dist/lib/deploy/test-url-match-rules.js +1 -1
  107. package/dist/lib/deploy/test-url-match-rules.js.map +1 -1
  108. package/dist/lib/deploy/upload.d.ts +1 -0
  109. package/dist/lib/deploy/upload.d.ts.map +1 -1
  110. package/dist/lib/deploy/upload.js +15 -24
  111. package/dist/lib/deploy/upload.js.map +1 -1
  112. package/dist/lib/deploy/validation.d.ts.map +1 -1
  113. package/dist/lib/deploy/validation.js +43 -48
  114. package/dist/lib/deploy/validation.js.map +1 -1
  115. package/dist/lib/rsbuild/config-factory.d.ts.map +1 -1
  116. package/dist/lib/rsbuild/config-factory.js +10 -17
  117. package/dist/lib/rsbuild/config-factory.js.map +1 -1
  118. package/dist/lib/rsbuild/plugins/asset-versioning.d.ts.map +1 -1
  119. package/dist/lib/rsbuild/plugins/asset-versioning.js +4 -14
  120. package/dist/lib/rsbuild/plugins/asset-versioning.js.map +1 -1
  121. package/dist/lib/rsbuild/plugins/brotli-compression.d.ts.map +1 -1
  122. package/dist/lib/rsbuild/plugins/brotli-compression.js +4 -4
  123. package/dist/lib/rsbuild/plugins/brotli-compression.js.map +1 -1
  124. package/dist/lib/rsbuild/plugins/copy-public.d.ts.map +1 -1
  125. package/dist/lib/rsbuild/plugins/copy-public.js.map +1 -1
  126. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.d.ts.map +1 -1
  127. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.js +1 -3
  128. package/dist/lib/rsbuild/postcss/tailwind-source-fallback.js.map +1 -1
  129. package/dist/lib/utils/console.d.ts +8 -0
  130. package/dist/lib/utils/console.d.ts.map +1 -0
  131. package/dist/lib/utils/console.js +10 -0
  132. package/dist/lib/utils/console.js.map +1 -0
  133. package/dist/lib/utils/filesystem.d.ts +9 -0
  134. package/dist/lib/utils/filesystem.d.ts.map +1 -0
  135. package/dist/lib/utils/filesystem.js +30 -0
  136. package/dist/lib/utils/filesystem.js.map +1 -0
  137. package/dist/lib/utils/formatters.d.ts +8 -0
  138. package/dist/lib/utils/formatters.d.ts.map +1 -0
  139. package/dist/lib/utils/formatters.js +22 -0
  140. package/dist/lib/utils/formatters.js.map +1 -0
  141. package/dist/lib/utils/index.d.ts +7 -0
  142. package/dist/lib/utils/index.d.ts.map +1 -0
  143. package/dist/lib/utils/index.js +7 -0
  144. package/dist/lib/utils/index.js.map +1 -0
  145. package/dist/lib/utils/setup-runtime.d.ts.map +1 -1
  146. package/dist/lib/utils/setup-runtime.js +22 -63
  147. package/dist/lib/utils/setup-runtime.js.map +1 -1
  148. package/dist/schema/config.schema.d.ts +9 -48
  149. package/dist/schema/config.schema.d.ts.map +1 -1
  150. package/dist/schema/config.schema.js +119 -120
  151. package/dist/schema/config.schema.js.map +1 -1
  152. package/dist/sdk/hooks/mocks.d.ts +9 -0
  153. package/dist/sdk/hooks/mocks.d.ts.map +1 -0
  154. package/dist/sdk/hooks/mocks.js +17 -0
  155. package/dist/sdk/hooks/mocks.js.map +1 -0
  156. package/dist/sdk/hooks/use-audience-manager.d.ts +44 -0
  157. package/dist/sdk/hooks/use-audience-manager.d.ts.map +1 -0
  158. package/dist/sdk/hooks/use-audience-manager.js +109 -0
  159. package/dist/sdk/hooks/use-audience-manager.js.map +1 -0
  160. package/dist/sdk/hooks/use-ip.d.ts +45 -0
  161. package/dist/sdk/hooks/use-ip.d.ts.map +1 -0
  162. package/dist/sdk/hooks/use-ip.js +46 -0
  163. package/dist/sdk/hooks/use-ip.js.map +1 -0
  164. package/dist/sdk/hooks/use-sdk-request.d.ts +46 -0
  165. package/dist/sdk/hooks/use-sdk-request.d.ts.map +1 -0
  166. package/dist/sdk/hooks/use-sdk-request.js +65 -0
  167. package/dist/sdk/hooks/use-sdk-request.js.map +1 -0
  168. package/dist/sdk/hooks/use-theme.d.ts +45 -0
  169. package/dist/sdk/hooks/use-theme.d.ts.map +1 -0
  170. package/dist/sdk/hooks/use-theme.js +97 -0
  171. package/dist/sdk/hooks/use-theme.js.map +1 -0
  172. package/dist/sdk/hooks/use-visitor.d.ts +41 -0
  173. package/dist/sdk/hooks/use-visitor.d.ts.map +1 -0
  174. package/dist/sdk/hooks/use-visitor.js +42 -0
  175. package/dist/sdk/hooks/use-visitor.js.map +1 -0
  176. package/dist/sdk/hooks/validation.d.ts +8 -0
  177. package/dist/sdk/hooks/validation.d.ts.map +1 -0
  178. package/dist/sdk/hooks/validation.js +13 -0
  179. package/dist/sdk/hooks/validation.js.map +1 -0
  180. package/dist/sdk/index.d.ts +17 -5
  181. package/dist/sdk/index.d.ts.map +1 -1
  182. package/dist/sdk/index.js +16 -5
  183. package/dist/sdk/index.js.map +1 -1
  184. package/dist/sdk/message-bus.d.ts +59 -0
  185. package/dist/sdk/message-bus.d.ts.map +1 -0
  186. package/dist/sdk/message-bus.js +152 -0
  187. package/dist/sdk/message-bus.js.map +1 -0
  188. package/dist/sdk/messages.d.ts +121 -0
  189. package/dist/sdk/messages.d.ts.map +1 -0
  190. package/dist/sdk/messages.js +9 -0
  191. package/dist/sdk/messages.js.map +1 -0
  192. package/dist/sdk/send-message.d.ts +1 -1
  193. package/dist/sdk/send-message.js +18 -18
  194. package/dist/sdk/send-message.js.map +1 -1
  195. package/dist/sdk/use-expand-link-app.d.ts +3 -3
  196. package/dist/sdk/use-expand-link-app.d.ts.map +1 -1
  197. package/dist/sdk/use-expand-link-app.js +9 -5
  198. package/dist/sdk/use-expand-link-app.js.map +1 -1
  199. package/dist/types.d.ts +235 -55
  200. package/dist/types.d.ts.map +1 -1
  201. package/dist/types.js +8 -3
  202. package/dist/types.js.map +1 -1
  203. package/package.json +3 -9
@@ -1,25 +1,22 @@
1
- import { Portal } from "@radix-ui/react-portal";
2
- import IframeResizer, { type IFrameComponent } from "iframe-resizer-react";
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { SettingsPreview } from "../components/settings-preview";
5
- import {
6
- Dialog,
7
- DialogContent,
8
- DialogHeader,
9
- DialogOverlay,
10
- DialogTitle,
11
- } from "../components/ui/dialog";
12
- import {
13
- Tabs,
14
- TabsContent,
15
- TabsList,
16
- TabsTrigger,
17
- } from "../components/ui/tabs";
18
- import { cn } from "../lib/utils";
19
- import { THEME_PRESETS } from "../shared/theme-presets";
1
+ import { Portal } from '@radix-ui/react-portal'
2
+ import IframeResizer from 'iframe-resizer-react'
3
+ import { useCallback, useEffect, useMemo, useState } from 'react'
4
+ import { SettingsPreview } from '../components/settings-preview'
5
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../components/ui/dialog'
6
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs'
7
+ import { cn } from '../lib/utils'
8
+ import { broadcastThemeUpdate, createDevParentSimulator } from '../shared/dev-parent-simulator'
9
+ import { THEME_PRESETS } from '../shared/theme-presets'
10
+
11
+ const IFRAME_STYLE = {
12
+ height: '0px',
13
+ width: '1px',
14
+ minWidth: '100%',
15
+ border: 0,
16
+ } as const
20
17
 
21
18
  function Chin({ title }: { title?: string }) {
22
- if (!title) return null;
19
+ if (!title) return null
23
20
 
24
21
  return (
25
22
  <div
@@ -31,367 +28,340 @@ function Chin({ title }: { title?: string }) {
31
28
  {title}
32
29
  </div>
33
30
  </div>
34
- );
31
+ )
35
32
  }
36
33
 
37
34
  export default function Preview() {
38
- // These are injected by the dev server
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__;
35
+ // These are injected by the dev server via source.define
36
+ const hasFeatured = typeof __HAS_FEATURED__ !== 'undefined' && __HAS_FEATURED__
37
+ const hasCarousel = typeof __HAS_FEATURED_CAROUSEL__ !== 'undefined' && __HAS_FEATURED_CAROUSEL__
45
38
 
46
39
  // Initialize state from localStorage
47
- const [selectedTab, setSelectedTab] = useState<
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
- });
64
- const [selectedTheme, setSelectedTheme] = useState<
65
- keyof typeof THEME_PRESETS
66
- >(() => {
67
- const saved = localStorage.getItem("linkapp-preview-theme");
68
- return (saved as keyof typeof THEME_PRESETS) || "default";
69
- });
40
+ const [selectedTab, setSelectedTab] = useState<'expanded' | 'featured' | 'carousel' | 'settings'>(() => {
41
+ const saved = localStorage.getItem('linkapp-preview-tab')
42
+ if (saved === 'featured' && !hasFeatured) return 'expanded'
43
+ if (saved === 'carousel' && !hasCarousel) return 'expanded'
44
+ return (saved as 'expanded' | 'featured' | 'carousel' | 'settings') || 'expanded'
45
+ })
46
+ const [selectedTheme, setSelectedTheme] = useState<keyof typeof THEME_PRESETS>(() => {
47
+ const saved = localStorage.getItem('linkapp-preview-theme')
48
+ return (saved as keyof typeof THEME_PRESETS) || 'default'
49
+ })
70
50
 
71
51
  // Chin configuration from build-time constants
72
- const chinPosition = __SETTINGS_CONFIG__?.featured_chin_position;
73
- const chinTitle = __PREVIEW_PROPS__?.linkTitle as string | undefined;
74
- const isOverlay =
75
- chinPosition === "overlayAbove" || chinPosition === "overlayBelow";
52
+ const chinPosition = __SETTINGS_CONFIG__?.featured_chin_position
53
+ const chinTitle = __PREVIEW_PROPS__?.linkTitle as string | undefined
76
54
 
77
- // Popup dialog state
78
- const [isPopupOpen, setIsPopupOpen] = useState(false);
55
+ // Click and sheet behavior from config
56
+ const clickBehavior = __SETTINGS_CONFIG__?.featured_head_click_behavior ?? 'default'
57
+ const sheetBehavior = __SETTINGS_CONFIG__?.sheet_behavior ?? 'default'
58
+ const canExpand = sheetBehavior !== 'none'
59
+ const isClickable = clickBehavior !== 'linkOff' && clickBehavior !== 'custom'
60
+
61
+ // Expanded overlay state (triggered by EXPAND_LINK_APP message)
62
+ const [isExpandedOpen, setIsExpandedOpen] = useState(false)
79
63
 
80
64
  // Generate unique IDs for iframes using timestamp
81
- const expandedIframeId = useMemo(
82
- () => `preview-iframe-expanded-${Date.now()}`,
83
- [],
84
- );
85
- const featuredIframeId = useMemo(
86
- () => `preview-iframe-featured-${Date.now()}`,
87
- [],
88
- );
89
- const carouselIframeId = useMemo(
90
- () => `preview-iframe-carousel-${Date.now()}`,
91
- [],
92
- );
93
- const popupIframeId = useMemo(() => `preview-iframe-popup-${Date.now()}`, []);
65
+ const expandedIframeId = useMemo(() => `preview-iframe-expanded-${Date.now()}`, [])
66
+ const featuredIframeId = useMemo(() => `preview-iframe-featured-${Date.now()}`, [])
67
+ const carouselIframeId = useMemo(() => `preview-iframe-carousel-${Date.now()}`, [])
68
+ const expandedOverlayIframeId = useMemo(() => `preview-iframe-expanded-overlay-${Date.now()}`, [])
94
69
 
95
70
  // Save selected tab to localStorage
96
71
  useEffect(() => {
97
- localStorage.setItem("linkapp-preview-tab", selectedTab);
98
- }, [selectedTab]);
72
+ localStorage.setItem('linkapp-preview-tab', selectedTab)
73
+ }, [selectedTab])
99
74
 
100
- // Save selected theme to localStorage
75
+ // Save selected theme to localStorage and broadcast to iframes
101
76
  useEffect(() => {
102
- localStorage.setItem("linkapp-preview-theme", selectedTheme);
103
- }, [selectedTheme]);
104
-
105
- // Handle iframe resize events
106
- const handleResized = useCallback(
107
- (ev: {
108
- iframe: IFrameComponent;
109
- height: number;
110
- width: number;
111
- type: string;
112
- }): void => {
113
- // IframeResizer automatically handles height adjustments
114
- // This callback is available for debugging if needed
115
- },
116
- [],
117
- );
77
+ localStorage.setItem('linkapp-preview-theme', selectedTheme)
78
+ // Broadcast theme update to iframes for CSS variable live updates
79
+ const theme = THEME_PRESETS[selectedTheme] || THEME_PRESETS.default
80
+ broadcastThemeUpdate(theme.variables, selectedTheme)
81
+ }, [selectedTheme])
118
82
 
119
83
  // Handle postMessage from featured iframe for EXPAND_LINK_APP
120
84
  const handleMessage = useCallback((event: MessageEvent) => {
121
85
  if (
122
86
  event.data &&
123
- typeof event.data === "object" &&
124
- event.data.source === "linkapp" &&
125
- event.data.type === "EXPAND_LINK_APP"
87
+ typeof event.data === 'object' &&
88
+ event.data.source === 'linkapp' &&
89
+ event.data.type === 'EXPAND_LINK_APP'
126
90
  ) {
127
- setIsPopupOpen(true);
91
+ setIsExpandedOpen(true)
128
92
  }
129
- }, []);
93
+ }, [])
94
+
95
+ // Handle click on featured preview based on config
96
+ const handleFeaturedClick = useCallback(() => {
97
+ if (!canExpand || !isClickable) return
98
+ setIsExpandedOpen(true)
99
+ }, [canExpand, isClickable])
130
100
 
131
101
  // Add message listener
132
102
  useEffect(() => {
133
- window.addEventListener("message", handleMessage);
134
- return () => window.removeEventListener("message", handleMessage);
135
- }, [handleMessage]);
103
+ window.addEventListener('message', handleMessage)
104
+ return () => window.removeEventListener('message', handleMessage)
105
+ }, [handleMessage])
106
+
107
+ // Initialize dev parent simulator for SDK hooks
108
+ useEffect(() => {
109
+ const simulator = createDevParentSimulator({
110
+ getCurrentTheme: () => THEME_PRESETS[selectedTheme] || THEME_PRESETS.default,
111
+ })
112
+ simulator.init()
113
+ return () => simulator.destroy()
114
+ }, [selectedTheme])
115
+
116
+ // Update favicon to use app icon
117
+ useEffect(() => {
118
+ let link = document.querySelector('link[rel="icon"][type="image/svg+xml"]') as HTMLLinkElement | null
119
+ if (!link) {
120
+ link = document.createElement('link')
121
+ link.rel = 'icon'
122
+ link.type = 'image/svg+xml'
123
+ document.head.appendChild(link)
124
+ }
125
+ link.href = '/app-icon.svg'
126
+ }, [])
136
127
 
137
128
  const themeVariables = useMemo(() => {
138
- const theme = THEME_PRESETS[selectedTheme] || THEME_PRESETS.default;
139
- return theme.variables;
140
- }, [selectedTheme]);
129
+ const theme = THEME_PRESETS[selectedTheme] || THEME_PRESETS.default
130
+ return theme.variables
131
+ }, [selectedTheme])
141
132
 
142
133
  return (
143
134
  <>
144
135
  <div
145
- className={cn("min-h-screen", {
146
- "bg-black/50": selectedTab === "expanded",
147
- "bg-linktree-frame":
148
- selectedTab === "featured" || selectedTab === "carousel",
136
+ className={cn('min-h-screen', {
137
+ 'bg-black/50': selectedTab === 'expanded',
138
+ 'bg-linktree-frame': selectedTab === 'featured' || selectedTab === 'carousel',
149
139
  })}
150
140
  id="preview"
151
141
  >
152
142
  <div style={themeVariables}>
153
143
  <Tabs
154
144
  value={selectedTab}
155
- onValueChange={(value) =>
156
- setSelectedTab(
157
- (value === "sheet" ? "expanded" : value) as
158
- | "expanded"
159
- | "featured"
160
- | "carousel"
161
- | "settings",
162
- )
163
- }
145
+ onValueChange={(value) => setSelectedTab(value as 'expanded' | 'featured' | 'carousel' | 'settings')}
164
146
  >
165
- <Portal>
166
- <div
167
- className="fixed top-0 left-0 right-0 p-4 flex justify-center gap-4 bg-background border-b"
168
- style={{ zIndex: 1000000 }}
169
- >
170
- <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>
180
-
181
- {/* Theme Switcher */}
182
- <div className="flex items-center gap-2">
183
- <label htmlFor="theme-select" className="text-sm font-medium">
184
- Theme:
185
- </label>
186
- <select
187
- id="theme-select"
188
- value={selectedTheme}
189
- onChange={(e) =>
190
- setSelectedTheme(
191
- e.target.value as keyof typeof THEME_PRESETS,
192
- )
193
- }
194
- 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"
195
- >
196
- {Object.entries(THEME_PRESETS).map(([key, theme]) => (
197
- <option key={key} value={key}>
198
- {theme.name}
199
- </option>
200
- ))}
201
- </select>
202
- </div>
203
- </div>
204
- </Portal>
205
-
206
- {/* Main Content with Padding for Fixed Tabs */}
207
- <div className="pt-20">
208
- <TabsContent value="expanded" className="m-0">
209
- {/* Expanded Modal - Always Open */}
210
- <Dialog open={true} modal={false}>
211
- <DialogContent
212
- className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
213
- showCloseButton={false}
214
- >
215
- <DialogHeader className="sticky top-0 bg-white px-4">
216
- <div className="grid h-16 grid-cols-[32px_auto_32px] items-center gap-4">
217
- <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
218
- <svg
219
- width="16"
220
- height="16"
221
- viewBox="0 0 16 16"
222
- fill="none"
223
- xmlns="http://www.w3.org/2000/svg"
224
- className=" "
225
- role="img"
226
- aria-hidden="true"
227
- >
228
- <path
229
- fill="currentColor"
230
- 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"
231
- ></path>
232
- </svg>
233
- </button>
234
-
235
- <DialogTitle className="self-center truncate py-3 text-center">
236
- Expanded
237
- </DialogTitle>
238
-
239
- <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
240
- <svg
241
- width="16"
242
- height="16"
243
- viewBox="0 0 16 16"
244
- fill="none"
245
- xmlns="http://www.w3.org/2000/svg"
246
- className=" "
247
- role="img"
248
- aria-hidden="true"
249
- >
250
- <path
251
- fill="currentColor"
252
- 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"
253
- ></path>
254
- </svg>
255
- </button>
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"
147
+ <Portal>
148
+ <div
149
+ className="fixed top-0 left-0 right-0 p-4 flex justify-center gap-4 bg-background border-b"
150
+ style={{ zIndex: 1000000 }}
285
151
  >
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>
152
+ <TabsList>
153
+ <TabsTrigger value="expanded">Expanded</TabsTrigger>
154
+ {hasFeatured && <TabsTrigger value="featured">Featured</TabsTrigger>}
155
+ {hasCarousel && <TabsTrigger value="carousel">Carousel</TabsTrigger>}
156
+ <TabsTrigger value="settings">Settings</TabsTrigger>
157
+ </TabsList>
158
+
159
+ {/* Theme Switcher */}
160
+ <div className="flex items-center gap-2">
161
+ <label htmlFor="theme-select" className="text-sm font-medium">
162
+ Theme:
163
+ </label>
164
+ <select
165
+ id="theme-select"
166
+ value={selectedTheme}
167
+ onChange={(e) => setSelectedTheme(e.target.value as keyof typeof THEME_PRESETS)}
168
+ 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"
169
+ >
170
+ {Object.entries(THEME_PRESETS).map(([key, theme]) => (
171
+ <option key={key} value={key}>
172
+ {theme.name}
173
+ </option>
174
+ ))}
175
+ </select>
326
176
  </div>
177
+ </div>
178
+ </Portal>
179
+
180
+ {/* Main Content with Padding for Fixed Tabs */}
181
+ <div className="pt-20">
182
+ <TabsContent value="expanded" className="m-0">
183
+ {/* Expanded Modal - Always Open */}
184
+ <Dialog open={true} modal={false}>
185
+ <DialogContent
186
+ className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
187
+ showCloseButton={false}
188
+ >
189
+ <DialogHeader className="sticky top-0 bg-white px-4">
190
+ <div className="grid h-16 grid-cols-[32px_auto_32px] items-center gap-4">
191
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
192
+ <svg
193
+ width="16"
194
+ height="16"
195
+ viewBox="0 0 16 16"
196
+ fill="none"
197
+ xmlns="http://www.w3.org/2000/svg"
198
+ role="img"
199
+ aria-hidden="true"
200
+ >
201
+ <path
202
+ fill="currentColor"
203
+ 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"
204
+ />
205
+ </svg>
206
+ </button>
207
+
208
+ <DialogTitle className="self-center truncate py-3 text-center">Expanded</DialogTitle>
209
+
210
+ <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
211
+ <svg
212
+ width="16"
213
+ height="16"
214
+ viewBox="0 0 16 16"
215
+ fill="none"
216
+ xmlns="http://www.w3.org/2000/svg"
217
+ role="img"
218
+ aria-hidden="true"
219
+ >
220
+ <path
221
+ fill="currentColor"
222
+ 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"
223
+ />
224
+ </svg>
225
+ </button>
226
+ </div>
227
+ </DialogHeader>
228
+
229
+ <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
230
+ <div className="h-full overflow-y-auto overflow-x-hidden">
231
+ <IframeResizer
232
+ key={`expanded-${selectedTheme}`}
233
+ id={expandedIframeId}
234
+ src={`/expanded?theme=${selectedTheme}`}
235
+ style={IFRAME_STYLE}
236
+ checkOrigin={false}
237
+ heightCalculationMethod="max"
238
+ />
239
+ </div>
240
+ </div>
241
+ </DialogContent>
242
+ </Dialog>
327
243
  </TabsContent>
328
- )}
329
244
 
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
- )}
245
+ {hasFeatured && (
246
+ <TabsContent value="featured" className="m-0 flex justify-center p-8">
247
+ <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-gray-100">
248
+ <div className="w-full max-w-[524px]">
249
+ {chinPosition === 'above' && <Chin title={chinTitle} />}
250
+
251
+ <div
252
+ className={cn(
253
+ 'relative 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',
254
+ canExpand && isClickable && 'cursor-pointer'
255
+ )}
256
+ style={
257
+ __SETTINGS_CONFIG__?.featured_head_allow_unlocked_aspect_ratio === false
258
+ ? { aspectRatio: '16 / 9' }
259
+ : undefined
260
+ }
261
+ onClick={handleFeaturedClick}
262
+ >
263
+ {chinPosition === 'overlayAbove' && (
264
+ <div className="absolute top-0 left-0 right-0 z-10">
265
+ <Chin title={chinTitle} />
266
+ </div>
267
+ )}
268
+
269
+ <IframeResizer
270
+ key={`featured-${selectedTheme}`}
271
+ id={featuredIframeId}
272
+ src={`/featured?theme=${selectedTheme}`}
273
+ style={IFRAME_STYLE}
274
+ checkOrigin={false}
275
+ heightCalculationMethod="max"
276
+ />
277
+
278
+ <button
279
+ type="button"
280
+ data-testid="NewLinkShareButton"
281
+ className="flex h-6 w-6 items-center justify-center rounded-xl text-[var(--button-style-text)] opacity-50 hover:bg-black/10 hover:opacity-100 absolute right-3 !top-auto bottom-[22px]"
282
+ >
283
+ <svg width="3" height="11" viewBox="0 0 3 11" fill="none" xmlns="http://www.w3.org/2000/svg">
284
+ <title>Share</title>
285
+ <path
286
+ className="fill-[currentColor]"
287
+ d="M1.33333 10.6667C0.966667 10.6667 0.652778 10.5361 0.391667 10.275C0.130556 10.0139 0 9.7 0 9.33333C0 8.96667 0.130556 8.65278 0.391667 8.39167C0.652778 8.13056 0.966667 8 1.33333 8C1.7 8 2.01389 8.13056 2.275 8.39167C2.53611 8.65278 2.66667 8.96667 2.66667 9.33333C2.66667 9.7 2.53611 10.0139 2.275 10.275C2.01389 10.5361 1.7 10.6667 1.33333 10.6667ZM1.33333 6.66667C0.966667 6.66667 0.652778 6.53611 0.391667 6.275C0.130556 6.01389 0 5.7 0 5.33333C0 4.96667 0.130556 4.65278 0.391667 4.39167C0.652778 4.13056 0.966667 4 1.33333 4C1.7 4 2.01389 4.13056 2.275 4.39167C2.53611 4.65278 2.66667 4.96667 2.66667 5.33333C2.66667 5.7 2.53611 6.01389 2.275 6.275C2.01389 6.53611 1.7 6.66667 1.33333 6.66667ZM1.33333 2.66667C0.966667 2.66667 0.652778 2.53611 0.391667 2.275C0.130556 2.01389 0 1.7 0 1.33333C0 0.966667 0.130556 0.652778 0.391667 0.391667C0.652778 0.130556 0.966667 0 1.33333 0C1.7 0 2.01389 0.130556 2.275 0.391667C2.53611 0.652778 2.66667 0.966667 2.66667 1.33333C2.66667 1.7 2.53611 2.01389 2.275 2.275C2.01389 2.53611 1.7 2.66667 1.33333 2.66667Z"
288
+ />
289
+ </svg>
290
+ </button>
291
+
292
+ {chinPosition === 'overlayBelow' && (
293
+ <div className="absolute bottom-0 left-0 right-0 z-10">
294
+ <Chin title={chinTitle} />
295
+ </div>
296
+ )}
297
+ </div>
298
+
299
+ {chinPosition === 'below' && <Chin title={chinTitle} />}
376
300
  </div>
377
-
378
- {chinPosition === "below" && <Chin title={chinTitle} />}
379
301
  </div>
380
- </div>
302
+ </TabsContent>
303
+ )}
304
+
305
+ {hasCarousel && (
306
+ <TabsContent value="carousel" className="m-0 flex justify-center p-8">
307
+ <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-gray-100">
308
+ <div className="w-full max-w-[220px]">
309
+ {chinPosition === 'above' && <Chin title={chinTitle} />}
310
+
311
+ <div
312
+ className="relative 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"
313
+ style={
314
+ __SETTINGS_CONFIG__?.featured_head_allow_unlocked_aspect_ratio === false
315
+ ? { aspectRatio: '11 / 8' }
316
+ : undefined
317
+ }
318
+ >
319
+ {chinPosition === 'overlayAbove' && (
320
+ <div className="absolute top-0 left-0 right-0 z-10">
321
+ <Chin title={chinTitle} />
322
+ </div>
323
+ )}
324
+
325
+ <IframeResizer
326
+ key={`carousel-${selectedTheme}`}
327
+ id={carouselIframeId}
328
+ src={`/featured-carousel?theme=${selectedTheme}`}
329
+ style={IFRAME_STYLE}
330
+ checkOrigin={false}
331
+ heightCalculationMethod="max"
332
+ />
333
+
334
+ {chinPosition === 'overlayBelow' && (
335
+ <div className="absolute bottom-0 left-0 right-0 z-10">
336
+ <Chin title={chinTitle} />
337
+ </div>
338
+ )}
339
+ </div>
340
+
341
+ {chinPosition === 'below' && <Chin title={chinTitle} />}
342
+ </div>
343
+ </div>
344
+ </TabsContent>
345
+ )}
346
+
347
+ <TabsContent value="settings" className="m-0">
348
+ <SettingsPreview
349
+ settings={__SETTINGS_CONFIG__}
350
+ manifest={__MANIFEST_CONFIG__}
351
+ previewProps={__PREVIEW_PROPS__ as Record<string, unknown> | undefined}
352
+ />
381
353
  </TabsContent>
382
- )}
383
-
384
- <TabsContent value="settings" className="m-0">
385
- <SettingsPreview settings={__SETTINGS_CONFIG__} />
386
- </TabsContent>
387
- </div>
388
- </Tabs>
354
+ </div>
355
+ </Tabs>
389
356
  </div>
390
357
 
391
- {/* Popup Dialog for EXPAND_LINK_APP message */}
392
- <Dialog open={isPopupOpen} onOpenChange={setIsPopupOpen}>
358
+ {/* Expanded Overlay Dialog for EXPAND_LINK_APP message */}
359
+ <Dialog open={isExpandedOpen} onOpenChange={setIsExpandedOpen}>
393
360
  <DialogContent
394
- className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
361
+ className={cn(
362
+ 'h-[calc(100dvh-2rem)] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto',
363
+ sheetBehavior === 'expandVideo' ? 'max-w-[800px]' : 'max-w-[608px]'
364
+ )}
395
365
  showCloseButton={false}
396
366
  >
397
367
  <DialogHeader className="sticky top-0 bg-white px-4">
@@ -403,20 +373,17 @@ export default function Preview() {
403
373
  viewBox="0 0 16 16"
404
374
  fill="none"
405
375
  xmlns="http://www.w3.org/2000/svg"
406
- className=" "
407
376
  role="img"
408
377
  aria-hidden="true"
409
378
  >
410
379
  <path
411
380
  fill="currentColor"
412
381
  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>
382
+ />
414
383
  </svg>
415
384
  </button>
416
385
 
417
- <DialogTitle className="self-center truncate py-3 text-center">
418
- Expanded
419
- </DialogTitle>
386
+ <DialogTitle className="self-center truncate py-3 text-center">Expanded</DialogTitle>
420
387
 
421
388
  <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
422
389
  <svg
@@ -425,14 +392,13 @@ export default function Preview() {
425
392
  viewBox="0 0 16 16"
426
393
  fill="none"
427
394
  xmlns="http://www.w3.org/2000/svg"
428
- className=" "
429
395
  role="img"
430
396
  aria-hidden="true"
431
397
  >
432
398
  <path
433
399
  fill="currentColor"
434
400
  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>
401
+ />
436
402
  </svg>
437
403
  </button>
438
404
  </div>
@@ -441,17 +407,11 @@ export default function Preview() {
441
407
  <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
442
408
  <div className="h-full overflow-y-auto overflow-x-hidden">
443
409
  <IframeResizer
444
- key={`popup-${selectedTheme}`}
445
- id={popupIframeId}
410
+ key={`expanded-overlay-${selectedTheme}`}
411
+ id={expandedOverlayIframeId}
446
412
  src={`/expanded?theme=${selectedTheme}`}
447
- style={{
448
- height: "0px",
449
- width: "1px",
450
- minWidth: "100%",
451
- border: 0,
452
- }}
413
+ style={IFRAME_STYLE}
453
414
  checkOrigin={false}
454
- onResized={handleResized}
455
415
  heightCalculationMethod="max"
456
416
  />
457
417
  </div>
@@ -460,5 +420,5 @@ export default function Preview() {
460
420
  </Dialog>
461
421
  </div>
462
422
  </>
463
- );
423
+ )
464
424
  }