diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..94483f7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,57 @@ +name: Deploy to Vercel + +on: + push: + branches: + - main + - master + paths: + - 'apps/docs/**' + pull_request: + branches: + - main + - master + paths: + - 'apps/docs/**' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: latest + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build docs + run: pnpm --filter docs build + + - name: Deploy to Vercel + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.ORG_ID }} + vercel-project-id: ${{ secrets.PROJECT_ID }} + working-directory: ./apps/docs + vercel-args: '--prod' + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + + - name: Deploy Preview to Vercel + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.ORG_ID }} + vercel-project-id: ${{ secrets.PROJECT_ID }} + working-directory: ./apps/docs + if: github.event_name == 'pull_request' diff --git a/.gitignore b/.gitignore index e7faeaf..935355d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -/.idea -/dist /node_modules -/public -/resources/img -/package-lock.json +pnpm-lock.yaml \ No newline at end of file diff --git a/.vercelignore b/.vercelignore new file mode 100644 index 0000000..336c5ff --- /dev/null +++ b/.vercelignore @@ -0,0 +1,38 @@ +# Dependencies +node_modules +.pnp +.pnp.js + +# Testing +coverage + +# Next.js +.next/ +out/ + +# Production +build/ +dist/ + +# Misc +.DS_Store +*.tsbuildinfo + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Vercel +.vercel + +# Other packages in monorepo +packages/ +plugins/ +!apps/docs/ diff --git a/apps/docs/.eslintrc.json b/apps/docs/.eslintrc.json new file mode 100644 index 0000000..e506540 --- /dev/null +++ b/apps/docs/.eslintrc.json @@ -0,0 +1,8 @@ +{ + "extends": "next/core-web-vitals", + "rules": { + "react-hooks/exhaustive-deps": "off", + "react-hooks/rules-of-hooks": "off", + "@next/next/no-img-element": "off" + } +} diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 0000000..f053e5b --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +package-lock.json \ No newline at end of file diff --git a/apps/docs/LICENSE b/apps/docs/LICENSE new file mode 100644 index 0000000..d475385 --- /dev/null +++ b/apps/docs/LICENSE @@ -0,0 +1,17 @@ +Creative Commons Attribution-NonCommercial 4.0 International +CC BY-NC 4.0 + +By using this work, you agree to the following terms: + +You are free to: +- **Share** — copy and redistribute the material in any medium or format +- **Adapt** — remix, transform, and build upon the material + +Under the following conditions: +- **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made. +- **NonCommercial** — You may not use the material for **commercial purposes**. + +No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. + +Full license text available at: +[creativecommons.org](https://creativecommons.org/licenses/by-nc/4.0/legalcode) \ No newline at end of file diff --git a/apps/docs/README.md b/apps/docs/README.md new file mode 100644 index 0000000..8a2d758 --- /dev/null +++ b/apps/docs/README.md @@ -0,0 +1,87 @@ +# Magic Docs + +Magic Docs by Once UI is a simple, modern, MDX-based documentation system built with Next.js. It automatically generates navigation based on the MDX files in the content directory. It comes with a built-in roadmap and changelog. + +View the demo [here](https://docs.once-ui.com). + +![Magic Docs](public/images/cover.jpg) + +## Getting started + +**1. Clone the repository** +``` +git clone https://github.com/once-ui-system/magic-docs.git +``` + +**2. Install dependencies** +``` +npm install +``` + +**3. Run dev server** +``` +npm run dev +``` + +**4. Edit config** +``` +src/resources/once-ui.config.js +``` + +**5. Create documentation pages** +``` +Add new .mdx files to src/content/ +``` + +Read the full documentation [here](https://docs.once-ui.com/magic-docs/quick-start). + +## Features + +### Once UI +- All tokens, components & features of [Once UI](https://once-ui.com) available through NPM + +### SEO +- Automatic open-graph and X image generation with next/og +- Automatic schema and metadata generation based on the content file + +### Pages +- Roadmap: when enabled, task progress is displayed in the homepage +- Changelog: when enabled, last changes are displayed in the homepage + +### Design +- Responsive layout optimized for all screen sizes +- Timeless design without heavy animations and motion +- Endless customization options through [Once UI](https://docs.once-ui.com/once-ui/contexts/themeProvider) +- Light and dark mode support with system preference detection + +### Navigation +- Organized documentation structure with nested categories +- Searchable content with command palette (Cmd+K / Ctrl+K) +- Automatically generated, responsive sidebar + +Magic Docs was built with [Once UI](https://once-ui.com) for [Next.js](https://nextjs.org). It requires Node.js v18.17+. + +## Creators + +Lorant One: [Threads](https://www.threads.net/@lorant.one) / [LinkedIn](https://www.linkedin.com/in/lorant-one/) + +## Get involved + +- Join the Design Engineers Club on [Discord](https://discord.com/invite/5EyAQ4eNdS) and share your project with us! +- Deployed your docs? Share it on the [Once UI Hub](https://once-ui.com/hub) too! We feature our favorite apps on our landing page. + +## Magic Docs + +This project is built with [Magic Docs](https://once-ui.com/products/magic-docs). Build your own documentation with Magic Docs for free! + +## License + +Distributed under the CC BY-NC 4.0 License. +- Attribution is required. +- Commercial usage is not allowed. +- You can extend the license to [Dopler CC](https://dopler.app/license) by purchasing a [Once UI Pro](https://once-ui.com/pricing) license. + +See `LICENSE.txt` for more information. + +## Deploy with Vercel +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fonce-ui-system%2Fmagic-docs&project-name=magic-docs&repository-name=magic-docs&redirect-url=https%3A%2F%2Fgithub.com%2Fonce-ui-system%2Fmagic-docs&demo-title=Magic%20Docs&demo-description=Showcase%20your%20designers%20or%20developer%20portfolio&demo-url=https%3A%2F%2Fdemo.magic-docs.com&demo-image=%2F%2Fraw.githubusercontent.com%2Fonce-ui-system%2Fmagic-docs%2Fmain%2Fpublic%2Fimages%2Fcover.jpg) \ No newline at end of file diff --git a/apps/docs/biome.json b/apps/docs/biome.json new file mode 100644 index 0000000..a46611a --- /dev/null +++ b/apps/docs/biome.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { "recommended": true } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs new file mode 100644 index 0000000..e3e9b64 --- /dev/null +++ b/apps/docs/next.config.mjs @@ -0,0 +1,64 @@ +/** @type {import('next').NextConfig} */ +import withMDX from '@next/mdx' + +const withMDXConfig = withMDX({ + extension: /\.mdx?$/, + options: { + remarkPlugins: [], + rehypePlugins: [], + }, +}) + +const nextConfig = { + sassOptions: { + compiler: "modern", + silenceDeprecations: ["legacy-js-api"], + }, + pageExtensions: ["ts", "tsx", "md", "mdx"], + transpilePackages: ["next-mdx-remote"], + output: 'standalone', + experimental: { + serverMinification: true, + serverActions: { + bodySizeLimit: '2mb', + }, + // Optimize package imports for react-icons + optimizePackageImports: ['react-icons'], + }, + // Configure image optimization + images: { + formats: ['image/avif', 'image/webp'], + remotePatterns: [ + { + protocol: 'https', + hostname: '**', + }, + ], + }, + // Reduce webpack cache size + webpack: (config, { dev, isServer }) => { + // Only enable source maps in development + if (!dev) { + config.devtool = false; + } + + // Optimize bundle size + config.optimization = { + ...config.optimization, + moduleIds: 'deterministic', + }; + + // Disable persistent caching in production to reduce size + if (!dev) { + config.cache = false; + } + + return config; + }, + // Disable webpack cache in production + generateBuildId: async () => { + return `build-${Date.now()}`; + }, +}; + +export default withMDXConfig(nextConfig); diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 0000000..9634faa --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,41 @@ +{ + "name": "@once-ui-system/docs", + "version": "2.0.0", + "scripts": { + "dev": "next dev", + "build": "rimraf .next/cache && next build", + "start": "next start", + "lint": "next lint", + "export": "next export", + "deploy": "vercel --prod", + "deploy:preview": "vercel" + }, + "dependencies": { + "@mdx-js/loader": "^3.1.0", + "@next/mdx": "^15.3.1", + "@once-ui-system/core": "^1.4.31", + "@once-ui-system/docs": "link:", + "classnames": "^2.5.1", + "eslint": "^9.21.0", + "eslint-config-next": "15.3.1", + "gray-matter": "^4.0.3", + "next": "15.3.1", + "next-mdx-remote": "^5.0.0", + "react": "19.0.0", + "react-dom": "19.0.0", + "react-icons": "^5.2.1", + "sass": "^1.77.6", + "sharp": "^0.33.4" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "19.0.1", + "@types/react-dom": "19.0.2", + "rimraf": "^6.0.1", + "typescript": "^5" + }, + "overrides": { + "@types/react": "19.0.1", + "@types/react-dom": "19.0.2" + } +} diff --git a/apps/docs/public/fonts/Inter.ttf b/apps/docs/public/fonts/Inter.ttf new file mode 100644 index 0000000..15e908f Binary files /dev/null and b/apps/docs/public/fonts/Inter.ttf differ diff --git a/apps/docs/public/images/cover.jpg b/apps/docs/public/images/cover.jpg new file mode 100644 index 0000000..d34e95e Binary files /dev/null and b/apps/docs/public/images/cover.jpg differ diff --git a/apps/docs/public/trademark/icon-dark.svg b/apps/docs/public/trademark/icon-dark.svg new file mode 100644 index 0000000..7751103 --- /dev/null +++ b/apps/docs/public/trademark/icon-dark.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/docs/public/trademark/icon-light.svg b/apps/docs/public/trademark/icon-light.svg new file mode 100644 index 0000000..7751103 --- /dev/null +++ b/apps/docs/public/trademark/icon-light.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/docs/src/app/(docs)/[...slug]/page.tsx b/apps/docs/src/app/(docs)/[...slug]/page.tsx new file mode 100644 index 0000000..b79d70a --- /dev/null +++ b/apps/docs/src/app/(docs)/[...slug]/page.tsx @@ -0,0 +1,157 @@ +import { notFound } from "next/navigation"; +import { getPages, getAdjacentPages } from "@/app/utils/utils"; +import { formatDate } from "@/app/utils/formatDate"; +import { Column, Heading, Icon, Row, Media, Text, Card, HeadingNav, Meta, Schema, Button } from "@once-ui-system/core"; +import { baseURL, layout, schema } from "@/resources"; +import { CustomMDX } from "@/product/mdx"; +import { Metadata } from "next"; +import React from "react"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string[] }>; +}): Promise { + const routeParams = await params; + const slugPath = routeParams.slug ? routeParams.slug.join('/') : ''; + + const docs = await getPages(); + const doc = docs.find((doc) => doc.slug === slugPath); + + if (!doc) return {}; + + return Meta.generate({ + title: doc.metadata.title + " – " + schema.name, + description: doc.metadata.summary, + baseURL, + path: `/${doc.slug}`, + type: "article", + publishedTime: doc.metadata.updatedAt, + image: doc.metadata.image || `/api/og/generate?title=${encodeURIComponent(doc.metadata.title)}&description=${encodeURIComponent(doc.metadata.summary)}`, + }); +} + +export default async function Docs({ + params, + }: { params: Promise<{ slug: string[] }> }) { + const routeParams = await params; + const slugPath = routeParams.slug.join('/'); + + let doc = getPages().find((doc) => doc.slug === slugPath); + + if (!doc) { + notFound(); + } + + const { prevPage, nextPage } = getAdjacentPages(slugPath, 'section'); + + // Determine section title - use "Docs" for top-level elements + const sectionTitle = routeParams.slug.length === 1 && !routeParams.slug[0].includes('/') + ? "Docs" + : routeParams.slug[0] + ?.split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + + return ( + <> + + + + + {sectionTitle} + {doc.metadata.title} + + Last update: {formatDate(doc.metadata.updatedAt)} + + {doc.metadata.github && ( + + )} + + {doc.metadata.image && ( + + )} + + + + + + {prevPage ? ( + + + + + + + {prevPage.slug.includes('/') ? + `${prevPage.slug.split('/')[0].split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}` : + 'page'} + + + {prevPage.metadata.title} + + + + + + ) : } + {nextPage ? ( + + + + + + {nextPage.slug.includes('/') ? + `${nextPage.slug.split('/')[0].split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}` : + 'page'} + + + {nextPage.metadata.title} + + + + + + + ) : } + + + + + + + On this page + + + + + ); +} \ No newline at end of file diff --git a/apps/docs/src/app/(docs)/layout.tsx b/apps/docs/src/app/(docs)/layout.tsx new file mode 100644 index 0000000..64997d2 --- /dev/null +++ b/apps/docs/src/app/(docs)/layout.tsx @@ -0,0 +1,16 @@ +import { Row } from "@once-ui-system/core"; +import { Sidebar } from "@/product"; +import React from "react"; + +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + ); +} \ No newline at end of file diff --git a/apps/docs/src/app/api/navigation/route.ts b/apps/docs/src/app/api/navigation/route.ts new file mode 100644 index 0000000..a74bdab --- /dev/null +++ b/apps/docs/src/app/api/navigation/route.ts @@ -0,0 +1,7 @@ +import getNavigation from "@/app/utils/getNavigation"; +import { NextResponse } from "next/server"; + +export async function GET() { + const navigation = getNavigation(); + return NextResponse.json(navigation); +} diff --git a/apps/docs/src/app/api/og/fetch/route.ts b/apps/docs/src/app/api/og/fetch/route.ts new file mode 100644 index 0000000..4287e50 --- /dev/null +++ b/apps/docs/src/app/api/og/fetch/route.ts @@ -0,0 +1,98 @@ +import { NextResponse } from 'next/server'; + +export const runtime = 'edge'; + +function decodeHTMLEntities(text: string): string { + return text.replace(/&(#?[a-zA-Z0-9]+);/g, (match, entity) => { + const entities: { [key: string]: string } = { + 'amp': '&', + 'lt': '<', + 'gt': '>', + 'quot': '"', + 'apos': "'", + '#x27': "'", + '#39': "'", + '#x26': '&', + '#38': '&' + }; + + if (entity.startsWith('#')) { + const code = entity.startsWith('#x') ? + parseInt(entity.slice(2), 16) : + parseInt(entity.slice(1), 10); + return String.fromCharCode(code); + } + + return entities[entity] || match; + }); +} + +async function fetchWithTimeout(url: string, timeout = 5000) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + try { + const response = await fetch(url, { + signal: controller.signal, + headers: { + 'User-Agent': 'bot' + } + }); + clearTimeout(timeoutId); + return response; + } catch (error) { + clearTimeout(timeoutId); + throw error; + } +} + +async function extractMetadata(html: string) { + const titleMatch = html.match(/]*>([^<]+)<\/title>/i); + const descMatch = html.match(/]*name="description"[^>]*content="([^"]+)"[^>]*>/i) + || html.match(/]*content="([^"]+)"[^>]*name="description"[^>]*>/i) + || html.match(/]*property="og:description"[^>]*content="([^"]+)"[^>]*>/i); + const imageMatch = html.match(/]*property="og:image"[^>]*content="([^"]+)"[^>]*>/i) + || html.match(/]*content="([^"]+)"[^>]*property="og:image"[^>]*>/i); + + const title = titleMatch?.[1]?.trim() || ''; + const description = descMatch?.[1]?.trim() || ''; + const image = imageMatch?.[1]?.trim() || ''; + + return { + title: decodeHTMLEntities(title), + description: decodeHTMLEntities(description), + image: image, + }; +} + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const url = searchParams.get('url'); + + if (!url) { + return NextResponse.json({ error: 'URL is required' }, { status: 400 }); + } + + try { + const response = await fetchWithTimeout(url); + + if (!response.ok) { + throw new Error(`Failed to fetch URL: ${response.status}`); + } + + const html = await response.text(); + const metadata = await extractMetadata(html); + + return NextResponse.json({ + ...metadata, + url, + }); + } catch (error) { + console.error('Error fetching metadata:', error instanceof Error ? error.message : String(error)); + + return NextResponse.json({ + error: 'Failed to fetch metadata', + message: error instanceof Error ? error.message : 'Unknown error occurred', + }, { status: 500 }); + } +} \ No newline at end of file diff --git a/apps/docs/src/app/api/og/generate/route.tsx b/apps/docs/src/app/api/og/generate/route.tsx new file mode 100644 index 0000000..ea43182 --- /dev/null +++ b/apps/docs/src/app/api/og/generate/route.tsx @@ -0,0 +1,133 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "edge"; + +export async function GET(request: Request) { + let url = new URL(request.url); + let title = url.searchParams.get("title") || "Documentation"; + let description = url.searchParams.get("description"); + const font = fetch(new URL("../../../../../public/fonts/Inter.ttf", import.meta.url)).then((res) => + res.arrayBuffer(), + ); + const fontData = await font; + + return new ImageResponse( +
+ {/* Horizontal lines */} +
+
+ + {/* Vertical lines */} +
+
+ +
+ + {title} + + {description && ( + + {description} + + )} +
+
+
+
+
+
, + { + width: 1920, + height: 1080, + fonts: [ + { + name: "Inter", + data: fontData, + style: "normal", + }, + ], + }, + ); +} diff --git a/apps/docs/src/app/api/og/proxy/route.ts b/apps/docs/src/app/api/og/proxy/route.ts new file mode 100644 index 0000000..738b767 --- /dev/null +++ b/apps/docs/src/app/api/og/proxy/route.ts @@ -0,0 +1,45 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + try { + // Get the URL parameter + const url = new URL(request.url); + const imageUrl = url.searchParams.get('url'); + + if (!imageUrl) { + return NextResponse.json({ error: 'Missing URL parameter' }, { status: 400 }); + } + + // Fetch the image + const response = await fetch(imageUrl, { + headers: { + 'User-Agent': 'Mozilla/5.0 (compatible; ImageProxy/1.0)', + }, + }); + + if (!response.ok) { + return NextResponse.json( + { error: `Failed to fetch image: ${response.status}` }, + { status: response.status } + ); + } + + // Get the image data + const contentType = response.headers.get('content-type') || 'image/jpeg'; + const imageData = await response.arrayBuffer(); + + // Return the image with appropriate headers + return new NextResponse(imageData, { + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=86400', + }, + }); + } catch (error) { + console.error('Error proxying image:', error); + return NextResponse.json( + { error: 'Failed to proxy image' }, + { status: 500 } + ); + } +} diff --git a/apps/docs/src/app/changelog/layout.tsx b/apps/docs/src/app/changelog/layout.tsx new file mode 100644 index 0000000..9e90788 --- /dev/null +++ b/apps/docs/src/app/changelog/layout.tsx @@ -0,0 +1,20 @@ +import { Row } from "@once-ui-system/core"; +import { Sidebar } from "@/product"; +import React, { memo } from "react"; + +const DocsLayout = memo(({ + children, +}: { + children: React.ReactNode; +}) => { + return ( + + + {children} + + ); +}); + +DocsLayout.displayName = 'ChangelogLayout'; + +export default DocsLayout; \ No newline at end of file diff --git a/apps/docs/src/app/changelog/page.tsx b/apps/docs/src/app/changelog/page.tsx new file mode 100644 index 0000000..03db8e9 --- /dev/null +++ b/apps/docs/src/app/changelog/page.tsx @@ -0,0 +1,130 @@ +import React from "react"; +import { Column, SmartLink, Row, Line, Text, Heading, Media, Meta, Schema, HeadingLink } from "@once-ui-system/core"; +import { baseURL, meta, schema, changelog } from "@/resources"; +import { formatDate } from "../utils/formatDate"; + +export async function generateMetadata() { + return Meta.generate({ + title: meta.changelog.title, + description: meta.changelog.description, + baseURL: baseURL, + path: meta.changelog.path, + image: meta.changelog.image + }); +} + +const Changelog: React.FC = () => { + return ( + + + + + Changelog + + + See what's new in Once UI + + + + {changelog.map((entry, index) => ( + + + + + {formatDate(entry.date)} + + {index < changelog.length - 1 && ( + + )} + + + + + {entry.title} + + + {entry.description && ( + + {entry.description} + + )} + + {entry.image && ( + + )} + + {entry.sections.map((section, sectionIndex) => { + return ( + + + {section.title} + + + {section.description && ( + + {section.description} + + )} + + {section.bullets && section.bullets.length > 0 && ( +
    + {section.bullets.map((bullet, bulletIndex) => ( +
  • + + {bullet} + +
  • + ))} +
+ )} + + {section.link && ( + + + View update + + + )} +
+ ); + })} +
+
+ ))} +
+ ); +}; + +export default Changelog; \ No newline at end of file diff --git a/apps/docs/src/app/error.tsx b/apps/docs/src/app/error.tsx new file mode 100644 index 0000000..25543c6 --- /dev/null +++ b/apps/docs/src/app/error.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { Column, Heading, Text, Button } from '@once-ui-system/core'; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( + + Dang! It's broken... + + An error occurred while rendering this page. This is what happened: + + + {error.message || 'Unknown error'} + + + + ); +} diff --git a/apps/docs/src/app/icon.ico b/apps/docs/src/app/icon.ico new file mode 100644 index 0000000..7751103 --- /dev/null +++ b/apps/docs/src/app/icon.ico @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx new file mode 100644 index 0000000..1c6dbcb --- /dev/null +++ b/apps/docs/src/app/layout.tsx @@ -0,0 +1,181 @@ +import '@once-ui-system/core/css/styles.css'; +import '@once-ui-system/core/css/tokens.css'; +import '@/resources/custom.css' + +import classNames from "classnames"; + +import { Footer, Header } from "@/product"; +import { baseURL } from "@/resources"; + +import { Background, Column, Flex, Meta } from "@once-ui-system/core"; +import { effects, fonts, layout, schema } from "../resources/once-ui.config"; +import { meta } from "@/resources"; +import { RouteGuard } from "@/product/RouteGuard"; +import { Providers } from '@/product/Providers'; + +export async function generateMetadata() { + const baseMetadata = Meta.generate({ + title: meta.home.title, + description: meta.home.description, + baseURL: baseURL, + path: meta.home.path, + image: meta.home.image + }); + + return { + ...baseMetadata, + metadataBase: new URL(`${baseURL}`), + openGraph: { + ...baseMetadata.openGraph, + siteName: meta.home.title, + locale: schema.locale, + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-video-preview": -1, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, + }; +} + +interface RootLayoutProps { + children: React.ReactNode; +} + +export default async function RootLayout({ children }: RootLayoutProps) { + return ( + <> + + +