@linktr.ee/linkapp 0.0.47 → 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 (204) 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 +37 -0
  11. package/dev-server/components/ui/input.tsx +24 -0
  12. package/dev-server/components/ui/label.tsx +21 -0
  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 +22 -0
  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 +316 -359
  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 +36 -73
  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 +8 -9
  204. package/runtime/index.html +28 -0
@@ -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, renderCssVariables } 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,370 +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])
136
106
 
137
- const renderedCssVariables = useMemo(() => {
138
- const themeVariables =
139
- THEME_PRESETS[selectedTheme] || THEME_PRESETS.default;
140
- return renderCssVariables(themeVariables.variables);
141
- }, [selectedTheme]);
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
+ }, [])
127
+
128
+ const themeVariables = useMemo(() => {
129
+ const theme = THEME_PRESETS[selectedTheme] || THEME_PRESETS.default
130
+ return theme.variables
131
+ }, [selectedTheme])
142
132
 
143
133
  return (
144
134
  <>
145
- <style>{`:root {
146
- ${renderedCssVariables}
147
- }`}</style>
148
-
149
135
  <div
150
- className={cn("min-h-screen", {
151
- "bg-black/50": selectedTab === "expanded",
152
- "bg-linktree-frame":
153
- selectedTab === "featured" || selectedTab === "carousel",
136
+ className={cn('min-h-screen', {
137
+ 'bg-black/50': selectedTab === 'expanded',
138
+ 'bg-linktree-frame': selectedTab === 'featured' || selectedTab === 'carousel',
154
139
  })}
155
140
  id="preview"
156
141
  >
157
- <Tabs
158
- value={selectedTab}
159
- onValueChange={(value) =>
160
- setSelectedTab(
161
- (value === "sheet" ? "expanded" : value) as
162
- | "expanded"
163
- | "featured"
164
- | "carousel"
165
- | "settings",
166
- )
167
- }
168
- >
169
- <Portal>
170
- <div
171
- className="fixed top-0 left-0 right-0 p-4 flex justify-center gap-4 bg-background border-b"
172
- style={{ zIndex: 1000000 }}
173
- >
174
- <TabsList>
175
- <TabsTrigger value="expanded">Expanded</TabsTrigger>
176
- {hasFeatured && (
177
- <TabsTrigger value="featured">Featured</TabsTrigger>
178
- )}
179
- {hasCarousel && (
180
- <TabsTrigger value="carousel">Carousel</TabsTrigger>
181
- )}
182
- <TabsTrigger value="settings">Settings</TabsTrigger>
183
- </TabsList>
184
-
185
- {/* Theme Switcher */}
186
- <div className="flex items-center gap-2">
187
- <label htmlFor="theme-select" className="text-sm font-medium">
188
- Theme:
189
- </label>
190
- <select
191
- id="theme-select"
192
- value={selectedTheme}
193
- onChange={(e) =>
194
- setSelectedTheme(
195
- e.target.value as keyof typeof THEME_PRESETS,
196
- )
197
- }
198
- 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"
199
- >
200
- {Object.entries(THEME_PRESETS).map(([key, theme]) => (
201
- <option key={key} value={key}>
202
- {theme.name}
203
- </option>
204
- ))}
205
- </select>
206
- </div>
207
- </div>
208
- </Portal>
209
-
210
- {/* Main Content with Padding for Fixed Tabs */}
211
- <div className="pt-20">
212
- <TabsContent value="expanded" className="m-0">
213
- {/* Expanded Modal - Always Open */}
214
- <Dialog open={true} modal={false}>
215
- <DialogContent
216
- className="h-[calc(100dvh-2rem)] max-w-[608px] md:min-h-[25vh] md:h-[80%] md:max-h-[900px] overflow-auto"
217
- showCloseButton={false}
218
- >
219
- <DialogHeader className="sticky top-0 bg-white px-4">
220
- <div className="grid h-16 grid-cols-[32px_auto_32px] items-center gap-4">
221
- <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
222
- <svg
223
- width="16"
224
- height="16"
225
- viewBox="0 0 16 16"
226
- fill="none"
227
- xmlns="http://www.w3.org/2000/svg"
228
- className=" "
229
- role="img"
230
- aria-hidden="true"
231
- >
232
- <path
233
- fill="currentColor"
234
- 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"
235
- ></path>
236
- </svg>
237
- </button>
238
-
239
- <DialogTitle className="self-center truncate py-3 text-center">
240
- Expanded
241
- </DialogTitle>
242
-
243
- <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
244
- <svg
245
- width="16"
246
- height="16"
247
- viewBox="0 0 16 16"
248
- fill="none"
249
- xmlns="http://www.w3.org/2000/svg"
250
- className=" "
251
- role="img"
252
- aria-hidden="true"
253
- >
254
- <path
255
- fill="currentColor"
256
- 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"
257
- ></path>
258
- </svg>
259
- </button>
260
- </div>
261
- </DialogHeader>
262
-
263
- <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
264
- <div className="h-full overflow-y-auto overflow-x-hidden">
265
- <IframeResizer
266
- key={`expanded-${selectedTheme}`}
267
- id={expandedIframeId}
268
- src={`/expanded?theme=${selectedTheme}`}
269
- style={{
270
- height: "0px",
271
- width: "1px",
272
- minWidth: "100%",
273
- border: 0,
274
- }}
275
- checkOrigin={false}
276
- onResized={handleResized}
277
- heightCalculationMethod="max"
278
- />
279
- </div>
280
- </div>
281
- </DialogContent>
282
- </Dialog>
283
- </TabsContent>
284
-
285
- {hasFeatured && (
286
- <TabsContent
287
- value="featured"
288
- className="m-0 flex justify-center p-8"
142
+ <div style={themeVariables}>
143
+ <Tabs
144
+ value={selectedTab}
145
+ onValueChange={(value) => setSelectedTab(value as 'expanded' | 'featured' | 'carousel' | 'settings')}
146
+ >
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 }}
289
151
  >
290
- <div className="w-full max-w-[580px] flex flex-col px-[28px] py-7 items-center bg-gray-100">
291
- <div className="w-full max-w-[524px]">
292
- {chinPosition === "above" && <Chin title={chinTitle} />}
293
-
294
- <div
295
- className={cn(
296
- "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",
297
- isOverlay && "relative",
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>
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>
330
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>
331
243
  </TabsContent>
332
- )}
333
244
 
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
- )}
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} />}
380
300
  </div>
