Skip to content

Commit b9ec9cc

Browse files
committed
docs: custom-sidebar example with nextjs + shadcn + tailwind
1 parent c082e84 commit b9ec9cc

17 files changed

Lines changed: 2853 additions & 0 deletions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# next.js
7+
/.next/
8+
/out/
9+
10+
# production
11+
/build
12+
13+
# debug
14+
npm-debug.log*
15+
yarn-debug.log*
16+
yarn-error.log*
17+
.pnpm-debug.log*
18+
19+
# env files
20+
.env*
21+
22+
# vercel
23+
.vercel
24+
25+
# typescript
26+
*.tsbuildinfo
27+
next-env.d.ts
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Custom Sidebar Example
2+
3+
_This example is built using NextJS, shadcn/ui, and the `@simplepdf/react-embed-pdf` package. Vanilla JS is also supported using the Iframe: [iframe example](../with-iframe/README.md)_
4+
5+
It demonstrates how to customize the SimplePDF editor by hiding the default sidebar and implementing a custom sidebar with tailored controls.
6+
7+
Additionally, it showcases programmatic control of the editor, allowing developers to interact with the PDF editor via code.
8+
9+
## Features
10+
11+
- **Custom Sidebar**: The default SimplePDF sidebar is hidden, replaced with a custom sidebar containing tools for text, checkboxes, signatures, images, and document uploads, along with a submit button and a download toggle.
12+
13+
- **Programmatic Control**: Use the `useEmbed` hook to programmatically control the SimplePDF editor, including submitting the document and selecting tools: [documentation](../../react/README#programmatic-control)
14+
15+
16+
## Installation
17+
18+
1. **Clone the Repository** (or create a new project directory):
19+
```sh
20+
git clone https://github.com/SimplePDF/simplepdf-embed.git
21+
cd examples/with-custom-sidebar
22+
```
23+
24+
2. **Install Dependencies**:
25+
```sh
26+
npm install
27+
```
28+
29+
3. **Run the Development Server**:
30+
```sh
31+
npm run dev
32+
```
33+
- Open your browser to `http://localhost:3000` to view the app.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
body {
6+
font-family: Arial, Helvetica, sans-serif;
7+
}
8+
9+
@layer utilities {
10+
.text-balance {
11+
text-wrap: balance;
12+
}
13+
}
14+
15+
@layer base {
16+
:root {
17+
--background: 0 0% 100%;
18+
--foreground: 0 0% 3.9%;
19+
--card: 0 0% 100%;
20+
--card-foreground: 0 0% 3.9%;
21+
--popover: 0 0% 100%;
22+
--popover-foreground: 0 0% 3.9%;
23+
--primary: 0 0% 9%;
24+
--primary-foreground: 0 0% 98%;
25+
--secondary: 0 0% 96.1%;
26+
--secondary-foreground: 0 0% 9%;
27+
--muted: 0 0% 96.1%;
28+
--muted-foreground: 0 0% 45.1%;
29+
--accent: 0 0% 96.1%;
30+
--accent-foreground: 0 0% 9%;
31+
--destructive: 0 84.2% 60.2%;
32+
--destructive-foreground: 0 0% 98%;
33+
--border: 0 0% 89.8%;
34+
--input: 0 0% 89.8%;
35+
--ring: 0 0% 3.9%;
36+
--chart-1: 12 76% 61%;
37+
--chart-2: 173 58% 39%;
38+
--chart-3: 197 37% 24%;
39+
--chart-4: 43 74% 66%;
40+
--chart-5: 27 87% 67%;
41+
--radius: 0.5rem;
42+
--sidebar-background: 0 0% 98%;
43+
--sidebar-foreground: 240 5.3% 26.1%;
44+
--sidebar-primary: 240 5.9% 10%;
45+
--sidebar-primary-foreground: 0 0% 98%;
46+
--sidebar-accent: 240 4.8% 95.9%;
47+
--sidebar-accent-foreground: 240 5.9% 10%;
48+
--sidebar-border: 220 13% 91%;
49+
--sidebar-ring: 217.2 91.2% 59.8%;
50+
}
51+
.dark {
52+
--background: 0 0% 3.9%;
53+
--foreground: 0 0% 98%;
54+
--card: 0 0% 3.9%;
55+
--card-foreground: 0 0% 98%;
56+
--popover: 0 0% 3.9%;
57+
--popover-foreground: 0 0% 98%;
58+
--primary: 0 0% 98%;
59+
--primary-foreground: 0 0% 9%;
60+
--secondary: 0 0% 14.9%;
61+
--secondary-foreground: 0 0% 98%;
62+
--muted: 0 0% 14.9%;
63+
--muted-foreground: 0 0% 63.9%;
64+
--accent: 0 0% 14.9%;
65+
--accent-foreground: 0 0% 98%;
66+
--destructive: 0 62.8% 30.6%;
67+
--destructive-foreground: 0 0% 98%;
68+
--border: 0 0% 14.9%;
69+
--input: 0 0% 14.9%;
70+
--ring: 0 0% 83.1%;
71+
--chart-1: 220 70% 50%;
72+
--chart-2: 160 60% 45%;
73+
--chart-3: 30 80% 55%;
74+
--chart-4: 280 65% 60%;
75+
--chart-5: 340 75% 55%;
76+
--sidebar-background: 240 5.9% 10%;
77+
--sidebar-foreground: 240 4.8% 95.9%;
78+
--sidebar-primary: 224.3 76.3% 48%;
79+
--sidebar-primary-foreground: 0 0% 100%;
80+
--sidebar-accent: 240 3.7% 15.9%;
81+
--sidebar-accent-foreground: 240 4.8% 95.9%;
82+
--sidebar-border: 240 3.7% 15.9%;
83+
--sidebar-ring: 217.2 91.2% 59.8%;
84+
}
85+
}
86+
87+
@layer base {
88+
* {
89+
@apply border-border;
90+
}
91+
body {
92+
@apply bg-background text-foreground;
93+
}
94+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Metadata } from 'next'
2+
import './globals.css'
3+
4+
export const metadata: Metadata = {
5+
title: 'SimplePDF - Custom Sidebar Demo',
6+
description: 'This example showcases the programmatic control of the editor together with custom sidebar design. Available under the PRO plan',
7+
}
8+
9+
export default function RootLayout({
10+
children,
11+
}: Readonly<{
12+
children: React.ReactNode
13+
}>) {
14+
return (
15+
<html lang="en">
16+
<body>{children}</body>
17+
</html>
18+
)
19+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Loading() {
2+
return null
3+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"use client"
2+
import { useState } from "react"
3+
import { Button } from "@/components/ui/button"
4+
import { Type, ImageIcon, MousePointer, Check, PenTool, FileText, Loader2 } from "lucide-react"
5+
import { EmbedPDF, useEmbed } from "@simplepdf/react-embed-pdf"
6+
import { Switch } from "@/components/ui/switch"
7+
8+
type ToolType = 'TEXT' | 'BOXED_TEXT' | 'CHECKBOX' | 'PICTURE' | 'SIGNATURE' | null;
9+
10+
export default function PDFEditorUI() {
11+
const [selectedTool, setSelectedTool] = useState<ToolType>(null)
12+
const [allowDownload, setAllowDownload] = useState<boolean>(false)
13+
const [isSubmitting, setIsSubmitting] = useState<boolean>(false)
14+
const [submitError, setSubmitError] = useState<string | null>(null)
15+
const [toolError, setToolError] = useState<string | null>(null)
16+
const { embedRef, actions } = useEmbed()
17+
18+
const handleToolSelect = async (tool: ToolType) => {
19+
setToolError(null)
20+
const selectedTool = await actions.selectTool(tool)
21+
if (!selectedTool.success) {
22+
console.error(selectedTool.error);
23+
setToolError("Failed to select tool");
24+
return;
25+
}
26+
27+
setSelectedTool(tool);
28+
}
29+
30+
const handleSubmit = async () => {
31+
setIsSubmitting(true)
32+
setSubmitError(null)
33+
34+
const submitResult = await actions.submit({ downloadCopyOnDevice: allowDownload })
35+
36+
if (!submitResult.success) {
37+
console.error(submitResult.error);
38+
setSubmitError("Failed to submit document");
39+
return;
40+
}
41+
42+
setIsSubmitting(false)
43+
44+
}
45+
46+
return (
47+
<div className="flex h-screen bg-gray-100">
48+
{/* Main Content Area */}
49+
<div className="flex-1 flex flex-col">
50+
{/* PDF Viewer */}
51+
<EmbedPDF
52+
className="w-100 h-screen"
53+
ref={embedRef}
54+
companyIdentifier="headless"
55+
mode="inline"
56+
/>
57+
</div>
58+
59+
{/* Right Sidebar */}
60+
<div className="w-80 bg-white border-l border-gray-200 flex flex-col">
61+
{/* Tool Icons */}
62+
<div className="p-4 border-b border-gray-200">
63+
<div className="flex items-center justify-center">
64+
<div className="flex space-x-2">
65+
<button
66+
onClick={() => handleToolSelect(null)}
67+
className={`p-2 hover:bg-gray-100 rounded border border-gray-200 ${selectedTool === null ? "bg-blue-100 border-blue-500" : ""}`}
68+
>
69+
<MousePointer className="h-5 w-5" />
70+
</button>
71+
<button
72+
onClick={() => handleToolSelect("TEXT")}
73+
className={`p-2 hover:bg-gray-100 rounded border border-gray-200 ${selectedTool === "TEXT" ? "bg-blue-100 border-blue-500" : ""}`}
74+
>
75+
<Type className="h-5 w-5" />
76+
</button>
77+
<button
78+
onClick={() => handleToolSelect("CHECKBOX")}
79+
className={`p-2 hover:bg-gray-100 rounded border border-gray-200 ${selectedTool === "CHECKBOX" ? "bg-blue-100 border-blue-500" : ""}`}
80+
>
81+
<Check className="h-5 w-5" />
82+
</button>
83+
<button
84+
onClick={() => handleToolSelect("SIGNATURE")}
85+
className={`p-2 hover:bg-gray-100 rounded border border-gray-200 ${selectedTool === "SIGNATURE" ? "bg-blue-100 border-blue-500" : ""}`}
86+
>
87+
<PenTool className="h-5 w-5" />
88+
</button>
89+
<button
90+
onClick={() => handleToolSelect("PICTURE")}
91+
className={`p-2 hover:bg-gray-100 rounded border border-gray-200 ${selectedTool === "PICTURE" ? "bg-blue-100 border-blue-500" : ""}`}
92+
>
93+
<ImageIcon className="h-5 w-5" />
94+
</button>
95+
</div>
96+
</div>
97+
</div>
98+
99+
{/* Main Content */}
100+
<div className="flex-1 p-6 flex flex-col">
101+
{/* Tool Error Display */}
102+
{toolError && (
103+
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded text-red-700 text-sm">{toolError}</div>
104+
)}
105+
106+
<div className="mt-auto space-y-4">
107+
{/* Download Copy Toggle - smaller and above submit */}
108+
<div className="flex items-center justify-between text-sm">
109+
<label htmlFor="allow-download" className="text-gray-600">
110+
Download copy
111+
</label>
112+
<Switch id="allow-download" checked={allowDownload} onCheckedChange={setAllowDownload} />
113+
</div>
114+
115+
{/* Submit Error Display */}
116+
{submitError && (
117+
<div className="p-3 bg-red-50 border border-red-200 rounded text-red-700 text-sm">{submitError}</div>
118+
)}
119+
120+
{/* Submit Button */}
121+
<Button
122+
onClick={handleSubmit}
123+
disabled={isSubmitting}
124+
className="w-full bg-blue-500 hover:bg-blue-600 text-white py-2.5 disabled:opacity-50"
125+
>
126+
{isSubmitting ? (
127+
<>
128+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
129+
Submitting...
130+
</>
131+
) : (
132+
"Submit"
133+
)}
134+
</Button>
135+
</div>
136+
</div>
137+
138+
{/* Footer */}
139+
<div className="p-6 border-t border-gray-200">
140+
<div className="flex items-center justify-center">
141+
<div className="flex items-center space-x-2">
142+
<span className="font-semibold text-gray-900">Custom Sidebar Demo</span>
143+
</div>
144+
</div>
145+
</div>
146+
</div>
147+
</div>
148+
)
149+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "default",
4+
"rsc": true,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "tailwind.config.ts",
8+
"css": "app/globals.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+
"iconLibrary": "lucide"
21+
}

0 commit comments

Comments
 (0)