diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index e2a83e3..0000000 --- a/.prettierrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tabWidth": 2, - "arrowParens": "avoid", - "bracketSameLine": true, - "bracketSpacing": false, - "singleQuote": true, - "trailingComma": "all", - "sqlKeywordCase": "lower", - "plugins": ["prettier-plugin-sql-cst"], - "overrides": [ - { - "files": "**/*.json", - "options": {"parser": "json"} - }, - { - "files": ["*.sql"], - "options": {"parser": "postgresql"} - } - ] -} diff --git a/apps/docs/package.json b/apps/docs/package.json index ff449f0..3907a24 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -6,7 +6,9 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "format-check": "prettier --check \"**/*.{ts,tsx,md}\"", + "format-fix": "prettier --write \"**/*.{ts,tsx,md}\"" }, "browserslist": "defaults, not ie <= 11", "dependencies": { @@ -42,6 +44,7 @@ "@next/eslint-plugin-next": "^14.2.3", "eslint-config-next": "latest", "eslint-plugin-next": "^0.0.0", + "postcss": "^8.4.38", "prettier": "^3.2.5", "sharp": "0.33.3" } diff --git a/apps/docs/prettier.config.js b/apps/docs/prettier.config.js index 6b16840..ff29114 100644 --- a/apps/docs/prettier.config.js +++ b/apps/docs/prettier.config.js @@ -1,6 +1,7 @@ +const root = require('../../prettier.config') + /** @type {import('prettier').Options} */ module.exports = { - singleQuote: true, - semi: false, + ...root, plugins: ['prettier-plugin-tailwindcss'], } diff --git a/apps/docs/src/app/docs/contributing/page.md b/apps/docs/src/app/docs/contributing/page.md new file mode 100644 index 0000000..e69de29 diff --git a/apps/docs/src/app/docs/getting-started/page.md b/apps/docs/src/app/docs/getting-started/page.md new file mode 100644 index 0000000..f15f130 --- /dev/null +++ b/apps/docs/src/app/docs/getting-started/page.md @@ -0,0 +1,141 @@ +--- +title: Getting started +--- + +Elwood has two integration modes. Pick which one is right for you! {% .lead %} + +{% quick-links %} + +{% quick-link title="Standalone" icon="installation" href="#standalone" description="Setup Elwood as a standalone Next.js application." /%} + +{% quick-link title="Embedded" icon="lightbulb" href="#embedded" description="Integrate Elwood into your existing React application." /%} + +{% /quick-links %} + +## Standalone + +In standalone mode, Elwood runs as standalone Next.js application & Supabase project. This is the easiest way to get started with Elwood. + +### Prerequisites + +You'll need the following installed on your machine: + +- Git +- Supabase CLI +- Node.js (>=v20) +- PNPM +- Deno (optional) + +### Clone the repository + +```bash +git clone https://github.com/elwood-software/elwood +``` + +### Install dependencies + +```bash +pnpm install +``` + +npm + +### Start Supabase + +```bash +supabase start +``` + +### Start Elwood + +```bash +pnpm dev +``` + +## Embedded + +You can integrate Elwood into your existing Next.js application. This is useful if you want to add Elwood to an existing project. + +### Install Elwood packages + +```bash +pnpm install @elwood/react @elwood/ui @elwood/js +``` + +### Add Elwood pages + +Elwood has a few pages you'll need to get your application up and running. + +#### Layout + +`app/elwood/(dashboard)/layout.tsx` + +```typescript +import {type PropsWithChildren} from 'react'; +import AuthPage from '@/app/(unauthenticated)/auth/page'; +import {RedirectType, redirect} from 'next/navigation'; +import {ElwoodThemeProvider} from '@elwood/ui'; + +export default async function Layout( + props: PropsWithChildren, +): Promise { + // + // !! CHANGE !! + // add your custom check for a user here + // Elwood does not provide authentication flow + // in Embedded mode + // + if (!user) { + redirect('/login', RedirectType.replace); + } + + return ( + +
{props.children}
+
+ ); +} + +``` + +#### Dashboard page + +`app/elwood/(dashboard)/[[...slug]]/page.tsx` + +```typescript +'use client'; + +import React, {useEffect, useState} from 'react'; +import { + Router, + createBrowserRouter, + dashboardRoutes, + type RouterProps, +} from '@elwood/react'; +import {type ElwoodClient, createClient} from '@elwood/js'; +import {Spinner} from '@elwood/ui'; + +export default function Page(): JSX.Element { + const [client, setClient] = useState(null); + const [router, setRouter] = useState(null); + + useEffect(() => { + setClient(createClient()); + setRouter( + createBrowserRouter(dashboardRoutes, { + basename: `/`, + }), + ); + }, []); + + if (!router || !client) { + return ; + } + + return ( + + + + ); +} +``` diff --git a/apps/docs/src/app/docs/page.md b/apps/docs/src/app/docs/page.md index 43cb1d2..b7983b3 100644 --- a/apps/docs/src/app/docs/page.md +++ b/apps/docs/src/app/docs/page.md @@ -1,112 +1,47 @@ --- -title: Getting started +title: Elwood Documentation --- -Learn how to get CacheAdvance set up in your project in under thirty minutes or it's free. {% .lead %} +The open source Dropbox alternative with Lighting fast, resumable uploads. Real-time, multi-user collaboration. Powerful role-based sharing. AI powered assistant (to the) file manager. {% .lead %} {% quick-links %} -{% quick-link title="Installation" icon="installation" href="/" description="Step-by-step guides to setting up your system and installing the library." /%} +{% quick-link title="Getting Started" icon="installation" href="/docs/getting-started" description="Step-by-step guides to setting up Elwood." /%} -{% quick-link title="Architecture guide" icon="presets" href="/" description="Learn how the internals work and contribute." /%} - -{% quick-link title="Plugins" icon="plugins" href="/" description="Extend the library with third-party plugins or write your own." /%} - -{% quick-link title="API reference" icon="theming" href="/" description="Learn to easily customize and modify your app's visual design to fit your brand." /%} +{% quick-link title="Support" icon="lightbulb" href="/docs/support" description="You've got Questions, we've got answers." /%} {% /quick-links %} -Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. - ---- - -## Quick start - -Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur. - -### Installing dependencies - -Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus. - -```shell -npm install @tailwindlabs/cache-advance -``` - -Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod. - -{% callout type="warning" title="Oh no! Something bad happened!" %} -This is what a disclaimer message looks like. You might want to include inline `code` in it. Or maybe you’ll want to include a [link](/) in it. I don’t think we should get too carried away with other scenarios like lists or tables — that would be silly. -{% /callout %} - -### Configuring the library - -Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus. - -```js -// cache-advance.config.js -export default { - strategy: 'predictive', - engine: { - cpus: 12, - backups: ['./storage/cache.wtf'], - }, -} -``` - -Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod. - {% callout title="You should know!" %} -This is what a disclaimer message looks like. You might want to include inline `code` in it. Or maybe you’ll want to include a [link](/) in it. I don’t think we should get too carried away with other scenarios like lists or tables — that would be silly. +Elwood is currently in public BETA. We are actively developing and improving the code & documentation. If you have any questions, please reach out to us at [hello@elwood.software](mailto:hello@elwood.software). {% /callout %} --- -## Basic usage - -Praesentium laudantium magni. Consequatur reiciendis aliquid nihil iusto ut in et. Quisquam ut et aliquid occaecati. Culpa veniam aut et voluptates amet perspiciatis. Qui exercitationem in qui. Vel qui dignissimos sit quae distinctio. - -### Your first cache - -Minima vel non iste debitis. Consequatur repudiandae et quod accusamus sit molestias consequatur aperiam. Et sequi ipsa eum voluptatibus ipsam. Et quisquam ut. - -Qui quae esse aspernatur fugit possimus. Quam sed molestiae temporibus. Eum perferendis dignissimos provident ea et. Et repudiandae quasi accusamus consequatur dolore nobis. Quia reiciendis necessitatibus a blanditiis iste quia. Ut quis et amet praesentium sapiente. - -Atque eos laudantium. Optio odit aspernatur consequuntur corporis soluta quidem sunt aut doloribus. Laudantium assumenda commodi. - -### Clearing the cache - -Vel aut velit sit dolor aut suscipit at veritatis voluptas. Laudantium tempore praesentium. Qui ut voluptatem. - -Ea est autem fugiat velit esse a alias earum. Dolore non amet soluta eos libero est. Consequatur qui aliquam qui odit eligendi ut impedit illo dignissimos. - -Ut dolore qui aut nam. Natus temporibus nisi voluptatum labore est ex error vel officia. Vero repellendus ut. Suscipit voluptate et placeat. Eius quo corporis ab et consequatur quisquam. Nihil officia facere dolorem occaecati alias deleniti deleniti in. - -### Adding middleware - -Officia nobis tempora maiores id iusto magni reprehenderit velit. Quae dolores inventore molestiae perspiciatis aut. Quis sequi officia quasi rem officiis officiis. Nesciunt ut cupiditate. Sunt aliquid explicabo enim ipsa eum recusandae. Vitae sunt eligendi et non beatae minima aut. - -Harum perferendis aut qui quibusdam tempore laboriosam voluptatum qui sed. Amet error amet totam exercitationem aut corporis accusantium dolorum. Perspiciatis aut animi et. Sed unde error ut aut rerum. - -Ut quo libero aperiam mollitia est repudiandae quaerat corrupti explicabo. Voluptas accusantium sed et doloribus voluptatem fugiat a mollitia. Numquam est magnam dolorem asperiores fugiat. Soluta et fuga amet alias temporibus quasi velit. Laudantium voluptatum perspiciatis doloribus quasi facere. Eveniet deleniti veniam et quia veritatis minus veniam perspiciatis. - ---- - -## Getting help - -Consequuntur et aut quisquam et qui consequatur eligendi. Necessitatibus dolorem sit. Excepturi cumque quibusdam soluta ullam rerum voluptatibus. Porro illo sequi consequatur nisi numquam nisi autem. Ut necessitatibus aut. Veniam ipsa voluptatem sed. - -### Submit an issue +## What is Elwood? -Inventore et aut minus ut voluptatem nihil commodi doloribus consequatur. Facilis perferendis nihil sit aut aspernatur iure ut dolores et. Aspernatur odit dignissimos. Aut qui est sint sint. +Elwood is an open source Dropbox alternative. -Facere aliquam qui. Dolorem officia ipsam adipisci qui molestiae. Error voluptatem reprehenderit ex. +- ✔️ Lighting fast, resumable uploads +- ✔️ Real-time, multi-user collaboration +- ✔️ Simple user management +- ✔️ File previews for images, videos, and documents (text, pdf, markdown) +- Public link sharing +- Role-based access control (RBAC) +- Unified search with external providers (S3, Dropbox, Box, Google Drive, etc) +- AI chat based file manager assistant +- Zero knowledge, end-to-end encrypted file storage +- Desktop app +- Mobile app -Consequatur enim quia maiores aperiam et ipsum dicta. Quam ut sit facere sit quae. Eligendi veritatis aut ut veritatis iste ut adipisci illo. +## Contribute -### Join the community +Want to contribute to Elwood? We'd love your help! Check out our [Contributing Guide](/docs/contributing) to get started. -Praesentium facilis iste aliquid quo quia a excepturi. Fuga reprehenderit illo sequi voluptatem voluptatem omnis. Id quia consequatur rerum consectetur eligendi et omnis. Voluptates iusto labore possimus provident praesentium id vel harum quisquam. Voluptatem provident corrupti. +## Follow Us -Eum et ut. Qui facilis est ipsa. Non facere quia sequi commodi autem. Dicta autem sit sequi omnis impedit. Eligendi amet dolorum magnam repudiandae in a. +Want to stay up to date on all the latest Elwood news and updates? -Molestiae iusto ut exercitationem dolorem unde iusto tempora atque nihil. Voluptatem velit facere laboriosam nobis ea. Consequatur rerum velit ipsum ipsam. Et qui saepe consequatur minima laborum tempore voluptatum et. Quia eveniet eaque sequi consequatur nihil eos. +- Watch us on [Github](https://github.com/elwood-software/elwood) +- Follow us on [Twitter](https://twitter.com/hello_elwood) +- Join us on [Discord](https://discord.gg/mkhKk5db) diff --git a/apps/docs/src/app/docs/support/page.md b/apps/docs/src/app/docs/support/page.md new file mode 100644 index 0000000..91402ac --- /dev/null +++ b/apps/docs/src/app/docs/support/page.md @@ -0,0 +1,17 @@ +--- +title: Support +--- + +We are here to help! Reach out with any questions, concerns, favorite "The Office" quotes, or just say "Hello!" {% .lead %} + +{% quick-links %} + +{% quick-link title="Forums" icon="lightbulb" href="https://github.com/orgs/elwood-software/discussions" description="The best place to get answers to all your questions" /%} + +{% quick-link title="Discord" icon="lightbulb" href="https://discord.gg/mkhKk5db" description="Join the community" /%} + +{% quick-link title="Bug Report" icon="lightbulb" href="https://github.com/elwood-software/elwood/issues" description="Found a bug... Open an Issue" /%} + +{% quick-link title="Email" icon="lightbulb" href="mailto:support@elwood.software" description="Old school is the best school" /%} + +{% /quick-links %} diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx index 0739f1c..6b5f570 100644 --- a/apps/docs/src/app/layout.tsx +++ b/apps/docs/src/app/layout.tsx @@ -1,51 +1,54 @@ -import { type Metadata } from 'next' -import { Inter } from 'next/font/google' -import localFont from 'next/font/local' -import clsx from 'clsx' +import {type Metadata} from 'next'; +import {Inter} from 'next/font/google'; +import localFont from 'next/font/local'; +import {cookies} from 'next/headers'; +import clsx from 'clsx'; -import { Providers } from '@/app/providers' -import { Layout } from '@/components/Layout' +import {Providers} from '@/app/providers'; +import {Layout} from '@/components/Layout'; -import '@/styles/tailwind.css' +import '@/styles/tailwind.css'; const inter = Inter({ subsets: ['latin'], display: 'swap', variable: '--font-inter', -}) +}); // Use local version of Lexend so that we can use OpenType features const lexend = localFont({ src: '../fonts/lexend.woff2', display: 'swap', variable: '--font-lexend', -}) +}); export const metadata: Metadata = { title: { template: '%s - Docs', - default: 'Elwood | Open Source Dropbox Alternative', + default: 'Elwood Documentation | Open Source Dropbox Alternative', }, description: 'Lighting fast, resumable uploads. Real-time, multi-user collaboration. Powerful role-based sharing (coming soon).', -} +}; + +export default function RootLayout({children}: {children: React.ReactNode}) { + const theme = cookies().get('system-theme')?.value ?? ''; + const validThemes = ['light', 'dark']; + const themeClassName = validThemes.includes(theme) ? theme : ''; -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { return ( - + suppressHydrationWarning> + {children} - ) + ); } diff --git a/apps/docs/src/app/not-found.tsx b/apps/docs/src/app/not-found.tsx index a41451d..f37628e 100644 --- a/apps/docs/src/app/not-found.tsx +++ b/apps/docs/src/app/not-found.tsx @@ -1,4 +1,4 @@ -import Link from 'next/link' +import Link from 'next/link'; export default function NotFound() { return ( @@ -15,11 +15,10 @@ export default function NotFound() {

