diff --git a/Navigation/.gitignore b/Navigation/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/Navigation/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/Navigation/README.md b/Navigation/README.md new file mode 100644 index 0000000..5d866fe --- /dev/null +++ b/Navigation/README.md @@ -0,0 +1,116 @@ +# React PDF Viewer Sample + +A React + TypeScript proof-of-concept that demonstrates **programmatic navigation** in a PDF using the [Syncfusion React PDF Viewer](https://www.syncfusion.com/pdf-viewer-sdk/react-pdf-viewer) component. The app loads a sample PDF from Syncfusion's CDN and exposes a custom control panel for jumping between pages, navigating to bookmarks, and running text searches. + +--- + +## ✨ Features + +| Capability | How it works | +|---|---| +| **Page navigation** | Go to previous / next page, or jump to a specific page number. | +| **Bookmark navigation** | Retrieve all PDF bookmarks, open / close the bookmark panel, and jump to a selected destination. | +| **Text search** | Search the document for a term (with optional match-case), then move to the next or previous result. | +| **Zoom controls** | Fit to page, fit to width, zoom in, and zoom out. | + +--- + +## 📋 Prerequisites + +Make sure the following are installed on your machine: + +- [Node.js](https://nodejs.org/) (LTS recommended) +- [npm](https://www.npmjs.com/) (bundled with Node.js) + +> Verify your environment: +> ```bash +> node --version +> npm --version +> ``` + +--- + +## 🚀 Getting started + +### 1. Install dependencies + +```bash +npm install +``` + +### 2. Run the development server + +```bash +npm run dev +``` + +Vite will print a local URL (usually `http://localhost:5173`). Open it in your browser to see the PDF viewer. + +### 3. Build for production + +```bash +npm run build +``` + +The optimized output is written to the `dist/` folder. + +### 4. Preview the production build locally + +```bash +npm run preview +``` + +## 🧩 Project structure + +``` +pdf-viewer-app/ +├── index.html # Vite entry HTML +├── package.json # Scripts and dependencies +├── vite.config.ts # Vite configuration +├── tsconfig*.json # TypeScript configuration +├── public/ # Static assets served as-is +└── src/ + ├── main.tsx # React root bootstrap (StrictMode) + ├── App.tsx # PDF viewer + custom control panel + ├── App.css # Layout and styling for the control grid + ├── index.css # Global styles + └── assets/ # Local assets +``` + +--- + +## 🔍 How the sample works + +1. **`src/main.tsx`** mounts the React app inside `#root` using `StrictMode`. +2. **`src/App.tsx`** renders the `PdfViewerComponent` from `@syncfusion/ej2-react-pdfviewer` and wires up: + - A custom **control grid** with page, bookmark, search, and zoom controls. + - A **status banner** that reflects the latest action (e.g. *"5 bookmarks retrieved"*). + - **Event handlers** for `documentLoad`, `pageChange`, and `documentLoadFailed`. + +### Key Syncfusion APIs demonstrated + +| API | Purpose | +|---|---| +| `viewer.navigation.goToPage(n)` | Jump to a specific page | +| `viewer.navigation.goToNextPage()` / `goToPreviousPage()` | Step through pages | +| `viewer.bookmark.getBookmarks()` | Retrieve the bookmark tree | +| `viewer.bookmark.goToBookmark(pageIndex, y)` | Jump to a bookmark destination | +| `viewer.bookmark.openBookmarkPane()` / `closeBookmarkPane()` | Toggle the bookmark panel | +| `viewer.textSearch.searchText(term, isMatchCase)` | Run a text search | +| `viewer.textSearch.searchNext()` / `searchPrevious()` | Move between matches | +| `viewer.magnification.fitToPage()` / `fitToWidth()` / `zoomIn()` / `zoomOut()` | Adjust the view | + +The sample PDF and viewer resources are loaded from Syncfusion's public CDN: + +```ts +const SAMPLE_PDF = 'https://cdn.syncfusion.com/content/pdf/pdf-succinctly.pdf'; +const RESOURCE_URL = 'https://cdn.syncfusion.com/ej2/34.2.3/dist/ej2-pdfviewer-lib'; +``` + +--- + +## 🔗 Useful links + +- [Syncfusion React PDF Viewer — Overview](https://help.syncfusion.com/document-processing/pdf/pdf-viewer/react/overview) +- [Syncfusion React PDF Viewer — API reference](https://ej2.syncfusion.com/react/documentation/api/pdfviewer) + diff --git a/Navigation/eslint.config.js b/Navigation/eslint.config.js new file mode 100644 index 0000000..ef614d2 --- /dev/null +++ b/Navigation/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/Navigation/index.html b/Navigation/index.html new file mode 100644 index 0000000..110f0f9 --- /dev/null +++ b/Navigation/index.html @@ -0,0 +1,13 @@ + + + + + + + pdf-viewer-app + + +
+ + + diff --git a/Navigation/package.json b/Navigation/package.json new file mode 100644 index 0000000..1822fca --- /dev/null +++ b/Navigation/package.json @@ -0,0 +1,31 @@ +{ + "name": "pdf-viewer-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@syncfusion/ej2-react-pdfviewer": "^34.2.4", + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.13.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.4", + "eslint": "^10.8.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0" + } +} diff --git a/Navigation/public/favicon.svg b/Navigation/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/Navigation/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Navigation/public/icons.svg b/Navigation/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/Navigation/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Navigation/src/App.css b/Navigation/src/App.css new file mode 100644 index 0000000..f90339d --- /dev/null +++ b/Navigation/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/Navigation/src/App.tsx b/Navigation/src/App.tsx new file mode 100644 index 0000000..1846b60 --- /dev/null +++ b/Navigation/src/App.tsx @@ -0,0 +1,302 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { + BookmarkView, + Inject, + LinkAnnotation, + Magnification, + Navigation, + PdfViewerComponent, + Print, + TextSearch, + TextSelection, + ThumbnailView, + Toolbar, +} from '@syncfusion/ej2-react-pdfviewer'; + +const SAMPLE_PDF = 'https://cdn.syncfusion.com/content/pdf/pdf-succinctly.pdf'; +const RESOURCE_URL = 'https://cdn.syncfusion.com/ej2/34.2.3/dist/ej2-pdfviewer-lib'; + +interface BookmarkItem { + id: string; + title: string; + pageIndex: number; + y: number; + depth: number; +} + +interface BookmarkResult { + bookmarks?: { bookMark?: BookmarkItem[]; bookmark?: BookmarkItem[]; BookMark?: BookmarkItem[] }; + Bookmarks?: { bookMark?: BookmarkItem[]; bookmark?: BookmarkItem[]; BookMark?: BookmarkItem[] }; + bookmarksDestination?: Record; + BookmarksDestination?: Record; + [key: string]: unknown; +} + +function normalizeBookmarkResult(result: unknown): BookmarkItem[] { + if (!result) return []; + + const typedResult = result as BookmarkResult; + const bookmarkRoot = typedResult.bookmarks ?? typedResult.Bookmarks; + const roots = + bookmarkRoot?.bookMark ?? + bookmarkRoot?.bookmark ?? + bookmarkRoot?.BookMark ?? + bookmarkRoot ?? + (Array.isArray(result) ? result : []); + const destinationRoot = typedResult.bookmarksDestination ?? typedResult.BookmarksDestination ?? {}; + const destinations = + destinationRoot.bookMarkDestination ?? + destinationRoot.bookmarkDestination ?? + destinationRoot.BookMarkDestination ?? + destinationRoot; + + const flattened: BookmarkItem[] = []; + + function visit(nodes: unknown, depth: number = 0): void { + if (!Array.isArray(nodes)) return; + + nodes.forEach((node: unknown, index: number) => { + const typedNode = node as Record; + const id = typedNode.Id ?? typedNode.id ?? typedNode.BookmarkId ?? index; + const destination = (destinations as Record)?.[Number(id)] ?? + (destinations as Record)?.[id as string] ?? + typedNode.destination ?? + typedNode.Destination ?? + {}; + const destinationTyped = destination as Record; + const pageIndex = + destinationTyped.PageIndex ?? destinationTyped.pageIndex ?? typedNode.PageIndex ?? typedNode.pageIndex; + const y = destinationTyped.Y ?? destinationTyped.y ?? typedNode.Y ?? typedNode.y ?? 0; + + flattened.push({ + id: `${depth}-${id}-${flattened.length}`, + title: (typedNode.Title ?? typedNode.title ?? typedNode.Text ?? typedNode.text ?? `Bookmark ${flattened.length + 1}`) as string, + pageIndex: Number(pageIndex), + y: Number(y), + depth, + }); + + visit(typedNode.Child ?? typedNode.child ?? typedNode.Children ?? typedNode.children, depth + 1); + }); + } + + visit(roots); + return flattened.filter((bookmark) => Number.isFinite(bookmark.pageIndex)); +} + +export default function App() { + const viewerRef = useRef(null); + const [pageNumber, setPageNumber] = useState('1'); + const [searchText, setSearchText] = useState('PDF'); + const [bookmarks, setBookmarks] = useState([]); + const [selectedBookmark, setSelectedBookmark] = useState(''); + const [status, setStatus] = useState('Loading the sample PDF…'); + const [isBookmarkPaneOpen, setIsBookmarkPaneOpen] = useState(false); + const [isStatusVisible, setIsStatusVisible] = useState(true); + const [isMatchCase, setIsMatchCase] = useState(false); + + const selectedBookmarkData = useMemo( + () => bookmarks.find((item) => item.id === selectedBookmark), + [bookmarks, selectedBookmark], + ); + + const viewer = (): PdfViewerComponent | null => viewerRef.current; + + function updatePageStatus(): void { + const instance = viewer(); + if (!instance) return; + setPageNumber(String(instance.currentPageNumber || 1)); + setStatus(`Page ${instance.currentPageNumber || 1} of ${instance.pageCount || 0}`); + } + + function retrieveBookmarks(): void { + const result = viewer()?.bookmark?.getBookmarks(); + const items = normalizeBookmarkResult(result); + setBookmarks(items); + setSelectedBookmark(items[0]?.id ?? ''); + setStatus( + items.length + ? `${items.length} bookmark${items.length === 1 ? '' : 's'} retrieved.` + : 'This PDF does not expose any navigable bookmarks.', + ); + } + + function goToSelectedBookmark(): void { + if (!selectedBookmarkData) { + setStatus('Retrieve and select a bookmark first.'); + return; + } + + viewer()?.bookmark?.goToBookmark( + selectedBookmarkData.pageIndex, + selectedBookmarkData.y, + ); + setStatus(`Navigated to "${selectedBookmarkData.title}".`); + } + + function goToPage(): void { + const requestedPage = Number(pageNumber); + if (!Number.isInteger(requestedPage) || requestedPage < 1) { + setStatus('Enter a valid page number starting from 1.'); + return; + } + viewer()?.navigation?.goToPage(requestedPage); + } + + function startSearch(): void { + const term = searchText.trim(); + if (!term) { + setStatus('Enter text to search for.'); + return; + } + viewer()?.textSearch?.searchText(term, isMatchCase); + setStatus(`Searching for "${term}"${isMatchCase ? ' (case-sensitive)' : ''}.`); + } + + function toggleBookmarkPane(): void { + if (isBookmarkPaneOpen) { + viewer()?.bookmark?.closeBookmarkPane(); + setIsBookmarkPaneOpen(false); + setStatus('Bookmark panel closed.'); + } else { + viewer()?.bookmark?.openBookmarkPane(); + setIsBookmarkPaneOpen(true); + setStatus('Bookmark panel opened.'); + } + } + + return ( +
+
+
+

Syncfusion React PDF Viewer

+

Programmatic navigation POC

+

+ Navigate by page, bookmark destination, or text-search result using a custom React UI. +

+
+ + Product documentation + +
+ +
+
+ 01 +

Page navigation

+
+ + +
+
+ + setPageNumber(event.target.value)} /> + +
+
+ +
+ 02 +