381
-
382
- {chinPosition === "below" && <Chin title={chinTitle} />}
383
301
  </div>
384
- </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
+ />
385
353
  </TabsContent>
386
- )}
387
-
388
- <TabsContent value="settings" className="m-0">
389
- <SettingsPreview settings={__SETTINGS_CONFIG__} />
390
- </TabsContent>
391
- </div>
392
- </Tabs>
354
+ </div>
355
+ </Tabs>
356
+ </div>
393
357
 
394
- {/* Popup Dialog for EXPAND_LINK_APP message */}
395
- <Dialog open={isPopupOpen} onOpenChange={setIsPopupOpen}>
358
+ {/* Expanded Overlay Dialog for EXPAND_LINK_APP message */}
359
+ <Dialog open={isExpandedOpen} onOpenChange={setIsExpandedOpen}>
396
360
  <DialogContent
397
- 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
+ )}
398
365
  showCloseButton={false}
399
366
  >
400
367
  <DialogHeader className="sticky top-0 bg-white px-4">
@@ -406,20 +373,17 @@ export default function Preview() {
406
373
  viewBox="0 0 16 16"
407
374
  fill="none"
408
375
  xmlns="http://www.w3.org/2000/svg"
409
- className=" "
410
376
  role="img"
411
377
  aria-hidden="true"
412
378
  >
413
379
  <path
414
380
  fill="currentColor"
415
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"
416
- ></path>
382
+ />
417
383
  </svg>
418
384
  </button>
419
385
 
420
- <DialogTitle className="self-center truncate py-3 text-center">
421
- Expanded
422
- </DialogTitle>
386
+ <DialogTitle className="self-center truncate py-3 text-center">Expanded</DialogTitle>
423
387
 
424
388
  <button className="flex size-8 items-center justify-center rounded-sm focus-visible:outline-none">
425
389
  <svg
@@ -428,14 +392,13 @@ export default function Preview() {
428
392
  viewBox="0 0 16 16"
429
393
  fill="none"
430
394
  xmlns="http://www.w3.org/2000/svg"
431
- className=" "
432
395
  role="img"
433
396
  aria-hidden="true"
434
397
  >
435
398
  <path
436
399
  fill="currentColor"
437
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"
438
- ></path>
401
+ />
439
402
  </svg>
440
403
  </button>
441
404
  </div>
@@ -444,17 +407,11 @@ export default function Preview() {
444
407
  <div className="flex h-[calc(100%-64px)] flex-1 flex-col justify-between overflow-hidden">
445
408
  <div className="h-full overflow-y-auto overflow-x-hidden">
446
409
  <IframeResizer
447
- key={`popup-${selectedTheme}`}
448
- id={popupIframeId}
410
+ key={`expanded-overlay-${selectedTheme}`}
411
+ id={expandedOverlayIframeId}
449
412
  src={`/expanded?theme=${selectedTheme}`}
450
- style={{
451
- height: "0px",
452
- width: "1px",
453
- minWidth: "100%",
454
- border: 0,
455
- }}
413
+ style={IFRAME_STYLE}
456
414
  checkOrigin={false}
457
- onResized={handleResized}
458
415
  heightCalculationMethod="max"
459
416
  />
460
417
  </div>
@@ -463,5 +420,5 @@ export default function Preview() {
463
420
  </Dialog>
464
421
  </div>
465
422
  </>
466
- );
423
+ )
467
424
  }