+ className="mt-8 text-sm font-medium text-slate-900 dark:text-white"> Go back home - ) + ); } diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index 23aaa7a..565bad6 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -1,5 +1,5 @@ -import { RedirectType, redirect } from 'next/navigation' +import {RedirectType, redirect} from 'next/navigation'; export default function Page(): void { - redirect('https://github.com/elwood-software/elwood', RedirectType.replace) + redirect('/docs', RedirectType.replace); } diff --git a/apps/docs/src/app/providers.tsx b/apps/docs/src/app/providers.tsx index d16b648..749fc3f 100644 --- a/apps/docs/src/app/providers.tsx +++ b/apps/docs/src/app/providers.tsx @@ -1,11 +1,9 @@ -'use client' +'use client'; -import { ThemeProvider } from 'next-themes' +import {ThemeProvider} from 'next-themes'; -export function Providers({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) +import {ElwoodThemeProvider} from '@elwood/ui'; + +export function Providers({children}: {children: React.ReactNode}) { + return {children}; } diff --git a/apps/docs/src/components/Button.tsx b/apps/docs/src/components/Button.tsx index 068c104..eec8126 100644 --- a/apps/docs/src/components/Button.tsx +++ b/apps/docs/src/components/Button.tsx @@ -1,30 +1,30 @@ -import Link from 'next/link' -import clsx from 'clsx' +import Link from 'next/link'; +import clsx from 'clsx'; const variantStyles = { primary: 'rounded-full bg-sky-300 py-2 px-4 text-sm font-semibold text-slate-900 hover:bg-sky-200 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-300/50 active:bg-sky-500', secondary: 'rounded-full bg-slate-800 py-2 px-4 text-sm font-medium text-white hover:bg-slate-700 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400', -} +}; type ButtonProps = { - variant?: keyof typeof variantStyles + variant?: keyof typeof variantStyles; } & ( | React.ComponentPropsWithoutRef - | (React.ComponentPropsWithoutRef<'button'> & { href?: undefined }) -) + | (React.ComponentPropsWithoutRef<'button'> & {href?: undefined}) +); export function Button({ variant = 'primary', className, ...props }: ButtonProps) { - className = clsx(variantStyles[variant], className) + className = clsx(variantStyles[variant], className); return typeof props.href === 'undefined' ? ( @@ -80,15 +77,13 @@ export function MobileNavigation() { open={isOpen} onClose={() => close()} className="fixed inset-0 z-50 flex items-start overflow-y-auto bg-slate-900/50 pr-10 backdrop-blur lg:hidden" - aria-label="Navigation" - > - + aria-label="Navigation"> +
@@ -99,5 +94,5 @@ export function MobileNavigation() { - ) + ); } diff --git a/apps/docs/src/components/Navigation.tsx b/apps/docs/src/components/Navigation.tsx index be35de0..dcaef84 100644 --- a/apps/docs/src/components/Navigation.tsx +++ b/apps/docs/src/components/Navigation.tsx @@ -1,31 +1,30 @@ -import Link from 'next/link' -import { usePathname } from 'next/navigation' -import clsx from 'clsx' +import Link from 'next/link'; +import {usePathname} from 'next/navigation'; +import clsx from 'clsx'; -import { navigation } from '@/lib/navigation' +import {navigation} from '@/lib/navigation'; export function Navigation({ className, onLinkClick, }: { - className?: string - onLinkClick?: React.MouseEventHandler + className?: string; + onLinkClick?: React.MouseEventHandler; }) { - let pathname = usePathname() + let pathname = usePathname(); return ( - ) + ); } diff --git a/apps/docs/src/components/PrevNextLinks.tsx b/apps/docs/src/components/PrevNextLinks.tsx index 75d779d..0264ea1 100644 --- a/apps/docs/src/components/PrevNextLinks.tsx +++ b/apps/docs/src/components/PrevNextLinks.tsx @@ -1,17 +1,17 @@ -'use client' +'use client'; -import Link from 'next/link' -import { usePathname } from 'next/navigation' -import clsx from 'clsx' +import Link from 'next/link'; +import {usePathname} from 'next/navigation'; +import clsx from 'clsx'; -import { navigation } from '@/lib/navigation' +import {navigation} from '@/lib/navigation'; function ArrowIcon(props: React.ComponentPropsWithoutRef<'svg'>) { return ( - ) + ); } function PageLink({ @@ -20,9 +20,9 @@ function PageLink({ dir = 'next', ...props }: Omit, 'dir' | 'title'> & { - title: string - href: string - dir?: 'previous' | 'next' + title: string; + href: string; + dir?: 'previous' | 'next'; }) { return (
@@ -35,8 +35,7 @@ function PageLink({ className={clsx( 'flex items-center gap-x-1 text-base font-semibold text-slate-500 hover:text-slate-600 dark:text-slate-400 dark:hover:text-slate-300', dir === 'previous' && 'flex-row-reverse', - )} - > + )}> {title}
- ) + ); } export function PrevNextLinks() { - let pathname = usePathname() - let allLinks = navigation.flatMap((section) => section.links) - let linkIndex = allLinks.findIndex((link) => link.href === pathname) - let previousPage = linkIndex > -1 ? allLinks[linkIndex - 1] : null - let nextPage = linkIndex > -1 ? allLinks[linkIndex + 1] : null + let pathname = usePathname(); + let allLinks = navigation.flatMap(section => section.links); + let linkIndex = allLinks.findIndex(link => link.href === pathname); + let previousPage = linkIndex > -1 ? allLinks[linkIndex - 1] : null; + let nextPage = linkIndex > -1 ? allLinks[linkIndex + 1] : null; if (!nextPage && !previousPage) { - return null + return null; } return ( @@ -66,5 +65,5 @@ export function PrevNextLinks() { {previousPage && } {nextPage && } - ) + ); } diff --git a/apps/docs/src/components/Prose.tsx b/apps/docs/src/components/Prose.tsx index 678dcd7..229b9c4 100644 --- a/apps/docs/src/components/Prose.tsx +++ b/apps/docs/src/components/Prose.tsx @@ -1,13 +1,13 @@ -import clsx from 'clsx' +import clsx from 'clsx'; export function Prose({ as, className, ...props }: React.ComponentPropsWithoutRef & { - as?: T + as?: T; }) { - let Component = as ?? 'div' + let Component = as ?? 'div'; return ( ({ )} {...props} /> - ) + ); } diff --git a/apps/docs/src/components/QuickLinks.tsx b/apps/docs/src/components/QuickLinks.tsx index 0970cae..e7d1f2b 100644 --- a/apps/docs/src/components/QuickLinks.tsx +++ b/apps/docs/src/components/QuickLinks.tsx @@ -1,13 +1,13 @@ -import Link from 'next/link' +import Link from 'next/link'; -import { Icon } from '@/components/Icon' +import {Icon} from '@/components/Icon'; -export function QuickLinks({ children }: { children: React.ReactNode }) { +export function QuickLinks({children}: {children: React.ReactNode}) { return (
{children}
- ) + ); } export function QuickLink({ @@ -16,10 +16,10 @@ export function QuickLink({ href, icon, }: { - title: string - description: string - href: string - icon: React.ComponentProps['icon'] + title: string; + description: string; + href: string; + icon: React.ComponentProps['icon']; }) { return (
@@ -37,5 +37,5 @@ export function QuickLink({

- ) + ); } diff --git a/apps/docs/src/components/Search.tsx b/apps/docs/src/components/Search.tsx index 141779d..220a950 100644 --- a/apps/docs/src/components/Search.tsx +++ b/apps/docs/src/components/Search.tsx @@ -1,4 +1,4 @@ -'use client' +'use client'; import { forwardRef, @@ -9,61 +9,61 @@ import { useId, useRef, useState, -} from 'react' -import Highlighter from 'react-highlight-words' -import { usePathname, useRouter, useSearchParams } from 'next/navigation' +} from 'react'; +import Highlighter from 'react-highlight-words'; +import {usePathname, useRouter, useSearchParams} from 'next/navigation'; import { type AutocompleteApi, type AutocompleteCollection, type AutocompleteState, createAutocomplete, -} from '@algolia/autocomplete-core' -import { Dialog } from '@headlessui/react' -import clsx from 'clsx' -import { type Result } from '@/markdoc/search.mjs' +} from '@algolia/autocomplete-core'; +import {Dialog} from '@headlessui/react'; +import clsx from 'clsx'; +import {type Result} from '@/markdoc/search.mjs'; -import { navigation } from '@/lib/navigation' +import {navigation} from '@/lib/navigation'; -type EmptyObject = Record +type EmptyObject = Record; type Autocomplete = AutocompleteApi< Result, React.SyntheticEvent, React.MouseEvent, React.KeyboardEvent -> +>; function SearchIcon(props: React.ComponentPropsWithoutRef<'svg'>) { return ( - ) + ); } function useAutocomplete({ close, }: { - close: (autocomplete: Autocomplete) => void + close: (autocomplete: Autocomplete) => void; }) { - let id = useId() - let router = useRouter() + let id = useId(); + let router = useRouter(); let [autocompleteState, setAutocompleteState] = useState< AutocompleteState | EmptyObject - >({}) + >({}); - function navigate({ itemUrl }: { itemUrl?: string }) { + function navigate({itemUrl}: {itemUrl?: string}) { if (!itemUrl) { - return + return; } - router.push(itemUrl) + router.push(itemUrl); if ( itemUrl === window.location.pathname + window.location.search + window.location.hash ) { - close(autocomplete) + close(autocomplete); } } @@ -77,39 +77,39 @@ function useAutocomplete({ id, placeholder: 'Find something...', defaultActiveItemId: 0, - onStateChange({ state }) { - setAutocompleteState(state) + onStateChange({state}) { + setAutocompleteState(state); }, - shouldPanelOpen({ state }) { - return state.query !== '' + shouldPanelOpen({state}) { + return state.query !== ''; }, navigator: { navigate, }, - getSources({ query }) { - return import('@/markdoc/search.mjs').then(({ search }) => { + getSources({query}) { + return import('@/markdoc/search.mjs').then(({search}) => { return [ { sourceId: 'documentation', getItems() { - return search(query, { limit: 5 }) + return search(query, {limit: 5}); }, - getItemUrl({ item }) { - return item.url + getItemUrl({item}) { + return item.url; }, onSelect: navigate, }, - ] - }) + ]; + }); }, }), - ) + ); - return { autocomplete, autocompleteState } + return {autocomplete, autocompleteState}; } function LoadingIcon(props: React.ComponentPropsWithoutRef<'svg'>) { - let id = useId() + let id = useId(); return ( - ) + ); } -function HighlightQuery({ text, query }: { text: string; query: string }) { +function HighlightQuery({text, query}: {text: string; query: string}) { return ( - ) + ); } function SearchResult({ @@ -154,19 +153,19 @@ function SearchResult({ collection, query, }: { - result: Result - autocomplete: Autocomplete - collection: AutocompleteCollection - query: string + result: Result; + autocomplete: Autocomplete; + collection: AutocompleteCollection; + query: string; }) { - let id = useId() + let id = useId(); - let sectionTitle = navigation.find((section) => - section.links.find((link) => link.href === result.url.split('#')[0]), - )?.title + let sectionTitle = navigation.find(section => + section.links.find(link => link.href === result.url.split('#')[0]), + )?.title; let hierarchy = [sectionTitle, result.pageTitle].filter( (x): x is string => typeof x === 'string', - ) + ); return (
  • + })}> {hierarchy.length > 0 && ( )}
  • - ) + ); } function SearchResults({ @@ -215,9 +210,9 @@ function SearchResults({ query, collection, }: { - autocomplete: Autocomplete - query: string - collection: AutocompleteCollection + autocomplete: Autocomplete; + query: string; + collection: AutocompleteCollection; }) { if (collection.items.length === 0) { return ( @@ -228,12 +223,12 @@ function SearchResults({ ”

    - ) + ); } return (
      - {collection.items.map((result) => ( + {collection.items.map(result => ( ))}
    - ) + ); } const SearchInput = forwardRef< React.ElementRef<'input'>, { - autocomplete: Autocomplete - autocompleteState: AutocompleteState | EmptyObject - onClose: () => void + autocomplete: Autocomplete; + autocompleteState: AutocompleteState | EmptyObject; + onClose: () => void; } ->(function SearchInput({ autocomplete, autocompleteState, onClose }, inputRef) { - let inputProps = autocomplete.getInputProps({ inputElement: null }) +>(function SearchInput({autocomplete, autocompleteState, onClose}, inputRef) { + let inputProps = autocomplete.getInputProps({inputElement: null}); return (
    @@ -262,11 +257,11 @@ const SearchInput = forwardRef< { + onKeyDown={event => { if ( event.key === 'Escape' && !autocompleteState.isOpen && @@ -275,12 +270,12 @@ const SearchInput = forwardRef< // In Safari, closing the dialog with the escape key can sometimes cause the scroll position to jump to the // bottom of the page. This is a workaround for that until we can figure out a proper fix in Headless UI. if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur() + document.activeElement.blur(); } - onClose() + onClose(); } else { - inputProps.onKeyDown(event) + inputProps.onKeyDown(event); } }} /> @@ -290,24 +285,24 @@ const SearchInput = forwardRef<
    )} - ) -}) + ); +}); function CloseOnNavigation({ close, autocomplete, }: { - close: (autocomplete: Autocomplete) => void - autocomplete: Autocomplete + close: (autocomplete: Autocomplete) => void; + autocomplete: Autocomplete; }) { - let pathname = usePathname() - let searchParams = useSearchParams() + let pathname = usePathname(); + let searchParams = useSearchParams(); useEffect(() => { - close(autocomplete) - }, [pathname, searchParams, close, autocomplete]) + close(autocomplete); + }, [pathname, searchParams, close, autocomplete]); - return null + return null; } function SearchDialog({ @@ -315,46 +310,46 @@ function SearchDialog({ setOpen, className, }: { - open: boolean - setOpen: (open: boolean) => void - className?: string + open: boolean; + setOpen: (open: boolean) => void; + className?: string; }) { - let formRef = useRef>(null) - let panelRef = useRef>(null) - let inputRef = useRef>(null) + let formRef = useRef>(null); + let panelRef = useRef>(null); + let inputRef = useRef>(null); let close = useCallback( (autocomplete: Autocomplete) => { - setOpen(false) - autocomplete.setQuery('') + setOpen(false); + autocomplete.setQuery(''); }, [setOpen], - ) + ); - let { autocomplete, autocompleteState } = useAutocomplete({ + let {autocomplete, autocompleteState} = useAutocomplete({ close() { - close(autocomplete) + close(autocomplete); }, - }) + }); useEffect(() => { if (open) { - return + return; } function onKeyDown(event: KeyboardEvent) { if (event.key === 'k' && (event.metaKey || event.ctrlKey)) { - event.preventDefault() - setOpen(true) + event.preventDefault(); + setOpen(true); } } - window.addEventListener('keydown', onKeyDown) + window.addEventListener('keydown', onKeyDown); return () => { - window.removeEventListener('keydown', onKeyDown) - } - }, [open, setOpen]) + window.removeEventListener('keydown', onKeyDown); + }; + }, [open, setOpen]); return ( <> @@ -364,19 +359,17 @@ function SearchDialog({ close(autocomplete)} - className={clsx('fixed inset-0 z-50', className)} - > + className={clsx('fixed inset-0 z-50', className)}>
    - +
    + })}> + {...autocomplete.getPanelProps({})}> {autocompleteState.isOpen && (
    - ) + ); } function useSearchProps() { - let buttonRef = useRef>(null) - let [open, setOpen] = useState(false) + let buttonRef = useRef>(null); + let [open, setOpen] = useState(false); return { buttonProps: { ref: buttonRef, onClick() { - setOpen(true) + setOpen(true); }, }, dialogProps: { open, setOpen: useCallback((open: boolean) => { - let { width = 0, height = 0 } = - buttonRef.current?.getBoundingClientRect() ?? {} + let {width = 0, height = 0} = + buttonRef.current?.getBoundingClientRect() ?? {}; if (!open || (width !== 0 && height !== 0)) { - setOpen(open) + setOpen(open); } }, []), }, - } + }; } export function Search() { - let [modifierKey, setModifierKey] = useState() - let { buttonProps, dialogProps } = useSearchProps() + let [modifierKey, setModifierKey] = useState(); + let {buttonProps, dialogProps} = useSearchProps(); useEffect(() => { setModifierKey( /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl ', - ) - }, []) + ); + }, []); return ( <> - ) + ); } diff --git a/apps/docs/src/components/TableOfContents.tsx b/apps/docs/src/components/TableOfContents.tsx index 2a8ce2e..06f0903 100644 --- a/apps/docs/src/components/TableOfContents.tsx +++ b/apps/docs/src/components/TableOfContents.tsx @@ -1,64 +1,64 @@ -'use client' +'use client'; -import { useCallback, useEffect, useState } from 'react' -import Link from 'next/link' -import clsx from 'clsx' +import {useCallback, useEffect, useState} from 'react'; +import Link from 'next/link'; +import clsx from 'clsx'; -import { type Section, type Subsection } from '@/lib/sections' +import {type Section, type Subsection} from '@/lib/sections'; export function TableOfContents({ tableOfContents, }: { - tableOfContents: Array
    + tableOfContents: Array
    ; }) { - let [currentSection, setCurrentSection] = useState(tableOfContents[0]?.id) + let [currentSection, setCurrentSection] = useState(tableOfContents[0]?.id); let getHeadings = useCallback((tableOfContents: Array
    ) => { return tableOfContents - .flatMap((node) => [node.id, ...node.children.map((child) => child.id)]) - .map((id) => { - let el = document.getElementById(id) - if (!el) return null + .flatMap(node => [node.id, ...node.children.map(child => child.id)]) + .map(id => { + let el = document.getElementById(id); + if (!el) return null; - let style = window.getComputedStyle(el) - let scrollMt = parseFloat(style.scrollMarginTop) + let style = window.getComputedStyle(el); + let scrollMt = parseFloat(style.scrollMarginTop); - let top = window.scrollY + el.getBoundingClientRect().top - scrollMt - return { id, top } + let top = window.scrollY + el.getBoundingClientRect().top - scrollMt; + return {id, top}; }) - .filter((x): x is { id: string; top: number } => x !== null) - }, []) + .filter((x): x is {id: string; top: number} => x !== null); + }, []); useEffect(() => { - if (tableOfContents.length === 0) return - let headings = getHeadings(tableOfContents) + if (tableOfContents.length === 0) return; + let headings = getHeadings(tableOfContents); function onScroll() { - let top = window.scrollY - let current = headings[0].id + let top = window.scrollY; + let current = headings[0].id; for (let heading of headings) { if (top >= heading.top - 10) { - current = heading.id + current = heading.id; } else { - break + break; } } - setCurrentSection(current) + setCurrentSection(current); } - window.addEventListener('scroll', onScroll, { passive: true }) - onScroll() + window.addEventListener('scroll', onScroll, {passive: true}); + onScroll(); return () => { - window.removeEventListener('scroll', onScroll) - } - }, [getHeadings, tableOfContents]) + window.removeEventListener('scroll', onScroll); + }; + }, [getHeadings, tableOfContents]); function isActive(section: Section | Subsection) { if (section.id === currentSection) { - return true + return true; } if (!section.children) { - return false + return false; } - return section.children.findIndex(isActive) > -1 + return section.children.findIndex(isActive) > -1; } return ( @@ -68,12 +68,11 @@ export function TableOfContents({ <>

    + className="font-display text-sm font-medium text-slate-900 dark:text-white"> On this page

      - {tableOfContents.map((section) => ( + {tableOfContents.map(section => (
    1. + )}> {section.title}

      {section.children.length > 0 && (
        - {section.children.map((subSection) => ( + className="mt-2 space-y-3 pl-5 text-slate-500 dark:text-slate-400"> + {section.children.map(subSection => (
      1. + }> {subSection.title}
      2. @@ -115,5 +111,5 @@ export function TableOfContents({ )} - ) + ); } diff --git a/apps/docs/src/components/ThemeSelector.tsx b/apps/docs/src/components/ThemeSelector.tsx index 1a09c6e..1e043b6 100644 --- a/apps/docs/src/components/ThemeSelector.tsx +++ b/apps/docs/src/components/ThemeSelector.tsx @@ -1,13 +1,13 @@ -import { useEffect, useState } from 'react' -import { useTheme } from 'next-themes' -import { Listbox } from '@headlessui/react' -import clsx from 'clsx' +import {useEffect, useState} from 'react'; +import {useTheme} from 'next-themes'; +import {Listbox} from '@headlessui/react'; +import clsx from 'clsx'; const themes = [ - { name: 'Light', value: 'light', icon: LightIcon }, - { name: 'Dark', value: 'dark', icon: DarkIcon }, - { name: 'System', value: 'system', icon: SystemIcon }, -] + {name: 'Light', value: 'light', icon: LightIcon}, + {name: 'Dark', value: 'dark', icon: DarkIcon}, + {name: 'System', value: 'system', icon: SystemIcon}, +]; function LightIcon(props: React.ComponentPropsWithoutRef<'svg'>) { return ( @@ -18,7 +18,7 @@ function LightIcon(props: React.ComponentPropsWithoutRef<'svg'>) { d="M7 1a1 1 0 0 1 2 0v1a1 1 0 1 1-2 0V1Zm4 7a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm2.657-5.657a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm-1.415 11.313-.707-.707a1 1 0 0 1 1.415-1.415l.707.708a1 1 0 0 1-1.415 1.414ZM16 7.999a1 1 0 0 0-1-1h-1a1 1 0 1 0 0 2h1a1 1 0 0 0 1-1ZM7 14a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1Zm-2.536-2.464a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm0-8.486A1 1 0 0 1 3.05 4.464l-.707-.707a1 1 0 0 1 1.414-1.414l.707.707ZM3 8a1 1 0 0 0-1-1H1a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1Z" /> - ) + ); } function DarkIcon(props: React.ComponentPropsWithoutRef<'svg'>) { @@ -30,7 +30,7 @@ function DarkIcon(props: React.ComponentPropsWithoutRef<'svg'>) { d="M7.23 3.333C7.757 2.905 7.68 2 7 2a6 6 0 1 0 0 12c.68 0 .758-.905.23-1.332A5.989 5.989 0 0 1 5 8c0-1.885.87-3.568 2.23-4.668ZM12 5a1 1 0 0 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 0 2 1 1 0 0 0-1 1 1 1 0 1 1-2 0 1 1 0 0 0-1-1 1 1 0 1 1 0-2 1 1 0 0 0 1-1 1 1 0 0 1 1-1Z" /> - ) + ); } function SystemIcon(props: React.ComponentPropsWithoutRef<'svg'>) { @@ -42,21 +42,21 @@ function SystemIcon(props: React.ComponentPropsWithoutRef<'svg'>) { d="M1 4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3h-1.5l.31 1.242c.084.333.36.573.63.808.091.08.182.158.264.24A1 1 0 0 1 11 15H5a1 1 0 0 1-.704-1.71c.082-.082.173-.16.264-.24.27-.235.546-.475.63-.808L5.5 11H4a3 3 0 0 1-3-3V4Zm3-1a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Z" /> - ) + ); } export function ThemeSelector( props: React.ComponentPropsWithoutRef>, ) { - let { theme, setTheme } = useTheme() - let [mounted, setMounted] = useState(false) + let {theme, setTheme} = useTheme(); + let [mounted, setMounted] = useState(false); useEffect(() => { - setMounted(true) - }, []) + setMounted(true); + }, []); if (!mounted) { - return
        + return
        ; } return ( @@ -64,8 +64,7 @@ export function ThemeSelector( Theme + aria-label="Theme"> - {themes.map((theme) => ( + {themes.map(theme => ( + className={({active, selected}) => clsx( 'flex cursor-pointer select-none items-center rounded-[0.625rem] p-1', { @@ -94,9 +93,8 @@ export function ThemeSelector( 'bg-slate-100 dark:bg-slate-900/40': active, }, ) - } - > - {({ selected }) => ( + }> + {({selected}) => ( <>
        - ) + ); } diff --git a/apps/docs/src/components/icons/InstallationIcon.tsx b/apps/docs/src/components/icons/InstallationIcon.tsx index c81a5e3..947230b 100644 --- a/apps/docs/src/components/icons/InstallationIcon.tsx +++ b/apps/docs/src/components/icons/InstallationIcon.tsx @@ -1,11 +1,11 @@ -import { DarkMode, Gradient, LightMode } from '@/components/Icon' +import {DarkMode, Gradient, LightMode} from '@/components/Icon'; export function InstallationIcon({ id, color, }: { - id: string - color?: React.ComponentProps['color'] + id: string; + color?: React.ComponentProps['color']; }) { return ( <> @@ -43,5 +43,5 @@ export function InstallationIcon({ /> - ) + ); } diff --git a/apps/docs/src/components/icons/LightbulbIcon.tsx b/apps/docs/src/components/icons/LightbulbIcon.tsx index 7367f51..5de9007 100644 --- a/apps/docs/src/components/icons/LightbulbIcon.tsx +++ b/apps/docs/src/components/icons/LightbulbIcon.tsx @@ -1,11 +1,11 @@ -import { DarkMode, Gradient, LightMode } from '@/components/Icon' +import {DarkMode, Gradient, LightMode} from '@/components/Icon'; export function LightbulbIcon({ id, color, }: { - id: string - color?: React.ComponentProps['color'] + id: string; + color?: React.ComponentProps['color']; }) { return ( <> @@ -48,5 +48,5 @@ export function LightbulbIcon({ /> - ) + ); } diff --git a/apps/docs/src/components/icons/PluginsIcon.tsx b/apps/docs/src/components/icons/PluginsIcon.tsx index 2caf217..2f1714e 100644 --- a/apps/docs/src/components/icons/PluginsIcon.tsx +++ b/apps/docs/src/components/icons/PluginsIcon.tsx @@ -1,11 +1,11 @@ -import { DarkMode, Gradient, LightMode } from '@/components/Icon' +import {DarkMode, Gradient, LightMode} from '@/components/Icon'; export function PluginsIcon({ id, color, }: { - id: string - color?: React.ComponentProps['color'] + id: string; + color?: React.ComponentProps['color']; }) { return ( <> @@ -33,8 +33,7 @@ export function PluginsIcon({ className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]" strokeWidth={2} strokeLinecap="round" - strokeLinejoin="round" - > + strokeLinejoin="round"> @@ -47,8 +46,7 @@ export function PluginsIcon({ className="stroke-[color:var(--icon-foreground)]" strokeWidth={2} strokeLinecap="round" - strokeLinejoin="round" - > + strokeLinejoin="round"> @@ -65,5 +63,5 @@ export function PluginsIcon({ /> - ) + ); } diff --git a/apps/docs/src/components/icons/PresetsIcon.tsx b/apps/docs/src/components/icons/PresetsIcon.tsx index e01b953..0dc7e5d 100644 --- a/apps/docs/src/components/icons/PresetsIcon.tsx +++ b/apps/docs/src/components/icons/PresetsIcon.tsx @@ -1,11 +1,11 @@ -import { DarkMode, Gradient, LightMode } from '@/components/Icon' +import {DarkMode, Gradient, LightMode} from '@/components/Icon'; export function PresetsIcon({ id, color, }: { - id: string - color?: React.ComponentProps['color'] + id: string; + color?: React.ComponentProps['color']; }) { return ( <> @@ -28,8 +28,7 @@ export function PresetsIcon({ fillOpacity={0.5} strokeWidth={2} strokeLinecap="round" - strokeLinejoin="round" - > + strokeLinejoin="round"> @@ -45,5 +44,5 @@ export function PresetsIcon({ - ) + ); } diff --git a/apps/docs/src/components/icons/ThemingIcon.tsx b/apps/docs/src/components/icons/ThemingIcon.tsx index 02876f7..252de0d 100644 --- a/apps/docs/src/components/icons/ThemingIcon.tsx +++ b/apps/docs/src/components/icons/ThemingIcon.tsx @@ -1,11 +1,11 @@ -import { DarkMode, Gradient, LightMode } from '@/components/Icon' +import {DarkMode, Gradient, LightMode} from '@/components/Icon'; export function ThemingIcon({ id, color, }: { - id: string - color?: React.ComponentProps['color'] + id: string; + color?: React.ComponentProps['color']; }) { return ( <> @@ -61,5 +61,5 @@ export function ThemingIcon({ /> - ) + ); } diff --git a/apps/docs/src/components/icons/WarningIcon.tsx b/apps/docs/src/components/icons/WarningIcon.tsx index abed5e3..f43d352 100644 --- a/apps/docs/src/components/icons/WarningIcon.tsx +++ b/apps/docs/src/components/icons/WarningIcon.tsx @@ -1,11 +1,11 @@ -import { DarkMode, Gradient, LightMode } from '@/components/Icon' +import {DarkMode, Gradient, LightMode} from '@/components/Icon'; export function WarningIcon({ id, color, }: { - id: string - color?: React.ComponentProps['color'] + id: string; + color?: React.ComponentProps['color']; }) { return ( <> @@ -57,5 +57,5 @@ export function WarningIcon({ /> - ) + ); } diff --git a/apps/docs/src/lib/navigation.ts b/apps/docs/src/lib/navigation.ts index 80b01b6..de038a5 100644 --- a/apps/docs/src/lib/navigation.ts +++ b/apps/docs/src/lib/navigation.ts @@ -1,9 +1,19 @@ export const navigation = [ { - title: 'Introduction', + title: 'Docs', links: [ - { title: 'Getting started', href: '/' }, - { title: 'Installation', href: '/docs/installation' }, + {title: 'Introduction', href: '/docs/'}, + {title: 'Getting started', href: '/docs/getting-started'}, + {title: 'Support', href: '/docs/support'}, + {title: 'Contributing', href: '/docs/contributing'}, ], }, -] + { + title: 'Follow us', + links: [ + {title: 'Github', href: 'https://github.com/elwood-software/elwood'}, + {title: 'Twitter', href: 'https://twitter.com/hello_elwood'}, + {title: 'Discord', href: 'https://discord.gg/mkhKk5db'}, + ], + }, +]; diff --git a/apps/docs/src/lib/sections.ts b/apps/docs/src/lib/sections.ts index afb1bfe..d9d31db 100644 --- a/apps/docs/src/lib/sections.ts +++ b/apps/docs/src/lib/sections.ts @@ -1,26 +1,26 @@ -import { type Node } from '@markdoc/markdoc' -import { slugifyWithCounter } from '@sindresorhus/slugify' +import {type Node} from '@markdoc/markdoc'; +import {slugifyWithCounter} from '@sindresorhus/slugify'; interface HeadingNode extends Node { - type: 'heading' + type: 'heading'; attributes: { - level: 1 | 2 | 3 | 4 | 5 | 6 - id?: string - [key: string]: unknown - } + level: 1 | 2 | 3 | 4 | 5 | 6; + id?: string; + [key: string]: unknown; + }; } type H2Node = HeadingNode & { attributes: { - level: 2 - } -} + level: 2; + }; +}; type H3Node = HeadingNode & { attributes: { - level: 3 - } -} + level: 3; + }; +}; function isHeadingNode(node: Node): node is HeadingNode { return ( @@ -28,70 +28,70 @@ function isHeadingNode(node: Node): node is HeadingNode { [1, 2, 3, 4, 5, 6].includes(node.attributes.level) && (typeof node.attributes.id === 'string' || typeof node.attributes.id === 'undefined') - ) + ); } function isH2Node(node: Node): node is H2Node { - return isHeadingNode(node) && node.attributes.level === 2 + return isHeadingNode(node) && node.attributes.level === 2; } function isH3Node(node: Node): node is H3Node { - return isHeadingNode(node) && node.attributes.level === 3 + return isHeadingNode(node) && node.attributes.level === 3; } function getNodeText(node: Node) { - let text = '' + let text = ''; for (let child of node.children ?? []) { if (child.type === 'text') { - text += child.attributes.content + text += child.attributes.content; } - text += getNodeText(child) + text += getNodeText(child); } - return text + return text; } export type Subsection = H3Node['attributes'] & { - id: string - title: string - children?: undefined -} + id: string; + title: string; + children?: undefined; +}; export type Section = H2Node['attributes'] & { - id: string - title: string - children: Array -} + id: string; + title: string; + children: Array; +}; export function collectSections( nodes: Array, slugify = slugifyWithCounter(), ) { - let sections: Array
        = [] + let sections: Array
        = []; for (let node of nodes) { if (isH2Node(node) || isH3Node(node)) { - let title = getNodeText(node) + let title = getNodeText(node); if (title) { - let id = slugify(title) + let id = slugify(title); if (isH3Node(node)) { if (!sections[sections.length - 1]) { throw new Error( 'Cannot add `h3` to table of contents without a preceding `h2`', - ) + ); } sections[sections.length - 1].children.push({ ...node.attributes, id, title, - }) + }); } else { - sections.push({ ...node.attributes, id, title, children: [] }) + sections.push({...node.attributes, id, title, children: []}); } } } - sections.push(...collectSections(node.children ?? [], slugify)) + sections.push(...collectSections(node.children ?? [], slugify)); } - return sections + return sections; } diff --git a/apps/docs/src/styles/tailwind.css b/apps/docs/src/styles/tailwind.css index e423890..b6a6242 100644 --- a/apps/docs/src/styles/tailwind.css +++ b/apps/docs/src/styles/tailwind.css @@ -2,9 +2,14 @@ @import './prism.css'; @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; +@import '@elwood/ui/style.css'; @layer base { [inert] ::-webkit-scrollbar { display: none; } + + body { + overflow: hidden; + } } diff --git a/apps/docs/tailwind.config.ts b/apps/docs/tailwind.config.ts index a46c213..2cfcab3 100644 --- a/apps/docs/tailwind.config.ts +++ b/apps/docs/tailwind.config.ts @@ -1,34 +1,40 @@ -import typographyPlugin from '@tailwindcss/typography' -import { type Config } from 'tailwindcss' +import typographyPlugin from '@tailwindcss/typography'; +import type {PluginCreator} from 'postcss'; +import {type Config} from 'tailwindcss'; +import {darkMode, theme, plugins} from '@elwood/ui/tailwind.config.js'; + +const themeExtend = theme?.extend || {}; export default { content: ['./src/**/*.{js,jsx,ts,tsx,md}'], - darkMode: 'selector', + darkMode: darkMode ?? 'selector', theme: { + ...theme, fontSize: { - xs: ['0.75rem', { lineHeight: '1rem' }], - sm: ['0.875rem', { lineHeight: '1.5rem' }], - base: ['1rem', { lineHeight: '2rem' }], - lg: ['1.125rem', { lineHeight: '1.75rem' }], - xl: ['1.25rem', { lineHeight: '2rem' }], - '2xl': ['1.5rem', { lineHeight: '2.5rem' }], - '3xl': ['2rem', { lineHeight: '2.5rem' }], - '4xl': ['2.5rem', { lineHeight: '3rem' }], - '5xl': ['3rem', { lineHeight: '3.5rem' }], - '6xl': ['3.75rem', { lineHeight: '1' }], - '7xl': ['4.5rem', { lineHeight: '1' }], - '8xl': ['6rem', { lineHeight: '1' }], - '9xl': ['8rem', { lineHeight: '1' }], + xs: ['0.75rem', {lineHeight: '1rem'}], + sm: ['0.875rem', {lineHeight: '1.5rem'}], + base: ['1rem', {lineHeight: '2rem'}], + lg: ['1.125rem', {lineHeight: '1.75rem'}], + xl: ['1.25rem', {lineHeight: '2rem'}], + '2xl': ['1.5rem', {lineHeight: '2.5rem'}], + '3xl': ['2rem', {lineHeight: '2.5rem'}], + '4xl': ['2.5rem', {lineHeight: '3rem'}], + '5xl': ['3rem', {lineHeight: '3.5rem'}], + '6xl': ['3.75rem', {lineHeight: '1'}], + '7xl': ['4.5rem', {lineHeight: '1'}], + '8xl': ['6rem', {lineHeight: '1'}], + '9xl': ['8rem', {lineHeight: '1'}], }, extend: { + ...themeExtend, fontFamily: { sans: 'var(--font-inter)', - display: ['var(--font-lexend)', { fontFeatureSettings: '"ss01"' }], + display: ['var(--font-lexend)', {fontFeatureSettings: '"ss01"'}], }, maxWidth: { '8xl': '88rem', }, }, }, - plugins: [typographyPlugin], -} satisfies Config + plugins: [...Array.from(plugins as PluginCreator[]), typographyPlugin], +} satisfies Config; diff --git a/apps/docs/types.d.ts b/apps/docs/types.d.ts index 349d908..9d58ccb 100644 --- a/apps/docs/types.d.ts +++ b/apps/docs/types.d.ts @@ -1,11 +1,11 @@ -import { type SearchOptions } from 'flexsearch' +import {type SearchOptions} from 'flexsearch'; declare module '@/markdoc/search.mjs' { export type Result = { - url: string - title: string - pageTitle?: string - } + url: string; + title: string; + pageTitle?: string; + }; - export function search(query: string, options?: SearchOptions): Array + export function search(query: string, options?: SearchOptions): Array; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 792d621..a02744a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,9 @@ importers: eslint-plugin-next: specifier: ^0.0.0 version: 0.0.0 + postcss: + specifier: ^8.4.38 + version: 8.4.38 prettier: specifier: ^3.2.5 version: 3.2.5 diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..9ed8605 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,20 @@ +/** @type {import('prettier').Options} */ +module.exports = { + tabWidth: 2, + arrowParens: "avoid", + bracketSameLine: true, + bracketSpacing: false, + singleQuote: true, + trailingComma: "all", + plugins: ["prettier-plugin-sql-cst"], + overrides: [ + { + files: "**/*.json", + options: {parser: "json"} + }, + { + files: ["*.sql"], + options: {parser: "postgresql"} + } + ] +}