Bookmark location

+
+ + +
+
+ + + +
+
+ +
+ 03 +

Search results

+
+ + setSearchText(event.target.value)} onKeyDown={(event) => event.key === 'Enter' && startSearch()} /> + +
+
+ { + console.log('Checkbox changed:', event.target.checked); + setIsMatchCase(event.target.checked); + }} + /> + +
+
+ + + +
+
+ +
+ 04 +

Presentation view

+
+ + + + +
+
+
+ + {isStatusVisible && ( +
+ {status} + +
+ )} + +
+ { + updatePageStatus(); + setTimeout(retrieveBookmarks, 200); + }} + pageChange={updatePageStatus} + documentLoadFailed={(args: unknown) => { + const typedArgs = args as Record | undefined; + setStatus(`Unable to load the PDF: ${typedArgs?.message ?? 'unknown error'}`); + }} + > + + +
+
+ ); +} diff --git a/Navigation/src/assets/hero.png b/Navigation/src/assets/hero.png new file mode 100644 index 0000000..02251f4 Binary files /dev/null and b/Navigation/src/assets/hero.png differ diff --git a/Navigation/src/assets/react.svg b/Navigation/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/Navigation/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Navigation/src/assets/vite.svg b/Navigation/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/Navigation/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/Navigation/src/index.css b/Navigation/src/index.css new file mode 100644 index 0000000..1d0226d --- /dev/null +++ b/Navigation/src/index.css @@ -0,0 +1,55 @@ +@import '@syncfusion/ej2-base/styles/tailwind3.css'; +@import '@syncfusion/ej2-buttons/styles/tailwind3.css'; +@import '@syncfusion/ej2-popups/styles/tailwind3.css'; +@import '@syncfusion/ej2-navigations/styles/tailwind3.css'; +@import '@syncfusion/ej2-splitbuttons/styles/tailwind3.css'; +@import '@syncfusion/ej2-inputs/styles/tailwind3.css'; +@import '@syncfusion/ej2-dropdowns/styles/tailwind3.css'; +@import '@syncfusion/ej2-lists/styles/tailwind3.css'; +@import '@syncfusion/ej2-react-pdfviewer/styles/tailwind3.css'; + +:root { + color: #172033; + background: #f2f5fa; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-synthesis: none; +} + +* { box-sizing: border-box; } +body { margin: 0; min-width: 320px; } +button, input, select { font: inherit; } +button { cursor: pointer; } + +.app-shell { width: min(1500px, calc(100% - 40px)); margin: 0 auto; padding: 42px 0 60px; } +.hero { display: flex; justify-content: space-between; align-items: end; gap: 24px; margin-bottom: 28px; } +.hero h1 { margin: 4px 0 8px; font-size: clamp(2rem, 4vw, 3.7rem); line-height: .98; letter-spacing: -.05em; } +.eyebrow { margin: 0; color: #5e38d3; font-weight: 800; text-transform: uppercase; letter-spacing: .14em; font-size: .75rem; } +.intro { max-width: 720px; margin: 0; color: #596176; font-size: 1.05rem; } +.hero a { color: #4e2cc0; font-weight: 700; white-space: nowrap; } + +.control-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; } +.control-card { min-height: 210px; padding: 20px; background: #fff; border: 1px solid #dfe4ee; border-radius: 16px; box-shadow: 0 8px 28px rgba(25, 35, 60, .05); } +.control-card h2 { margin: 8px 0 18px; font-size: 1.05rem; } +.step { color: #7051d6; font-weight: 800; font-size: .72rem; letter-spacing: .12em; } +.button-row, .field-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px; } +.field-row label { width: 100%; color: #596176; font-size: .78rem; font-weight: 700; } +.field-row input, .field-row select { flex: 1 1 90px; min-width: 0; height: 38px; border: 1px solid #cbd2df; border-radius: 8px; padding: 0 10px; background: #fff; } +.bookmark-row select { flex-basis: 150px; } +.control-card button { min-height: 38px; border: 1px solid #cbd2df; background: #fff; color: #263049; border-radius: 8px; padding: 7px 12px; font-weight: 700; } +.control-card button:hover { border-color: #7051d6; color: #4e2cc0; } +.control-card button.primary { border-color: #5e38d3; background: #5e38d3; color: #fff; } +.status { margin: 16px 0 10px; padding: 10px 14px; background: #e8e3fb; color: #3c248c; border-radius: 10px; font-weight: 700; font-size: .9rem; display: flex; justify-content: space-between; align-items: center; } +.close-btn { border: none; background: none; color: #3c248c; cursor: pointer; font-size: 1.2rem; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: background-color 0.2s; } +.close-btn:hover { background-color: rgba(60, 36, 140, 0.1); } +.checkbox-row { display: flex; align-items: center; margin-top: 10px; width: 100%; } +.checkbox-row input[type="checkbox"] { width: 18px; height: 18px; margin: 0 8px 0 0; cursor: pointer; border: 1px solid #cbd2df; border-radius: 4px; } +.checkbox-row label { width: auto; color: #596176; font-size: .78rem; font-weight: 700; margin: 0; display: flex; align-items: center; cursor: pointer; } +.viewer-frame { overflow: hidden; background: #fff; border: 1px solid #d8deea; border-radius: 16px; box-shadow: 0 18px 50px rgba(25, 35, 60, .11); margin-top: 20px;} + +@media (max-width: 1100px) { .control-grid { grid-template-columns: repeat(2, 1fr); } } +@media (max-width: 680px) { + .app-shell { width: min(100% - 24px, 1500px); padding-top: 24px; } + .hero { display: block; } + .hero a { display: inline-block; margin-top: 18px; } + .control-grid { grid-template-columns: 1fr; } +} diff --git a/Navigation/src/main.tsx b/Navigation/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/Navigation/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/Navigation/tsconfig.app.json b/Navigation/tsconfig.app.json new file mode 100644 index 0000000..6830b6f --- /dev/null +++ b/Navigation/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/Navigation/tsconfig.json b/Navigation/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/Navigation/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/Navigation/tsconfig.node.json b/Navigation/tsconfig.node.json new file mode 100644 index 0000000..8455dcb --- /dev/null +++ b/Navigation/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/Navigation/vite.config.ts b/Navigation/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/Navigation/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +})