@linktr.ee/linkapp 0.0.1 → 0.0.2
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.
- package/dev-server/README.md +82 -0
- package/dev-server/classic/main.tsx +24 -0
- package/dev-server/classic.html +12 -0
- package/dev-server/components/ui/dialog.tsx +142 -0
- package/dev-server/components/ui/tabs.tsx +52 -0
- package/dev-server/components.json +21 -0
- package/dev-server/featured/main.tsx +24 -0
- package/dev-server/featured.html +12 -0
- package/dev-server/index.html +12 -0
- package/dev-server/lib/utils.ts +6 -0
- package/dev-server/package-lock.json +3070 -0
- package/dev-server/package.json +29 -0
- package/dev-server/preview/Preview.tsx +62 -0
- package/dev-server/preview/main.tsx +15 -0
- package/dev-server/preview/preview.css +122 -0
- package/dev-server/vite.config.ts +29 -0
- package/package.json +2 -1
|
@@ -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>
|