nextjs-link-preview 1.0.6 → 2.0.0
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/README.md +195 -187
- package/bin/cli.mjs +86 -0
- package/dist/index.d.ts +15 -15
- package/dist/index.esm.js +31 -16
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +31 -16
- package/dist/index.js.map +1 -1
- package/dist/types/nextjs/components/LinkPreview.d.ts +15 -15
- package/dist/types/nextjs/components/LinkPreview.d.ts.map +1 -1
- package/package.json +73 -72
- package/src/nextjs/components/LinkPreview.tsx +194 -0
package/README.md
CHANGED
|
@@ -1,187 +1,195 @@
|
|
|
1
|
-
# Next.js Link Preview
|
|
2
|
-
|
|
3
|
-
A simple, lightweight Next.js component for displaying beautiful link preview cards.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<LinkPreview
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1
|
+
# Next.js Link Preview
|
|
2
|
+
|
|
3
|
+
A simple, lightweight Next.js component for displaying beautiful link preview cards.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Option 1: Install as npm package
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install nextjs-link-preview
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { FaGithub } from "react-icons/fa";
|
|
15
|
+
import { LinkPreview } from "nextjs-link-preview";
|
|
16
|
+
|
|
17
|
+
<LinkPreview
|
|
18
|
+
url="https://github.com/vercel/next.js"
|
|
19
|
+
title="Next.js"
|
|
20
|
+
description="The React Framework for the Web"
|
|
21
|
+
preset={FaGithub}
|
|
22
|
+
/>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Option 2: Copy to your project
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx nextjs-link-preview init
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This copies the component source into `src/components/LinkPreview.tsx`. You own the code and can customize it however you want.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Custom path
|
|
35
|
+
npx nextjs-link-preview init --path src/ui/LinkPreview.tsx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- Pure presentational component - no data fetching
|
|
41
|
+
- Preset icon support via react-icons
|
|
42
|
+
- Custom icon support (react-icons, lucide, heroicons, any React element)
|
|
43
|
+
- Customizable text colors
|
|
44
|
+
- Three size variants (small, medium, large)
|
|
45
|
+
- Two layouts (vertical, horizontal)
|
|
46
|
+
- TypeScript support
|
|
47
|
+
- Fully customizable styling
|
|
48
|
+
- CLI copy-to-source support
|
|
49
|
+
- Peer deps only: React, Next.js, react-icons
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Basic Usage
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { LinkPreview } from "nextjs-link-preview";
|
|
57
|
+
|
|
58
|
+
export default function Page() {
|
|
59
|
+
return (
|
|
60
|
+
<LinkPreview
|
|
61
|
+
url="https://example.com"
|
|
62
|
+
title="Example Site"
|
|
63
|
+
description="This is an example website"
|
|
64
|
+
image="https://example.com/preview.png"
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### With Preset Icons
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { FaGithub, FaNpm } from "react-icons/fa";
|
|
74
|
+
|
|
75
|
+
// GitHub
|
|
76
|
+
<LinkPreview
|
|
77
|
+
url="https://github.com/user/repo"
|
|
78
|
+
title="My Repository"
|
|
79
|
+
description="A cool open source project"
|
|
80
|
+
preset={FaGithub}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
// npm
|
|
84
|
+
<LinkPreview
|
|
85
|
+
url="https://npmjs.com/package/my-package"
|
|
86
|
+
title="my-package"
|
|
87
|
+
description="An awesome npm package"
|
|
88
|
+
preset={FaNpm}
|
|
89
|
+
/>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With Icons
|
|
93
|
+
|
|
94
|
+
Pass any React element as an icon. Works with react-icons, lucide, heroicons, or plain SVGs:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { FaGithub } from "react-icons/fa";
|
|
98
|
+
|
|
99
|
+
<LinkPreview
|
|
100
|
+
url="https://github.com/user/repo"
|
|
101
|
+
title="My Repository"
|
|
102
|
+
description="A cool open source project"
|
|
103
|
+
icon={<FaGithub size={48} />}
|
|
104
|
+
/>;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Priority order: `icon` > `image` > `preset`.
|
|
108
|
+
|
|
109
|
+
### Custom Text Colors
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<LinkPreview
|
|
113
|
+
url="https://example.com"
|
|
114
|
+
title="Styled Preview"
|
|
115
|
+
description="With custom colors"
|
|
116
|
+
image="https://example.com/preview.png"
|
|
117
|
+
titleColor="#1a1a2e"
|
|
118
|
+
descriptionColor="#999"
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Size Variants
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<LinkPreview url="..." title="..." image="..." size="small" />
|
|
126
|
+
<LinkPreview url="..." title="..." image="..." size="medium" /> {/* default */}
|
|
127
|
+
<LinkPreview url="..." title="..." image="..." size="large" />
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Layouts
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
{/* Vertical (default) - image on top */}
|
|
134
|
+
<LinkPreview url="..." title="..." image="..." layout="vertical" />
|
|
135
|
+
|
|
136
|
+
{/* Horizontal - image on left */}
|
|
137
|
+
<LinkPreview url="..." title="..." image="..." layout="horizontal" />
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Custom Styling
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<LinkPreview
|
|
144
|
+
url="https://example.com"
|
|
145
|
+
title="Example"
|
|
146
|
+
image="..."
|
|
147
|
+
width="400px"
|
|
148
|
+
className="my-custom-class"
|
|
149
|
+
/>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Props
|
|
153
|
+
|
|
154
|
+
| Prop | Type | Default | Description |
|
|
155
|
+
| ------------------ | ------------------------------------ | ------------ | ------------------------------------------------------ |
|
|
156
|
+
| `url` | `string` | **required** | Link destination |
|
|
157
|
+
| `title` | `string` | **required** | Preview card title |
|
|
158
|
+
| `description` | `string` | `undefined` | Preview card description |
|
|
159
|
+
| `image` | `string` | `undefined` | Custom image URL |
|
|
160
|
+
| `preset` | `IconType` | `undefined` | Preset icon component (from react-icons) |
|
|
161
|
+
| `icon` | `React.ReactNode` | `undefined` | Custom icon element (takes priority over image/preset) |
|
|
162
|
+
| `size` | `"small"` \| `"medium"` \| `"large"` | `"medium"` | Preview card size |
|
|
163
|
+
| `layout` | `"vertical"` \| `"horizontal"` | `"vertical"` | Image position |
|
|
164
|
+
| `width` | `string` \| `number` | `"100%"` | Card width |
|
|
165
|
+
| `height` | `string` \| `number` | `"auto"` | Card height |
|
|
166
|
+
| `className` | `string` | `""` | Additional CSS classes |
|
|
167
|
+
| `titleColor` | `string` | `undefined` | Custom title text color |
|
|
168
|
+
| `descriptionColor` | `string` | `"#666"` | Custom description text color |
|
|
169
|
+
|
|
170
|
+
## Preset Icons
|
|
171
|
+
|
|
172
|
+
Pass any react-icons component as the `preset` prop. This gives you access to the full react-icons catalog.
|
|
173
|
+
|
|
174
|
+
## Testing
|
|
175
|
+
|
|
176
|
+
To test the component locally:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm test
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
This will:
|
|
183
|
+
|
|
184
|
+
1. Build the component
|
|
185
|
+
2. Start the demo at http://localhost:3000
|
|
186
|
+
3. Open your browser to see the interactive demo
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT
|
|
191
|
+
|
|
192
|
+
## Links
|
|
193
|
+
|
|
194
|
+
- [GitHub](https://github.com/sethcarney/nextjs-link-preview)
|
|
195
|
+
- [npm](https://www.npmjs.com/package/nextjs-link-preview)
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
4
|
+
import { resolve, dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const command = args[0];
|
|
12
|
+
|
|
13
|
+
if (!command || command === "--help" || command === "-h") {
|
|
14
|
+
console.log(`
|
|
15
|
+
nextjs-link-preview CLI
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
npx nextjs-link-preview init [--path <destination>]
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
init Copy the LinkPreview component into your project
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--path Destination path (default: src/components/LinkPreview.tsx)
|
|
25
|
+
--help Show this help message
|
|
26
|
+
`);
|
|
27
|
+
process.exit(command === "--help" || command === "-h" ? 0 : 1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (command !== "init") {
|
|
31
|
+
console.error(`Unknown command: ${command}`);
|
|
32
|
+
console.error('Run "npx nextjs-link-preview --help" for usage info.');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse --path flag
|
|
37
|
+
let destPath = "src/components/LinkPreview.tsx";
|
|
38
|
+
const pathIndex = args.indexOf("--path");
|
|
39
|
+
if (pathIndex !== -1 && args[pathIndex + 1]) {
|
|
40
|
+
destPath = args[pathIndex + 1];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const sourcePath = join(__dirname, "..", "src", "nextjs", "components", "LinkPreview.tsx");
|
|
44
|
+
const targetPath = resolve(process.cwd(), destPath);
|
|
45
|
+
const targetDir = dirname(targetPath);
|
|
46
|
+
|
|
47
|
+
if (!existsSync(sourcePath)) {
|
|
48
|
+
console.error("Error: Could not find LinkPreview.tsx source file.");
|
|
49
|
+
console.error("This may be a packaging issue. Please report it at:");
|
|
50
|
+
console.error("https://github.com/sethcarney/nextjs-link-preview/issues");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (existsSync(targetPath)) {
|
|
55
|
+
console.log(`File already exists at: ${destPath}`);
|
|
56
|
+
console.log("Overwriting with the latest version...\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!existsSync(targetDir)) {
|
|
60
|
+
mkdirSync(targetDir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const source = readFileSync(sourcePath, "utf-8");
|
|
65
|
+
writeFileSync(targetPath, source, "utf-8");
|
|
66
|
+
|
|
67
|
+
const importPath = destPath.replace(/\.tsx$/, "").replace(/\\/g, "/");
|
|
68
|
+
console.log(`
|
|
69
|
+
LinkPreview component copied to: ${destPath}
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
import { LinkPreview } from './${importPath}';
|
|
73
|
+
|
|
74
|
+
<LinkPreview
|
|
75
|
+
url="https://github.com/user/repo"
|
|
76
|
+
title="My Repo"
|
|
77
|
+
description="A cool repository"
|
|
78
|
+
preset="github"
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
The component is now yours to customize!
|
|
82
|
+
`);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error("Error copying component:", err.message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { IconType } from 'react-icons';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Simple Link Preview Component
|
|
5
6
|
*
|
|
6
7
|
* Usage with custom image:
|
|
7
|
-
* <LinkPreview
|
|
8
|
-
* url="https://example.com"
|
|
9
|
-
* title="Example"
|
|
10
|
-
* description="Example description"
|
|
11
|
-
* image="https://example.com/image.png"
|
|
12
|
-
* />
|
|
8
|
+
* <LinkPreview url="..." title="..." image="https://example.com/image.png" />
|
|
13
9
|
*
|
|
14
|
-
* Usage with preset:
|
|
15
|
-
* <LinkPreview
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
10
|
+
* Usage with preset icon (react-icons component):
|
|
11
|
+
* <LinkPreview url="..." title="..." preset={FaGithub} />
|
|
12
|
+
*
|
|
13
|
+
* Usage with icon (any React element, e.g. react-icons):
|
|
14
|
+
* <LinkPreview url="..." title="..." icon={<FaGithub size={48} />} />
|
|
15
|
+
*
|
|
16
|
+
* Usage with custom text colors:
|
|
17
|
+
* <LinkPreview url="..." title="..." titleColor="#333" descriptionColor="#999" />
|
|
21
18
|
*/
|
|
22
19
|
|
|
23
20
|
type LinkPreviewSize = "small" | "medium" | "large";
|
|
24
21
|
type LinkPreviewLayout = "vertical" | "horizontal";
|
|
25
|
-
type LinkPreviewPreset =
|
|
22
|
+
type LinkPreviewPreset = IconType;
|
|
26
23
|
interface LinkPreviewProps {
|
|
27
24
|
url: string;
|
|
28
25
|
title: string;
|
|
29
26
|
description?: string;
|
|
30
27
|
image?: string;
|
|
31
28
|
preset?: LinkPreviewPreset;
|
|
29
|
+
icon?: React.ReactNode;
|
|
32
30
|
size?: LinkPreviewSize;
|
|
33
31
|
layout?: LinkPreviewLayout;
|
|
34
32
|
width?: string | number;
|
|
35
33
|
height?: string | number;
|
|
36
34
|
className?: string;
|
|
35
|
+
titleColor?: string;
|
|
36
|
+
descriptionColor?: string;
|
|
37
37
|
}
|
|
38
|
-
declare function LinkPreview({ url, title, description, image, preset, size, layout, width, height, className }: LinkPreviewProps): React.JSX.Element;
|
|
38
|
+
declare function LinkPreview({ url, title, description, image, preset, icon, size, layout, width, height, className, titleColor, descriptionColor }: LinkPreviewProps): React.JSX.Element;
|
|
39
39
|
|
|
40
40
|
export { LinkPreview, LinkPreview as default };
|
|
41
41
|
export type { LinkPreviewLayout, LinkPreviewPreset, LinkPreviewProps, LinkPreviewSize };
|
package/dist/index.esm.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
// Inline SVG data URIs for preset images
|
|
5
|
-
const PRESET_IMAGES = {
|
|
6
|
-
github: `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTgiIGhlaWdodD0iOTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00OC44NTQgMEMyMS44MzkgMCAwIDIyIDAgNDkuMjE3YzAgMjEuNzU2IDEzLjk5MyA0MC4xNzIgMzMuNDA1IDQ2LjY5IDIuNDI3LjQ5IDMuMzE2LTEuMDU5IDMuMzE2LTIuMzYyIDAtMS4xNDEtLjA4LTUuMDUyLS4wOC05LjEyNy0xMy41OSAyLjkzNC0xNi40Mi01Ljg2Ny0xNi40Mi01Ljg2Ny0yLjE4NC01LjcwNC01LjQyLTcuMTctNS40Mi03LjE3LTQuNDQ4LTMuMDE1LjMyNC0zLjAxNS4zMjQtMy4wMTUgNC45MzQuMzI2IDcuNTIzIDUuMDUyIDcuNTIzIDUuMDUyIDQuMzY3IDcuNDk2IDExLjQwNCA1LjM3OCAxNC4yMzUgNC4wNzQuNDA0LTMuMTc4IDEuNjk5LTUuMzc4IDMuMDc0LTYuNi0xMC44MzktMS4xNDEtMjIuMjQzLTUuMzc4LTIyLjI0My0yNC4yODMgMC01LjM3OCAxLjk0LTkuNzc4IDUuMDE0LTEzLjItLjQ4NS0xLjIyMi0yLjE4NC02LjI3NS40ODYtMTMuMDM4IDAgMCA0LjEyNS0xLjMwNCAxMy40MjYgNS4wNTJhNDYuOTcgNDYuOTcgMCAwIDEgMTIuMjE0LTEuNjNjNC4xMjUgMCA4LjMzLjU3MSAxMi4yMTMgMS42MyA5LjMwMi02LjM1NiAxMy40MjctNS4wNTIgMTMuNDI3LTUuMDUyIDIuNjcgNi43NjMuOTcgMTEuODE2LjQ4NSAxMy4wMzggMy4xNTUgMy40MjIgNS4wMTUgNy44MjIgNS4wMTUgMTMuMiAwIDE4LjkwNS0xMS40MDQgMjMuMDYtMjIuMzI0IDI0LjI4MyAxLjc4IDEuNTQ4IDMuMzE2IDQuNDgxIDMuMzE2IDkuMTI2IDAgNi42LS4wOCAxMS44OTctLjA4IDEzLjUyNiAwIDEuMzA0Ljg5IDIuODUzIDMuMzE2IDIuMzY0IDE5LjQxMi02LjUyIDMzLjQwNS0yNC45MzUgMzMuNDA1LTQ2LjY5MUM5Ny43MDcgMjIgNzUuNzg4IDAgNDguODU0IDB6IiBmaWxsPSIjMjQyOTJmIi8+PC9zdmc+`,
|
|
7
|
-
npm: "https://avatars.githubusercontent.com/u/6078720?s=200&v=4"
|
|
8
|
-
};
|
|
9
4
|
const sizeConfig = {
|
|
10
5
|
small: {
|
|
11
6
|
imageHeight: "120px",
|
|
@@ -32,11 +27,12 @@ const sizeConfig = {
|
|
|
32
27
|
lineClamp: 3
|
|
33
28
|
}
|
|
34
29
|
};
|
|
35
|
-
function LinkPreview({ url, title, description, image, preset, size = "medium", layout = "vertical", width = "100%", height = "auto", className = "" }) {
|
|
30
|
+
function LinkPreview({ url, title, description, image, preset, icon, size = "medium", layout = "vertical", width = "100%", height = "auto", className = "", titleColor, descriptionColor }) {
|
|
36
31
|
const config = sizeConfig[size];
|
|
37
32
|
const isHorizontal = layout === "horizontal";
|
|
38
|
-
|
|
39
|
-
const
|
|
33
|
+
const PresetIcon = preset;
|
|
34
|
+
const iconSize = size === "large" ? 96 : size === "medium" ? 64 : 48;
|
|
35
|
+
const iconFontSize = size === "large" ? "96px" : size === "medium" ? "64px" : "48px";
|
|
40
36
|
return (React.createElement("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: `link-preview ${className}`, style: {
|
|
41
37
|
display: isHorizontal ? "flex" : "block",
|
|
42
38
|
flexDirection: isHorizontal ? "row" : undefined,
|
|
@@ -54,17 +50,36 @@ function LinkPreview({ url, title, description, image, preset, size = "medium",
|
|
|
54
50
|
}, onMouseLeave: (e) => {
|
|
55
51
|
e.currentTarget.style.boxShadow = "none";
|
|
56
52
|
} },
|
|
57
|
-
|
|
53
|
+
icon ? (React.createElement("div", { style: {
|
|
54
|
+
width: isHorizontal ? config.imageWidth : "100%",
|
|
55
|
+
height: isHorizontal ? "100%" : config.imageHeight,
|
|
56
|
+
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
57
|
+
flexShrink: isHorizontal ? 0 : undefined,
|
|
58
|
+
display: "flex",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
justifyContent: "center",
|
|
61
|
+
backgroundColor: "#f6f8fa",
|
|
62
|
+
fontSize: iconFontSize
|
|
63
|
+
} }, icon)) : image ? (React.createElement("div", { style: {
|
|
58
64
|
width: isHorizontal ? config.imageWidth : "100%",
|
|
59
65
|
height: isHorizontal ? "100%" : config.imageHeight,
|
|
60
66
|
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
61
67
|
flexShrink: isHorizontal ? 0 : undefined,
|
|
62
|
-
backgroundImage: `url(${
|
|
63
|
-
backgroundSize:
|
|
68
|
+
backgroundImage: `url(${image})`,
|
|
69
|
+
backgroundSize: "cover",
|
|
64
70
|
backgroundPosition: "center",
|
|
65
|
-
backgroundRepeat: "no-repeat"
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
backgroundRepeat: "no-repeat"
|
|
72
|
+
} })) : PresetIcon ? (React.createElement("div", { style: {
|
|
73
|
+
width: isHorizontal ? config.imageWidth : "100%",
|
|
74
|
+
height: isHorizontal ? "100%" : config.imageHeight,
|
|
75
|
+
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
76
|
+
flexShrink: isHorizontal ? 0 : undefined,
|
|
77
|
+
display: "flex",
|
|
78
|
+
alignItems: "center",
|
|
79
|
+
justifyContent: "center",
|
|
80
|
+
backgroundColor: "#f6f8fa"
|
|
81
|
+
} },
|
|
82
|
+
React.createElement(PresetIcon, { size: iconSize }))) : null,
|
|
68
83
|
React.createElement("div", { style: {
|
|
69
84
|
padding: config.padding,
|
|
70
85
|
flex: isHorizontal ? 1 : undefined,
|
|
@@ -72,11 +87,11 @@ function LinkPreview({ url, title, description, image, preset, size = "medium",
|
|
|
72
87
|
flexDirection: "column",
|
|
73
88
|
justifyContent: "center"
|
|
74
89
|
} },
|
|
75
|
-
React.createElement("h3", { style: { margin: "0 0 8px 0", fontSize: config.titleSize } }, title),
|
|
90
|
+
React.createElement("h3", { style: { margin: "0 0 8px 0", fontSize: config.titleSize, color: titleColor } }, title),
|
|
76
91
|
description && (React.createElement("p", { style: {
|
|
77
92
|
margin: 0,
|
|
78
93
|
fontSize: config.descriptionSize,
|
|
79
|
-
color: "#666",
|
|
94
|
+
color: descriptionColor || "#666",
|
|
80
95
|
display: "-webkit-box",
|
|
81
96
|
WebkitLineClamp: config.lineClamp,
|
|
82
97
|
WebkitBoxOrient: "vertical",
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/nextjs/components/LinkPreview.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Simple Link Preview Component\n *\n * Usage with custom image:\n * <LinkPreview
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/nextjs/components/LinkPreview.tsx"],"sourcesContent":["\"use client\";\r\n\r\n/**\r\n * Simple Link Preview Component\r\n *\r\n * Usage with custom image:\r\n * <LinkPreview url=\"...\" title=\"...\" image=\"https://example.com/image.png\" />\r\n *\r\n * Usage with preset icon (react-icons component):\r\n * <LinkPreview url=\"...\" title=\"...\" preset={FaGithub} />\r\n *\r\n * Usage with icon (any React element, e.g. react-icons):\r\n * <LinkPreview url=\"...\" title=\"...\" icon={<FaGithub size={48} />} />\r\n *\r\n * Usage with custom text colors:\r\n * <LinkPreview url=\"...\" title=\"...\" titleColor=\"#333\" descriptionColor=\"#999\" />\r\n */\r\n\r\nimport React from \"react\";\r\nimport type { IconType } from \"react-icons\";\r\n\r\nexport type LinkPreviewSize = \"small\" | \"medium\" | \"large\";\r\nexport type LinkPreviewLayout = \"vertical\" | \"horizontal\";\r\nexport type LinkPreviewPreset = IconType;\r\n\r\nexport interface LinkPreviewProps {\r\n url: string;\r\n title: string;\r\n description?: string;\r\n image?: string;\r\n preset?: LinkPreviewPreset;\r\n icon?: React.ReactNode;\r\n size?: LinkPreviewSize;\r\n layout?: LinkPreviewLayout;\r\n width?: string | number;\r\n height?: string | number;\r\n className?: string;\r\n titleColor?: string;\r\n descriptionColor?: string;\r\n}\r\n\r\nconst sizeConfig = {\r\n small: {\r\n imageHeight: \"120px\",\r\n imageWidth: \"120px\",\r\n titleSize: \"14px\",\r\n descriptionSize: \"12px\",\r\n padding: \"8px\",\r\n lineClamp: 1\r\n },\r\n medium: {\r\n imageHeight: \"200px\",\r\n imageWidth: \"200px\",\r\n titleSize: \"16px\",\r\n descriptionSize: \"14px\",\r\n padding: \"12px\",\r\n lineClamp: 2\r\n },\r\n large: {\r\n imageHeight: \"300px\",\r\n imageWidth: \"280px\",\r\n titleSize: \"20px\",\r\n descriptionSize: \"16px\",\r\n padding: \"16px\",\r\n lineClamp: 3\r\n }\r\n};\r\n\r\nexport function LinkPreview({\r\n url,\r\n title,\r\n description,\r\n image,\r\n preset,\r\n icon,\r\n size = \"medium\",\r\n layout = \"vertical\",\r\n width = \"100%\",\r\n height = \"auto\",\r\n className = \"\",\r\n titleColor,\r\n descriptionColor\r\n}: LinkPreviewProps) {\r\n const config = sizeConfig[size];\r\n const isHorizontal = layout === \"horizontal\";\r\n\r\n const PresetIcon = preset;\r\n const iconSize = size === \"large\" ? 96 : size === \"medium\" ? 64 : 48;\r\n const iconFontSize = size === \"large\" ? \"96px\" : size === \"medium\" ? \"64px\" : \"48px\";\r\n\r\n return (\r\n <a\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className={`link-preview ${className}`}\r\n style={{\r\n display: isHorizontal ? \"flex\" : \"block\",\r\n flexDirection: isHorizontal ? \"row\" : undefined,\r\n width,\r\n maxWidth: isHorizontal ? undefined : \"400px\",\r\n height,\r\n textDecoration: \"none\",\r\n color: \"inherit\",\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n transition: \"box-shadow 0.3s\"\r\n }}\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLElement).style.boxShadow = \"0 4px 12px rgba(0,0,0,0.15)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n (e.currentTarget as HTMLElement).style.boxShadow = \"none\";\r\n }}\r\n >\r\n {icon ? (\r\n <div\r\n style={{\r\n width: isHorizontal ? config.imageWidth : \"100%\",\r\n height: isHorizontal ? \"100%\" : config.imageHeight,\r\n minHeight: isHorizontal ? config.imageHeight : undefined,\r\n flexShrink: isHorizontal ? 0 : undefined,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n backgroundColor: \"#f6f8fa\",\r\n fontSize: iconFontSize\r\n }}\r\n >\r\n {icon}\r\n </div>\r\n ) : image ? (\r\n <div\r\n style={{\r\n width: isHorizontal ? config.imageWidth : \"100%\",\r\n height: isHorizontal ? \"100%\" : config.imageHeight,\r\n minHeight: isHorizontal ? config.imageHeight : undefined,\r\n flexShrink: isHorizontal ? 0 : undefined,\r\n backgroundImage: `url(${image})`,\r\n backgroundSize: \"cover\",\r\n backgroundPosition: \"center\",\r\n backgroundRepeat: \"no-repeat\"\r\n }}\r\n />\r\n ) : PresetIcon ? (\r\n <div\r\n style={{\r\n width: isHorizontal ? config.imageWidth : \"100%\",\r\n height: isHorizontal ? \"100%\" : config.imageHeight,\r\n minHeight: isHorizontal ? config.imageHeight : undefined,\r\n flexShrink: isHorizontal ? 0 : undefined,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n backgroundColor: \"#f6f8fa\"\r\n }}\r\n >\r\n <PresetIcon size={iconSize} />\r\n </div>\r\n ) : null}\r\n <div\r\n style={{\r\n padding: config.padding,\r\n flex: isHorizontal ? 1 : undefined,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n justifyContent: \"center\"\r\n }}\r\n >\r\n <h3 style={{ margin: \"0 0 8px 0\", fontSize: config.titleSize, color: titleColor }}>{title}</h3>\r\n {description && (\r\n <p\r\n style={\r\n {\r\n margin: 0,\r\n fontSize: config.descriptionSize,\r\n color: descriptionColor || \"#666\",\r\n display: \"-webkit-box\",\r\n WebkitLineClamp: config.lineClamp,\r\n WebkitBoxOrient: \"vertical\",\r\n overflow: \"hidden\"\r\n } as React.CSSProperties\r\n }\r\n >\r\n {description}\r\n </p>\r\n )}\r\n </div>\r\n </a>\r\n );\r\n}\r\n\r\nexport default LinkPreview;\r\n"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
|
|
6
6
|
var React = require('react');
|
|
7
7
|
|
|
8
|
-
// Inline SVG data URIs for preset images
|
|
9
|
-
const PRESET_IMAGES = {
|
|
10
|
-
github: `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTgiIGhlaWdodD0iOTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00OC44NTQgMEMyMS44MzkgMCAwIDIyIDAgNDkuMjE3YzAgMjEuNzU2IDEzLjk5MyA0MC4xNzIgMzMuNDA1IDQ2LjY5IDIuNDI3LjQ5IDMuMzE2LTEuMDU5IDMuMzE2LTIuMzYyIDAtMS4xNDEtLjA4LTUuMDUyLS4wOC05LjEyNy0xMy41OSAyLjkzNC0xNi40Mi01Ljg2Ny0xNi40Mi01Ljg2Ny0yLjE4NC01LjcwNC01LjQyLTcuMTctNS40Mi03LjE3LTQuNDQ4LTMuMDE1LjMyNC0zLjAxNS4zMjQtMy4wMTUgNC45MzQuMzI2IDcuNTIzIDUuMDUyIDcuNTIzIDUuMDUyIDQuMzY3IDcuNDk2IDExLjQwNCA1LjM3OCAxNC4yMzUgNC4wNzQuNDA0LTMuMTc4IDEuNjk5LTUuMzc4IDMuMDc0LTYuNi0xMC44MzktMS4xNDEtMjIuMjQzLTUuMzc4LTIyLjI0My0yNC4yODMgMC01LjM3OCAxLjk0LTkuNzc4IDUuMDE0LTEzLjItLjQ4NS0xLjIyMi0yLjE4NC02LjI3NS40ODYtMTMuMDM4IDAgMCA0LjEyNS0xLjMwNCAxMy40MjYgNS4wNTJhNDYuOTcgNDYuOTcgMCAwIDEgMTIuMjE0LTEuNjNjNC4xMjUgMCA4LjMzLjU3MSAxMi4yMTMgMS42MyA5LjMwMi02LjM1NiAxMy40MjctNS4wNTIgMTMuNDI3LTUuMDUyIDIuNjcgNi43NjMuOTcgMTEuODE2LjQ4NSAxMy4wMzggMy4xNTUgMy40MjIgNS4wMTUgNy44MjIgNS4wMTUgMTMuMiAwIDE4LjkwNS0xMS40MDQgMjMuMDYtMjIuMzI0IDI0LjI4MyAxLjc4IDEuNTQ4IDMuMzE2IDQuNDgxIDMuMzE2IDkuMTI2IDAgNi42LS4wOCAxMS44OTctLjA4IDEzLjUyNiAwIDEuMzA0Ljg5IDIuODUzIDMuMzE2IDIuMzY0IDE5LjQxMi02LjUyIDMzLjQwNS0yNC45MzUgMzMuNDA1LTQ2LjY5MUM5Ny43MDcgMjIgNzUuNzg4IDAgNDguODU0IDB6IiBmaWxsPSIjMjQyOTJmIi8+PC9zdmc+`,
|
|
11
|
-
npm: "https://avatars.githubusercontent.com/u/6078720?s=200&v=4"
|
|
12
|
-
};
|
|
13
8
|
const sizeConfig = {
|
|
14
9
|
small: {
|
|
15
10
|
imageHeight: "120px",
|
|
@@ -36,11 +31,12 @@ const sizeConfig = {
|
|
|
36
31
|
lineClamp: 3
|
|
37
32
|
}
|
|
38
33
|
};
|
|
39
|
-
function LinkPreview({ url, title, description, image, preset, size = "medium", layout = "vertical", width = "100%", height = "auto", className = "" }) {
|
|
34
|
+
function LinkPreview({ url, title, description, image, preset, icon, size = "medium", layout = "vertical", width = "100%", height = "auto", className = "", titleColor, descriptionColor }) {
|
|
40
35
|
const config = sizeConfig[size];
|
|
41
36
|
const isHorizontal = layout === "horizontal";
|
|
42
|
-
|
|
43
|
-
const
|
|
37
|
+
const PresetIcon = preset;
|
|
38
|
+
const iconSize = size === "large" ? 96 : size === "medium" ? 64 : 48;
|
|
39
|
+
const iconFontSize = size === "large" ? "96px" : size === "medium" ? "64px" : "48px";
|
|
44
40
|
return (React.createElement("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: `link-preview ${className}`, style: {
|
|
45
41
|
display: isHorizontal ? "flex" : "block",
|
|
46
42
|
flexDirection: isHorizontal ? "row" : undefined,
|
|
@@ -58,17 +54,36 @@ function LinkPreview({ url, title, description, image, preset, size = "medium",
|
|
|
58
54
|
}, onMouseLeave: (e) => {
|
|
59
55
|
e.currentTarget.style.boxShadow = "none";
|
|
60
56
|
} },
|
|
61
|
-
|
|
57
|
+
icon ? (React.createElement("div", { style: {
|
|
58
|
+
width: isHorizontal ? config.imageWidth : "100%",
|
|
59
|
+
height: isHorizontal ? "100%" : config.imageHeight,
|
|
60
|
+
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
61
|
+
flexShrink: isHorizontal ? 0 : undefined,
|
|
62
|
+
display: "flex",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
justifyContent: "center",
|
|
65
|
+
backgroundColor: "#f6f8fa",
|
|
66
|
+
fontSize: iconFontSize
|
|
67
|
+
} }, icon)) : image ? (React.createElement("div", { style: {
|
|
62
68
|
width: isHorizontal ? config.imageWidth : "100%",
|
|
63
69
|
height: isHorizontal ? "100%" : config.imageHeight,
|
|
64
70
|
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
65
71
|
flexShrink: isHorizontal ? 0 : undefined,
|
|
66
|
-
backgroundImage: `url(${
|
|
67
|
-
backgroundSize:
|
|
72
|
+
backgroundImage: `url(${image})`,
|
|
73
|
+
backgroundSize: "cover",
|
|
68
74
|
backgroundPosition: "center",
|
|
69
|
-
backgroundRepeat: "no-repeat"
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
backgroundRepeat: "no-repeat"
|
|
76
|
+
} })) : PresetIcon ? (React.createElement("div", { style: {
|
|
77
|
+
width: isHorizontal ? config.imageWidth : "100%",
|
|
78
|
+
height: isHorizontal ? "100%" : config.imageHeight,
|
|
79
|
+
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
80
|
+
flexShrink: isHorizontal ? 0 : undefined,
|
|
81
|
+
display: "flex",
|
|
82
|
+
alignItems: "center",
|
|
83
|
+
justifyContent: "center",
|
|
84
|
+
backgroundColor: "#f6f8fa"
|
|
85
|
+
} },
|
|
86
|
+
React.createElement(PresetIcon, { size: iconSize }))) : null,
|
|
72
87
|
React.createElement("div", { style: {
|
|
73
88
|
padding: config.padding,
|
|
74
89
|
flex: isHorizontal ? 1 : undefined,
|
|
@@ -76,11 +91,11 @@ function LinkPreview({ url, title, description, image, preset, size = "medium",
|
|
|
76
91
|
flexDirection: "column",
|
|
77
92
|
justifyContent: "center"
|
|
78
93
|
} },
|
|
79
|
-
React.createElement("h3", { style: { margin: "0 0 8px 0", fontSize: config.titleSize } }, title),
|
|
94
|
+
React.createElement("h3", { style: { margin: "0 0 8px 0", fontSize: config.titleSize, color: titleColor } }, title),
|
|
80
95
|
description && (React.createElement("p", { style: {
|
|
81
96
|
margin: 0,
|
|
82
97
|
fontSize: config.descriptionSize,
|
|
83
|
-
color: "#666",
|
|
98
|
+
color: descriptionColor || "#666",
|
|
84
99
|
display: "-webkit-box",
|
|
85
100
|
WebkitLineClamp: config.lineClamp,
|
|
86
101
|
WebkitBoxOrient: "vertical",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/nextjs/components/LinkPreview.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Simple Link Preview Component\n *\n * Usage with custom image:\n * <LinkPreview
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/nextjs/components/LinkPreview.tsx"],"sourcesContent":["\"use client\";\r\n\r\n/**\r\n * Simple Link Preview Component\r\n *\r\n * Usage with custom image:\r\n * <LinkPreview url=\"...\" title=\"...\" image=\"https://example.com/image.png\" />\r\n *\r\n * Usage with preset icon (react-icons component):\r\n * <LinkPreview url=\"...\" title=\"...\" preset={FaGithub} />\r\n *\r\n * Usage with icon (any React element, e.g. react-icons):\r\n * <LinkPreview url=\"...\" title=\"...\" icon={<FaGithub size={48} />} />\r\n *\r\n * Usage with custom text colors:\r\n * <LinkPreview url=\"...\" title=\"...\" titleColor=\"#333\" descriptionColor=\"#999\" />\r\n */\r\n\r\nimport React from \"react\";\r\nimport type { IconType } from \"react-icons\";\r\n\r\nexport type LinkPreviewSize = \"small\" | \"medium\" | \"large\";\r\nexport type LinkPreviewLayout = \"vertical\" | \"horizontal\";\r\nexport type LinkPreviewPreset = IconType;\r\n\r\nexport interface LinkPreviewProps {\r\n url: string;\r\n title: string;\r\n description?: string;\r\n image?: string;\r\n preset?: LinkPreviewPreset;\r\n icon?: React.ReactNode;\r\n size?: LinkPreviewSize;\r\n layout?: LinkPreviewLayout;\r\n width?: string | number;\r\n height?: string | number;\r\n className?: string;\r\n titleColor?: string;\r\n descriptionColor?: string;\r\n}\r\n\r\nconst sizeConfig = {\r\n small: {\r\n imageHeight: \"120px\",\r\n imageWidth: \"120px\",\r\n titleSize: \"14px\",\r\n descriptionSize: \"12px\",\r\n padding: \"8px\",\r\n lineClamp: 1\r\n },\r\n medium: {\r\n imageHeight: \"200px\",\r\n imageWidth: \"200px\",\r\n titleSize: \"16px\",\r\n descriptionSize: \"14px\",\r\n padding: \"12px\",\r\n lineClamp: 2\r\n },\r\n large: {\r\n imageHeight: \"300px\",\r\n imageWidth: \"280px\",\r\n titleSize: \"20px\",\r\n descriptionSize: \"16px\",\r\n padding: \"16px\",\r\n lineClamp: 3\r\n }\r\n};\r\n\r\nexport function LinkPreview({\r\n url,\r\n title,\r\n description,\r\n image,\r\n preset,\r\n icon,\r\n size = \"medium\",\r\n layout = \"vertical\",\r\n width = \"100%\",\r\n height = \"auto\",\r\n className = \"\",\r\n titleColor,\r\n descriptionColor\r\n}: LinkPreviewProps) {\r\n const config = sizeConfig[size];\r\n const isHorizontal = layout === \"horizontal\";\r\n\r\n const PresetIcon = preset;\r\n const iconSize = size === \"large\" ? 96 : size === \"medium\" ? 64 : 48;\r\n const iconFontSize = size === \"large\" ? \"96px\" : size === \"medium\" ? \"64px\" : \"48px\";\r\n\r\n return (\r\n <a\r\n href={url}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className={`link-preview ${className}`}\r\n style={{\r\n display: isHorizontal ? \"flex\" : \"block\",\r\n flexDirection: isHorizontal ? \"row\" : undefined,\r\n width,\r\n maxWidth: isHorizontal ? undefined : \"400px\",\r\n height,\r\n textDecoration: \"none\",\r\n color: \"inherit\",\r\n border: \"1px solid #e0e0e0\",\r\n borderRadius: \"8px\",\r\n overflow: \"hidden\",\r\n transition: \"box-shadow 0.3s\"\r\n }}\r\n onMouseEnter={(e) => {\r\n (e.currentTarget as HTMLElement).style.boxShadow = \"0 4px 12px rgba(0,0,0,0.15)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n (e.currentTarget as HTMLElement).style.boxShadow = \"none\";\r\n }}\r\n >\r\n {icon ? (\r\n <div\r\n style={{\r\n width: isHorizontal ? config.imageWidth : \"100%\",\r\n height: isHorizontal ? \"100%\" : config.imageHeight,\r\n minHeight: isHorizontal ? config.imageHeight : undefined,\r\n flexShrink: isHorizontal ? 0 : undefined,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n backgroundColor: \"#f6f8fa\",\r\n fontSize: iconFontSize\r\n }}\r\n >\r\n {icon}\r\n </div>\r\n ) : image ? (\r\n <div\r\n style={{\r\n width: isHorizontal ? config.imageWidth : \"100%\",\r\n height: isHorizontal ? \"100%\" : config.imageHeight,\r\n minHeight: isHorizontal ? config.imageHeight : undefined,\r\n flexShrink: isHorizontal ? 0 : undefined,\r\n backgroundImage: `url(${image})`,\r\n backgroundSize: \"cover\",\r\n backgroundPosition: \"center\",\r\n backgroundRepeat: \"no-repeat\"\r\n }}\r\n />\r\n ) : PresetIcon ? (\r\n <div\r\n style={{\r\n width: isHorizontal ? config.imageWidth : \"100%\",\r\n height: isHorizontal ? \"100%\" : config.imageHeight,\r\n minHeight: isHorizontal ? config.imageHeight : undefined,\r\n flexShrink: isHorizontal ? 0 : undefined,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n backgroundColor: \"#f6f8fa\"\r\n }}\r\n >\r\n <PresetIcon size={iconSize} />\r\n </div>\r\n ) : null}\r\n <div\r\n style={{\r\n padding: config.padding,\r\n flex: isHorizontal ? 1 : undefined,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n justifyContent: \"center\"\r\n }}\r\n >\r\n <h3 style={{ margin: \"0 0 8px 0\", fontSize: config.titleSize, color: titleColor }}>{title}</h3>\r\n {description && (\r\n <p\r\n style={\r\n {\r\n margin: 0,\r\n fontSize: config.descriptionSize,\r\n color: descriptionColor || \"#666\",\r\n display: \"-webkit-box\",\r\n WebkitLineClamp: config.lineClamp,\r\n WebkitBoxOrient: \"vertical\",\r\n overflow: \"hidden\"\r\n } as React.CSSProperties\r\n }\r\n >\r\n {description}\r\n </p>\r\n )}\r\n </div>\r\n </a>\r\n );\r\n}\r\n\r\nexport default LinkPreview;\r\n"],"names":[],"mappings":"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"}
|
|
@@ -2,37 +2,37 @@
|
|
|
2
2
|
* Simple Link Preview Component
|
|
3
3
|
*
|
|
4
4
|
* Usage with custom image:
|
|
5
|
-
* <LinkPreview
|
|
6
|
-
* url="https://example.com"
|
|
7
|
-
* title="Example"
|
|
8
|
-
* description="Example description"
|
|
9
|
-
* image="https://example.com/image.png"
|
|
10
|
-
* />
|
|
5
|
+
* <LinkPreview url="..." title="..." image="https://example.com/image.png" />
|
|
11
6
|
*
|
|
12
|
-
* Usage with preset:
|
|
13
|
-
* <LinkPreview
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
7
|
+
* Usage with preset icon (react-icons component):
|
|
8
|
+
* <LinkPreview url="..." title="..." preset={FaGithub} />
|
|
9
|
+
*
|
|
10
|
+
* Usage with icon (any React element, e.g. react-icons):
|
|
11
|
+
* <LinkPreview url="..." title="..." icon={<FaGithub size={48} />} />
|
|
12
|
+
*
|
|
13
|
+
* Usage with custom text colors:
|
|
14
|
+
* <LinkPreview url="..." title="..." titleColor="#333" descriptionColor="#999" />
|
|
19
15
|
*/
|
|
20
16
|
import React from "react";
|
|
17
|
+
import type { IconType } from "react-icons";
|
|
21
18
|
export type LinkPreviewSize = "small" | "medium" | "large";
|
|
22
19
|
export type LinkPreviewLayout = "vertical" | "horizontal";
|
|
23
|
-
export type LinkPreviewPreset =
|
|
20
|
+
export type LinkPreviewPreset = IconType;
|
|
24
21
|
export interface LinkPreviewProps {
|
|
25
22
|
url: string;
|
|
26
23
|
title: string;
|
|
27
24
|
description?: string;
|
|
28
25
|
image?: string;
|
|
29
26
|
preset?: LinkPreviewPreset;
|
|
27
|
+
icon?: React.ReactNode;
|
|
30
28
|
size?: LinkPreviewSize;
|
|
31
29
|
layout?: LinkPreviewLayout;
|
|
32
30
|
width?: string | number;
|
|
33
31
|
height?: string | number;
|
|
34
32
|
className?: string;
|
|
33
|
+
titleColor?: string;
|
|
34
|
+
descriptionColor?: string;
|
|
35
35
|
}
|
|
36
|
-
export declare function LinkPreview({ url, title, description, image, preset, size, layout, width, height, className }: LinkPreviewProps): React.JSX.Element;
|
|
36
|
+
export declare function LinkPreview({ url, title, description, image, preset, icon, size, layout, width, height, className, titleColor, descriptionColor }: LinkPreviewProps): React.JSX.Element;
|
|
37
37
|
export default LinkPreview;
|
|
38
38
|
//# sourceMappingURL=LinkPreview.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LinkPreview.d.ts","sourceRoot":"","sources":["../../../../src/nextjs/components/LinkPreview.tsx"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"LinkPreview.d.ts","sourceRoot":"","sources":["../../../../src/nextjs/components/LinkPreview.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAC3D,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,YAAY,CAAC;AAC1D,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA6BD,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,KAAK,EACL,WAAW,EACX,KAAK,EACL,MAAM,EACN,IAAI,EACJ,IAAe,EACf,MAAmB,EACnB,KAAc,EACd,MAAe,EACf,SAAc,EACd,UAAU,EACV,gBAAgB,EACjB,EAAE,gBAAgB,qBA6GlB;AAED,eAAe,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,72 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nextjs-link-preview",
|
|
3
|
-
"version": "
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "A simple, lightweight Next.js component for displaying beautiful link preview cards with preset image support",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"nextjs",
|
|
8
|
-
"next",
|
|
9
|
-
"react",
|
|
10
|
-
"link-preview",
|
|
11
|
-
"preview",
|
|
12
|
-
"card",
|
|
13
|
-
"component",
|
|
14
|
-
"typescript",
|
|
15
|
-
"presentational",
|
|
16
|
-
"github",
|
|
17
|
-
"npm",
|
|
18
|
-
"preset"
|
|
19
|
-
],
|
|
20
|
-
"author": "Seth Carney",
|
|
21
|
-
"license": "MIT",
|
|
22
|
-
"repository": {
|
|
23
|
-
"type": "git",
|
|
24
|
-
"url": "git+https://github.com/sethcarney/nextjs-link-preview.git"
|
|
25
|
-
},
|
|
26
|
-
"homepage": "https://github.com/sethcarney/nextjs-link-preview#readme",
|
|
27
|
-
"bugs": {
|
|
28
|
-
"url": "https://github.com/sethcarney/nextjs-link-preview/issues"
|
|
29
|
-
},
|
|
30
|
-
"publishConfig": {
|
|
31
|
-
"registry": "https://registry.npmjs.org/",
|
|
32
|
-
"access": "public"
|
|
33
|
-
},
|
|
34
|
-
"main": "dist/index.js",
|
|
35
|
-
"module": "dist/index.esm.js",
|
|
36
|
-
"types": "dist/index.d.ts",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"dev": "
|
|
50
|
-
"test": "
|
|
51
|
-
"prepublishOnly": "npm run build"
|
|
52
|
-
},
|
|
53
|
-
"peerDependencies": {
|
|
54
|
-
"next": ">=14.0.0",
|
|
55
|
-
"react": ">=18.0.0",
|
|
56
|
-
"react-dom": ">=18.0.0"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"devDependencies": {
|
|
60
|
-
"@rollup/plugin-commonjs": "^29.0.0",
|
|
61
|
-
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
62
|
-
"@rollup/plugin-typescript": "^12.3.0",
|
|
63
|
-
"@types/react": "^19.2.
|
|
64
|
-
"@types/react-dom": "^19.2.
|
|
65
|
-
"prettier": "^3.
|
|
66
|
-
"
|
|
67
|
-
"rollup
|
|
68
|
-
"rollup-plugin-
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "nextjs-link-preview",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A simple, lightweight Next.js component for displaying beautiful link preview cards with preset image support",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"nextjs",
|
|
8
|
+
"next",
|
|
9
|
+
"react",
|
|
10
|
+
"link-preview",
|
|
11
|
+
"preview",
|
|
12
|
+
"card",
|
|
13
|
+
"component",
|
|
14
|
+
"typescript",
|
|
15
|
+
"presentational",
|
|
16
|
+
"github",
|
|
17
|
+
"npm",
|
|
18
|
+
"preset"
|
|
19
|
+
],
|
|
20
|
+
"author": "Seth Carney",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/sethcarney/nextjs-link-preview.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/sethcarney/nextjs-link-preview#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/sethcarney/nextjs-link-preview/issues"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"registry": "https://registry.npmjs.org/",
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"main": "dist/index.js",
|
|
35
|
+
"module": "dist/index.esm.js",
|
|
36
|
+
"types": "dist/index.d.ts",
|
|
37
|
+
"bin": {
|
|
38
|
+
"nextjs-link-preview": "bin/cli.mjs"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"bin",
|
|
43
|
+
"src/nextjs/components/LinkPreview.tsx",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "rollup -c",
|
|
49
|
+
"dev": "node bin/cli.mjs init --path nextjs-demo/src/components/LinkPreview.tsx && cd nextjs-demo && npm run dev",
|
|
50
|
+
"test": "node bin/cli.mjs init --path nextjs-demo/src/components/LinkPreview.tsx && cd nextjs-demo && npm run dev",
|
|
51
|
+
"prepublishOnly": "npm run build"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"next": ">=14.0.0",
|
|
55
|
+
"react": ">=18.0.0",
|
|
56
|
+
"react-dom": ">=18.0.0",
|
|
57
|
+
"react-icons": ">=4.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
61
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
62
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
63
|
+
"@types/react": "^19.2.13",
|
|
64
|
+
"@types/react-dom": "^19.2.3",
|
|
65
|
+
"prettier": "^3.8.1",
|
|
66
|
+
"react-icons": "^5.5.0",
|
|
67
|
+
"rollup": "^4.57.1",
|
|
68
|
+
"rollup-plugin-dts": "^6.3.0",
|
|
69
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
70
|
+
"tslib": "^2.8.1",
|
|
71
|
+
"typescript": "^5.9.3"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple Link Preview Component
|
|
5
|
+
*
|
|
6
|
+
* Usage with custom image:
|
|
7
|
+
* <LinkPreview url="..." title="..." image="https://example.com/image.png" />
|
|
8
|
+
*
|
|
9
|
+
* Usage with preset icon (react-icons component):
|
|
10
|
+
* <LinkPreview url="..." title="..." preset={FaGithub} />
|
|
11
|
+
*
|
|
12
|
+
* Usage with icon (any React element, e.g. react-icons):
|
|
13
|
+
* <LinkPreview url="..." title="..." icon={<FaGithub size={48} />} />
|
|
14
|
+
*
|
|
15
|
+
* Usage with custom text colors:
|
|
16
|
+
* <LinkPreview url="..." title="..." titleColor="#333" descriptionColor="#999" />
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import React from "react";
|
|
20
|
+
import type { IconType } from "react-icons";
|
|
21
|
+
|
|
22
|
+
export type LinkPreviewSize = "small" | "medium" | "large";
|
|
23
|
+
export type LinkPreviewLayout = "vertical" | "horizontal";
|
|
24
|
+
export type LinkPreviewPreset = IconType;
|
|
25
|
+
|
|
26
|
+
export interface LinkPreviewProps {
|
|
27
|
+
url: string;
|
|
28
|
+
title: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
image?: string;
|
|
31
|
+
preset?: LinkPreviewPreset;
|
|
32
|
+
icon?: React.ReactNode;
|
|
33
|
+
size?: LinkPreviewSize;
|
|
34
|
+
layout?: LinkPreviewLayout;
|
|
35
|
+
width?: string | number;
|
|
36
|
+
height?: string | number;
|
|
37
|
+
className?: string;
|
|
38
|
+
titleColor?: string;
|
|
39
|
+
descriptionColor?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sizeConfig = {
|
|
43
|
+
small: {
|
|
44
|
+
imageHeight: "120px",
|
|
45
|
+
imageWidth: "120px",
|
|
46
|
+
titleSize: "14px",
|
|
47
|
+
descriptionSize: "12px",
|
|
48
|
+
padding: "8px",
|
|
49
|
+
lineClamp: 1
|
|
50
|
+
},
|
|
51
|
+
medium: {
|
|
52
|
+
imageHeight: "200px",
|
|
53
|
+
imageWidth: "200px",
|
|
54
|
+
titleSize: "16px",
|
|
55
|
+
descriptionSize: "14px",
|
|
56
|
+
padding: "12px",
|
|
57
|
+
lineClamp: 2
|
|
58
|
+
},
|
|
59
|
+
large: {
|
|
60
|
+
imageHeight: "300px",
|
|
61
|
+
imageWidth: "280px",
|
|
62
|
+
titleSize: "20px",
|
|
63
|
+
descriptionSize: "16px",
|
|
64
|
+
padding: "16px",
|
|
65
|
+
lineClamp: 3
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export function LinkPreview({
|
|
70
|
+
url,
|
|
71
|
+
title,
|
|
72
|
+
description,
|
|
73
|
+
image,
|
|
74
|
+
preset,
|
|
75
|
+
icon,
|
|
76
|
+
size = "medium",
|
|
77
|
+
layout = "vertical",
|
|
78
|
+
width = "100%",
|
|
79
|
+
height = "auto",
|
|
80
|
+
className = "",
|
|
81
|
+
titleColor,
|
|
82
|
+
descriptionColor
|
|
83
|
+
}: LinkPreviewProps) {
|
|
84
|
+
const config = sizeConfig[size];
|
|
85
|
+
const isHorizontal = layout === "horizontal";
|
|
86
|
+
|
|
87
|
+
const PresetIcon = preset;
|
|
88
|
+
const iconSize = size === "large" ? 96 : size === "medium" ? 64 : 48;
|
|
89
|
+
const iconFontSize = size === "large" ? "96px" : size === "medium" ? "64px" : "48px";
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<a
|
|
93
|
+
href={url}
|
|
94
|
+
target="_blank"
|
|
95
|
+
rel="noopener noreferrer"
|
|
96
|
+
className={`link-preview ${className}`}
|
|
97
|
+
style={{
|
|
98
|
+
display: isHorizontal ? "flex" : "block",
|
|
99
|
+
flexDirection: isHorizontal ? "row" : undefined,
|
|
100
|
+
width,
|
|
101
|
+
maxWidth: isHorizontal ? undefined : "400px",
|
|
102
|
+
height,
|
|
103
|
+
textDecoration: "none",
|
|
104
|
+
color: "inherit",
|
|
105
|
+
border: "1px solid #e0e0e0",
|
|
106
|
+
borderRadius: "8px",
|
|
107
|
+
overflow: "hidden",
|
|
108
|
+
transition: "box-shadow 0.3s"
|
|
109
|
+
}}
|
|
110
|
+
onMouseEnter={(e) => {
|
|
111
|
+
(e.currentTarget as HTMLElement).style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
|
|
112
|
+
}}
|
|
113
|
+
onMouseLeave={(e) => {
|
|
114
|
+
(e.currentTarget as HTMLElement).style.boxShadow = "none";
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{icon ? (
|
|
118
|
+
<div
|
|
119
|
+
style={{
|
|
120
|
+
width: isHorizontal ? config.imageWidth : "100%",
|
|
121
|
+
height: isHorizontal ? "100%" : config.imageHeight,
|
|
122
|
+
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
123
|
+
flexShrink: isHorizontal ? 0 : undefined,
|
|
124
|
+
display: "flex",
|
|
125
|
+
alignItems: "center",
|
|
126
|
+
justifyContent: "center",
|
|
127
|
+
backgroundColor: "#f6f8fa",
|
|
128
|
+
fontSize: iconFontSize
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{icon}
|
|
132
|
+
</div>
|
|
133
|
+
) : image ? (
|
|
134
|
+
<div
|
|
135
|
+
style={{
|
|
136
|
+
width: isHorizontal ? config.imageWidth : "100%",
|
|
137
|
+
height: isHorizontal ? "100%" : config.imageHeight,
|
|
138
|
+
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
139
|
+
flexShrink: isHorizontal ? 0 : undefined,
|
|
140
|
+
backgroundImage: `url(${image})`,
|
|
141
|
+
backgroundSize: "cover",
|
|
142
|
+
backgroundPosition: "center",
|
|
143
|
+
backgroundRepeat: "no-repeat"
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
) : PresetIcon ? (
|
|
147
|
+
<div
|
|
148
|
+
style={{
|
|
149
|
+
width: isHorizontal ? config.imageWidth : "100%",
|
|
150
|
+
height: isHorizontal ? "100%" : config.imageHeight,
|
|
151
|
+
minHeight: isHorizontal ? config.imageHeight : undefined,
|
|
152
|
+
flexShrink: isHorizontal ? 0 : undefined,
|
|
153
|
+
display: "flex",
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
justifyContent: "center",
|
|
156
|
+
backgroundColor: "#f6f8fa"
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<PresetIcon size={iconSize} />
|
|
160
|
+
</div>
|
|
161
|
+
) : null}
|
|
162
|
+
<div
|
|
163
|
+
style={{
|
|
164
|
+
padding: config.padding,
|
|
165
|
+
flex: isHorizontal ? 1 : undefined,
|
|
166
|
+
display: "flex",
|
|
167
|
+
flexDirection: "column",
|
|
168
|
+
justifyContent: "center"
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<h3 style={{ margin: "0 0 8px 0", fontSize: config.titleSize, color: titleColor }}>{title}</h3>
|
|
172
|
+
{description && (
|
|
173
|
+
<p
|
|
174
|
+
style={
|
|
175
|
+
{
|
|
176
|
+
margin: 0,
|
|
177
|
+
fontSize: config.descriptionSize,
|
|
178
|
+
color: descriptionColor || "#666",
|
|
179
|
+
display: "-webkit-box",
|
|
180
|
+
WebkitLineClamp: config.lineClamp,
|
|
181
|
+
WebkitBoxOrient: "vertical",
|
|
182
|
+
overflow: "hidden"
|
|
183
|
+
} as React.CSSProperties
|
|
184
|
+
}
|
|
185
|
+
>
|
|
186
|
+
{description}
|
|
187
|
+
</p>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</a>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default LinkPreview;
|