@linktr.ee/linkapp 0.0.1 → 0.0.3

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,82 @@
1
+ # Development Server Setup
2
+
3
+ This directory contains a custom Vite-based development server for previewing LinkApp components with multiple routes and iframe embeds.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ dev-server/
9
+ ├── index.html # Root preview page entry
10
+ ├── vite.config.ts # Vite configuration (MPA setup)
11
+ ├── server.ts # TypeScript server launcher
12
+ ├── preview/
13
+ │ ├── main.tsx # Preview page React entry
14
+ │ └── Preview.tsx # Preview UI component
15
+ ├── classic/
16
+ │ ├── index.html # Classic layout entry
17
+ │ └── main.tsx # Classic React entry
18
+ └── featured/
19
+ ├── index.html # Featured layout entry
20
+ └── main.tsx # Featured React entry
21
+ ```
22
+
23
+ ## Available Routes
24
+
25
+ - **`http://localhost:3000/`** - Preview page with drawer/modal and iframes
26
+ - **`http://localhost:3000/classic/`** - Classic layout (renders `app/classic.tsx`)
27
+ - **`http://localhost:3000/featured/`** - Featured layout (renders `app/featured.tsx`)
28
+
29
+ ## Features
30
+
31
+ ### Preview Page (`/`)
32
+ - Interactive drawer/modal containing an iframe to `/classic`
33
+ - Inline iframe displaying `/featured`
34
+ - Clean UI with route information and controls
35
+
36
+ ### Classic & Featured Routes
37
+ - Both routes render their respective components from `../app/`
38
+ - Wrapped in `layout.tsx` with `globals.css` imported
39
+ - Original `1-demo` files remain unchanged
40
+
41
+ ## Running the Server
42
+
43
+ ```bash
44
+ npm run dev:preview
45
+ ```
46
+
47
+ This command uses `tsx` to run the TypeScript server script, which programmatically starts Vite with the custom configuration.
48
+
49
+ ## Technical Details
50
+
51
+ ### Multi-Page Application (MPA)
52
+ The setup uses Vite's MPA capability with multiple HTML entry points:
53
+ - Root: `index.html` → Preview page
54
+ - `/classic/`: `classic/index.html` → Classic layout
55
+ - `/featured/`: `featured/index.html` → Featured layout
56
+
57
+ ### Imports
58
+ All entry points import from the original `app/` directory without modifying any existing files:
59
+ - `@/app/layout` - Root layout component
60
+ - `@/app/classic` - Classic component
61
+ - `@/app/featured` - Featured component
62
+ - `@/app/globals.css` - Global styles
63
+
64
+ ### Port Configuration
65
+ Server runs on port 3000 with `strictPort: true` to match requirements.
66
+
67
+ ## Development Workflow
68
+
69
+ 1. Run `npm run dev:preview`
70
+ 2. Open `http://localhost:3000/` in your browser
71
+ 3. Use the "Open Classic Preview" button to see the drawer/modal
72
+ 4. The featured layout is always visible in the inline iframe
73
+ 5. Navigate directly to `/classic/` or `/featured/` to view layouts standalone
74
+
75
+ ## First Principles Design
76
+
77
+ This implementation follows a clean separation of concerns:
78
+ - Original app files (`1-demo/app/*`) remain untouched
79
+ - New dev-server directory contains all preview infrastructure
80
+ - Uses Vite's native MPA support for clean routing (no client-side router)
81
+ - TypeScript throughout for type safety
82
+ - Programmatic server creation for flexibility
@@ -0,0 +1,24 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import Layout from "@/app/layout";
4
+ import Classic from "@/app/classic";
5
+ import "@/app/globals.css";
6
+
7
+ // Mock Linktree context for development
8
+ const mockContext = {
9
+ linkUrl: "https://example.com/demo",
10
+ showTitle: true,
11
+ };
12
+
13
+ const rootElement = document.getElementById("root");
14
+ if (!rootElement) {
15
+ throw new Error("Root element not found");
16
+ }
17
+
18
+ createRoot(rootElement).render(
19
+ <StrictMode>
20
+ <Layout>
21
+ <Classic {...mockContext} />
22
+ </Layout>
23
+ </StrictMode>,
24
+ );
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Classic - LinkApp</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/classic/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,142 @@
1
+ "use client"
2
+
3
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
4
+ import { XIcon } from "lucide-react"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ function Dialog({
9
+ ...props
10
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
11
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
12
+ }
13
+
14
+ function DialogTrigger({
15
+ ...props
16
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
17
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
18
+ }
19
+
20
+ function DialogPortal({
21
+ ...props
22
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
23
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
24
+ }
25
+
26
+ function DialogClose({
27
+ ...props
28
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
29
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
30
+ }
31
+
32
+ function DialogOverlay({
33
+ className,
34
+ ...props
35
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
36
+ return (
37
+ <DialogPrimitive.Overlay
38
+ data-slot="dialog-overlay"
39
+ className={cn(
40
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ )
46
+ }
47
+
48
+ function DialogContent({
49
+ className,
50
+ children,
51
+ showCloseButton = true,
52
+ ...props
53
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
54
+ showCloseButton?: boolean
55
+ }) {
56
+ return (
57
+ <DialogPortal data-slot="dialog-portal">
58
+ <DialogOverlay />
59
+ <DialogPrimitive.Content
60
+ data-slot="dialog-content"
61
+ className={cn(
62
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
63
+ className
64
+ )}
65
+ {...props}
66
+ >
67
+ {children}
68
+ {showCloseButton && (
69
+ <DialogPrimitive.Close
70
+ data-slot="dialog-close"
71
+ className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
72
+ >
73
+ <XIcon />
74
+ <span className="sr-only">Close</span>
75
+ </DialogPrimitive.Close>
76
+ )}
77
+ </DialogPrimitive.Content>
78
+ </DialogPortal>
79
+ )
80
+ }
81
+
82
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
83
+ return (
84
+ <div
85
+ data-slot="dialog-header"
86
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
87
+ {...props}
88
+ />
89
+ )
90
+ }
91
+
92
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
93
+ return (
94
+ <div
95
+ data-slot="dialog-footer"
96
+ className={cn(
97
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
98
+ className
99
+ )}
100
+ {...props}
101
+ />
102
+ )
103
+ }
104
+
105
+ function DialogTitle({
106
+ className,
107
+ ...props
108
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
109
+ return (
110
+ <DialogPrimitive.Title
111
+ data-slot="dialog-title"
112
+ className={cn("text-lg leading-none font-semibold", className)}
113
+ {...props}
114
+ />
115
+ )
116
+ }
117
+
118
+ function DialogDescription({
119
+ className,
120
+ ...props
121
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
122
+ return (
123
+ <DialogPrimitive.Description
124
+ data-slot="dialog-description"
125
+ className={cn("text-muted-foreground text-sm", className)}
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ export {
132
+ Dialog,
133
+ DialogClose,
134
+ DialogContent,
135
+ DialogDescription,
136
+ DialogFooter,
137
+ DialogHeader,
138
+ DialogOverlay,
139
+ DialogPortal,
140
+ DialogTitle,
141
+ DialogTrigger,
142
+ }
@@ -0,0 +1,52 @@
1
+ import * as React from "react"
2
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Tabs = TabsPrimitive.Root
6
+
7
+ const TabsList = React.forwardRef<
8
+ React.ElementRef<typeof TabsPrimitive.List>,
9
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
10
+ >(({ className, ...props }, ref) => (
11
+ <TabsPrimitive.List
12
+ ref={ref}
13
+ className={cn(
14
+ "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ ))
20
+ TabsList.displayName = TabsPrimitive.List.displayName
21
+
22
+ const TabsTrigger = React.forwardRef<
23
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
24
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
25
+ >(({ className, ...props }, ref) => (
26
+ <TabsPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ ))
35
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
36
+
37
+ const TabsContent = React.forwardRef<
38
+ React.ElementRef<typeof TabsPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
40
+ >(({ className, ...props }, ref) => (
41
+ <TabsPrimitive.Content
42
+ ref={ref}
43
+ className={cn(
44
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ ))
50
+ TabsContent.displayName = TabsPrimitive.Content.displayName
51
+
52
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "preview/preview.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "registries": {}
21
+ }
@@ -0,0 +1,24 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import Layout from "@/app/layout";
4
+ import Featured from "@/app/featured";
5
+ import "@/app/globals.css";
6
+
7
+ // Mock Linktree context for development
8
+ const mockContext = {
9
+ linkUrl: "https://example.com/demo",
10
+ showTitle: true,
11
+ };
12
+
13
+ const rootElement = document.getElementById("root");
14
+ if (!rootElement) {
15
+ throw new Error("Root element not found");
16
+ }
17
+
18
+ createRoot(rootElement).render(
19
+ <StrictMode>
20
+ <Layout>
21
+ <Featured {...mockContext} />
22
+ </Layout>
23
+ </StrictMode>,
24
+ );
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Featured - LinkApp</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/featured/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Preview - LinkApp</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/preview/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }