diff --git a/capstart-website/content/docs/transitions.mdx b/capstart-website/content/docs/transitions.mdx index 133a883..91ab173 100644 --- a/capstart-website/content/docs/transitions.mdx +++ b/capstart-website/content/docs/transitions.mdx @@ -3,7 +3,7 @@ title: Transitions description: Add framework-agnostic page transitions to a Capacitor app. --- -`@capgo/transitions` adds native-feeling page transitions to a Capacitor app without forcing a design system or a router. +`@capgo/capacitor-transitions` adds native-feeling page transitions to a Capacitor app without forcing a design system or a router. It is a separate library from `@capgo/capacitor-native-navigation`. You can use both together: Native Navigation can render the native navbar and tabbar, while Transitions animates the page content underneath. @@ -32,11 +32,23 @@ It is a separate library from `@capgo/capacitor-native-navigation`. You can use ## Installation +### Vite + Capacitor + shadcn/ui + +If you use Vite, Capacitor, React, and shadcn/ui, install the complete React navigation shell from the Capstart registry: + +```bash +npx shadcn@latest add AdrienADV/capstart/react-capacitor-navigation +``` + +The registry installs the transition setup, a React Router shell, a bottom tab bar, a native-feeling header, example screens, `viewport-fit=cover`, and the minimal safe-area CSS variables needed by the layout. + +### Manual installation + ```bash -npm install @capgo/transitions +npm install @capgo/capacitor-transitions ``` -The package is published as `@capgo/transitions`. The GitHub repository is named `capacitor-transitions`. +The package is published as `@capgo/capacitor-transitions`. The GitHub repository is named `capacitor-transitions`. ## Basic Structure @@ -69,8 +81,8 @@ import { useEffect, useRef } from 'react'; import { initTransitions, setupRouterOutlet, -} from '@capgo/transitions/react'; -import '@capgo/transitions'; +} from '@capgo/capacitor-transitions/react'; +import '@capgo/capacitor-transitions'; initTransitions({ platform: 'auto' }); @@ -92,7 +104,7 @@ export function AppShell({ children }: { children: React.ReactNode }) { ```tsx title="HomePage.tsx" import { useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; -import { setDirection, setupPage } from '@capgo/transitions/react'; +import { setDirection, setupPage } from '@capgo/capacitor-transitions/react'; export function HomePage() { const navigate = useNavigate(); @@ -159,7 +171,7 @@ export function HomePage() { For lower-level control, create a transition controller and drive the stack manually. ```ts title="transition-controller.ts" -import { createTransitionController } from '@capgo/transitions'; +import { createTransitionController } from '@capgo/capacitor-transitions'; const controller = createTransitionController({ platform: 'auto', @@ -182,11 +194,11 @@ await controller.setRoot(homePageElement, { ## Use With Native Navigation -`@capgo/transitions` and `@capgo/capacitor-native-navigation` solve different parts of the interface. +`@capgo/capacitor-transitions` and `@capgo/capacitor-native-navigation` solve different parts of the interface. | Library | Responsibility | | --- | --- | -| `@capgo/transitions` | Animates web pages, headers, content, and footers inside the WebView. | +| `@capgo/capacitor-transitions` | Animates web pages, headers, content, and footers inside the WebView. | | `@capgo/capacitor-native-navigation` | Renders native navbar and tabbar surfaces around the WebView. | When you use both together, keep the native bars outside the animated page content. Let Native Navigation own the bars, and let Transitions animate the route content. diff --git a/registry.json b/registry.json new file mode 100644 index 0000000..4acca0c --- /dev/null +++ b/registry.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "name": "capstart", + "homepage": "https://github.com/AdrienADV/capstart", + "items": [ + { + "name": "react-capacitor-navigation", + "type": "registry:item", + "title": "React Capacitor Navigation", + "description": "Mobile-first React Router shell for Capacitor apps with Capgo transitions, a tab layout, a native-feeling header, safe-area variables, and example screens.", + "dependencies": [ + "@capacitor/core", + "@capgo/capacitor-transitions", + "lucide-react", + "react-router" + ], + "registryDependencies": [ + "button" + ], + "css": { + ":root": { + "--safe-area-top": "var(--safe-area-inset-top, env(safe-area-inset-top, 0px))", + "--safe-area-bottom": "var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))" + }, + "html, body, #root": { + "height": "100%" + }, + "body": { + "margin": "0", + "overflow": "hidden" + }, + "cap-router-outlet": { + "display": "block", + "height": "100%" + } + }, + "files": [ + { + "path": "registry/react-capacitor-navigation/index.html", + "type": "registry:file", + "target": "~/index.html" + }, + { + "path": "registry/react-capacitor-navigation/src/main.tsx", + "type": "registry:file", + "target": "~/src/main.tsx" + }, + { + "path": "registry/react-capacitor-navigation/src/app.tsx", + "type": "registry:file", + "target": "~/src/app.tsx" + }, + { + "path": "registry/react-capacitor-navigation/src/router.tsx", + "type": "registry:file", + "target": "~/src/router.tsx" + }, + { + "path": "registry/react-capacitor-navigation/src/components/capacitor-header.tsx", + "type": "registry:file", + "target": "~/src/components/capacitor-header.tsx" + }, + { + "path": "registry/react-capacitor-navigation/src/hooks/use-capacitor-page.ts", + "type": "registry:file", + "target": "~/src/hooks/use-capacitor-page.ts" + }, + { + "path": "registry/react-capacitor-navigation/src/components/capacitor-tab-bar.tsx", + "type": "registry:file", + "target": "~/src/components/capacitor-tab-bar.tsx" + }, + { + "path": "registry/react-capacitor-navigation/src/pages/app/home.tsx", + "type": "registry:file", + "target": "~/src/pages/app/home.tsx" + }, + { + "path": "registry/react-capacitor-navigation/src/pages/app/settings.tsx", + "type": "registry:file", + "target": "~/src/pages/app/settings.tsx" + }, + { + "path": "registry/react-capacitor-navigation/src/pages/app/home/details.tsx", + "type": "registry:file", + "target": "~/src/pages/app/home/details.tsx" + } + ], + "docs": "This item installs a starter navigation shell and targets index.html, src/main.tsx, src/app.tsx and src/router.tsx. Run `shadcn add AdrienADV/capstart/react-capacitor-navigation --dry-run` first in existing projects. For Capacitor 8 safe areas, index.html includes `viewport-fit=cover`; the registry injects the safe-area and full-height shell rules into the project's configured global CSS file. Use the default `SystemBars.insetsHandling = \"css\"` behavior so Android can inject `--safe-area-inset-*` variables." + } + ] +} diff --git a/registry/react-capacitor-navigation/index.html b/registry/react-capacitor-navigation/index.html new file mode 100644 index 0000000..bcc2d31 --- /dev/null +++ b/registry/react-capacitor-navigation/index.html @@ -0,0 +1,15 @@ + + + + + + Capacitor App + + +
+ + + diff --git a/registry/react-capacitor-navigation/src/app.tsx b/registry/react-capacitor-navigation/src/app.tsx new file mode 100644 index 0000000..33de54d --- /dev/null +++ b/registry/react-capacitor-navigation/src/app.tsx @@ -0,0 +1,27 @@ +import { useEffect, useRef } from "react" +import { useLocation } from "react-router" +import { setupRouterOutlet } from "@capgo/capacitor-transitions/react" + +import Router from "./router" + +export default function App() { + const location = useLocation() + const outletRef = useRef(null) + + useEffect(() => { + if (!outletRef.current) { + return + } + + setupRouterOutlet(outletRef.current, { + platform: "auto", + swipeGesture: "auto", + }) + }, []) + + return ( + + + + ) +} diff --git a/registry/react-capacitor-navigation/src/components/capacitor-header.tsx b/registry/react-capacitor-navigation/src/components/capacitor-header.tsx new file mode 100644 index 0000000..a8929da --- /dev/null +++ b/registry/react-capacitor-navigation/src/components/capacitor-header.tsx @@ -0,0 +1,51 @@ +import type { ReactNode } from "react" +import { ChevronLeftIcon } from "lucide-react" +import { useNavigate } from "react-router" +import { setDirection } from "@capgo/capacitor-transitions/react" + +import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +interface CapacitorHeaderProps { + title?: string + children?: ReactNode + className?: string +} + +export default function CapacitorHeader({ + title, + children, + className, +}: CapacitorHeaderProps) { + const navigate = useNavigate() + + function goBack() { + setDirection("back") + navigate(-1) + } + + return ( + +
+ + {title ?

{title}

: null} + {children} +
+
+ ) +} diff --git a/registry/react-capacitor-navigation/src/components/capacitor-tab-bar.tsx b/registry/react-capacitor-navigation/src/components/capacitor-tab-bar.tsx new file mode 100644 index 0000000..e66f639 --- /dev/null +++ b/registry/react-capacitor-navigation/src/components/capacitor-tab-bar.tsx @@ -0,0 +1,39 @@ +import { HomeIcon, SettingsIcon } from "lucide-react" +import { NavLink } from "react-router" +import { setNavigation } from "@capgo/capacitor-transitions/react" + +import { cn } from "@/lib/utils" + +const tabs = [ + { to: "/app", icon: HomeIcon, label: "Home" }, + { to: "/app/settings", icon: SettingsIcon, label: "Settings" }, +] + +export default function CapacitorTabBar() { + return ( + + + + ) +} diff --git a/registry/react-capacitor-navigation/src/hooks/use-capacitor-page.ts b/registry/react-capacitor-navigation/src/hooks/use-capacitor-page.ts new file mode 100644 index 0000000..3f1eab9 --- /dev/null +++ b/registry/react-capacitor-navigation/src/hooks/use-capacitor-page.ts @@ -0,0 +1,16 @@ +import { useEffect, useRef } from "react" +import { setupPage } from "@capgo/capacitor-transitions/react" + +export function useCapacitorPage() { + const pageRef = useRef(null) + + useEffect(() => { + if (!pageRef.current) { + return + } + + return setupPage(pageRef.current) + }, []) + + return pageRef +} diff --git a/registry/react-capacitor-navigation/src/main.tsx b/registry/react-capacitor-navigation/src/main.tsx new file mode 100644 index 0000000..4480496 --- /dev/null +++ b/registry/react-capacitor-navigation/src/main.tsx @@ -0,0 +1,18 @@ +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import { BrowserRouter } from "react-router" +import { initTransitions } from "@capgo/capacitor-transitions/react" +import "@capgo/capacitor-transitions" + +import App from "./app" +import "./index.css" + +initTransitions({ platform: "auto" }) + +createRoot(document.getElementById("root")!).render( + + + + + +) diff --git a/registry/react-capacitor-navigation/src/pages/app/home.tsx b/registry/react-capacitor-navigation/src/pages/app/home.tsx new file mode 100644 index 0000000..a9701a7 --- /dev/null +++ b/registry/react-capacitor-navigation/src/pages/app/home.tsx @@ -0,0 +1,37 @@ +import { ArrowRightIcon } from "lucide-react" +import { useNavigate } from "react-router" +import { setDirection } from "@capgo/capacitor-transitions/react" + +import CapacitorTabBar from "@/components/capacitor-tab-bar" +import { Button } from "@/components/ui/button" +import { useCapacitorPage } from "@/hooks/use-capacitor-page" + +export default function Home() { + const navigate = useNavigate() + const pageRef = useCapacitorPage() + + function goToDetails() { + setDirection("forward") + navigate("/app/details") + } + + return ( + + +
+

Capstart Navigation

+

Welcome

+

+ This screen lives inside the tab navigation. +

+ + +
+
+ +
+ ) +} diff --git a/registry/react-capacitor-navigation/src/pages/app/home/details.tsx b/registry/react-capacitor-navigation/src/pages/app/home/details.tsx new file mode 100644 index 0000000..aac1f65 --- /dev/null +++ b/registry/react-capacitor-navigation/src/pages/app/home/details.tsx @@ -0,0 +1,20 @@ +import CapacitorHeader from "@/components/capacitor-header" +import { useCapacitorPage } from "@/hooks/use-capacitor-page" + +export default function Details() { + const pageRef = useCapacitorPage() + + return ( + + + +
+

+ This screen is outside the tab navigation, so the bottom navigation + is hidden. +

+
+
+
+ ) +} diff --git a/registry/react-capacitor-navigation/src/pages/app/settings.tsx b/registry/react-capacitor-navigation/src/pages/app/settings.tsx new file mode 100644 index 0000000..a4ec5f5 --- /dev/null +++ b/registry/react-capacitor-navigation/src/pages/app/settings.tsx @@ -0,0 +1,26 @@ +import CapacitorTabBar from "@/components/capacitor-tab-bar" +import { Button } from "@/components/ui/button" +import { useCapacitorPage } from "@/hooks/use-capacitor-page" + +export default function Settings() { + const pageRef = useCapacitorPage() + + return ( + + +
+

Capstart Navigation

+

Settings

+

+ Replace this screen with your project settings. +

+ + +
+
+ +
+ ) +} diff --git a/registry/react-capacitor-navigation/src/router.tsx b/registry/react-capacitor-navigation/src/router.tsx new file mode 100644 index 0000000..08521bb --- /dev/null +++ b/registry/react-capacitor-navigation/src/router.tsx @@ -0,0 +1,21 @@ +import type { Location } from "react-router" +import { Navigate, Route, Routes } from "react-router" + +import Details from "./pages/app/home/details" +import Home from "./pages/app/home" +import Settings from "./pages/app/settings" + +interface RouterProps { + location: Location +} + +export default function Router({ location }: RouterProps) { + return ( + + } /> + } /> + } /> + } /> + + ) +}