How to customize Toolpad Core.
diff --git a/docs/data/toolpad/core/features/audit-logs.md b/docs/data/toolpad/core/features/audit-logs.md
index 03396b93789..bbf15d8a4a8 100644
--- a/docs/data/toolpad/core/features/audit-logs.md
+++ b/docs/data/toolpad/core/features/audit-logs.md
@@ -1,3 +1,3 @@
# Audit Logs
-Toolpad can provide access to internal events to be displayed on your dashboard as audit logs
+Toolpad can provide access to internal events to be displayed on your dashboard as audit logs.
diff --git a/docs/data/toolpad/core/features/authentication.md b/docs/data/toolpad/core/features/authentication.md
index a68f0bba2a5..befc5ff279d 100644
--- a/docs/data/toolpad/core/features/authentication.md
+++ b/docs/data/toolpad/core/features/authentication.md
@@ -1,6 +1,6 @@
# Authentication
-Toolpad provides working authentication out of the box, with support for multiple providers
+Toolpad provides working authentication out of the box, with support for multiple providers.
## Setup authentication pages
diff --git a/docs/data/toolpad/core/introduction/ReactRouter.js b/docs/data/toolpad/core/integrations/ReactRouter.js
similarity index 92%
rename from docs/data/toolpad/core/introduction/ReactRouter.js
rename to docs/data/toolpad/core/integrations/ReactRouter.js
index 7b8059c1283..8498907f251 100644
--- a/docs/data/toolpad/core/introduction/ReactRouter.js
+++ b/docs/data/toolpad/core/integrations/ReactRouter.js
@@ -4,8 +4,8 @@ import { createTheme } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
-import { createMemoryRouter, RouterProvider, Outlet } from 'react-router-dom';
-import { AppProvider } from '@toolpad/core/react-router-dom';
+import { createMemoryRouter, RouterProvider, Outlet } from 'react-router';
+import { ReactRouterAppProvider } from '@toolpad/core/react-router';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { PageContainer } from '@toolpad/core/PageContainer';
@@ -67,14 +67,14 @@ function App(props) {
const { window } = props;
return (
- This guide walks you through adding Toolpad Core to an existing Next.js app.
+
+## Prerequisites
+
+Ensure that you have `@mui/material` and `next` installed. You also need the following to make the integration work correctly:
+
+ {
+ 'use server';
+ try {
+ return await signIn(provider.id, {
+ redirectTo: callbackUrl ?? '/',
+ });
+ } catch (error) {
+ // The desired flow for successful sign in in all cases
+ // and unsuccessful sign in for OAuth providers will cause a `redirect`,
+ // and `redirect` is a throwing function, so we need to re-throw
+ // to allow the redirect to happen
+ // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642
+ // Detect a `NEXT_REDIRECT` error and re-throw it
+ if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
+ throw error;
+ }
+ // Handle Auth.js errors
+ if (error instanceof AuthError) {
+ return {
+ error: error.message,
+ type: error.type,
+ };
+ }
+ // An error boundary must exist to handle unknown errors
+ return {
+ error: 'Something went wrong.',
+ type: 'UnknownError',
+ };
+ }
+ }}
+ />
+ );
+}
+```
+
+### Create a route handler for sign-in
+
+`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth]/route.ts`:
+
+```ts title="app/api/auth/[...nextauth]/route.ts"
+import { handlers } from '../../../../auth';
+
+export const { GET, POST } = handlers;
+```
+
+### Add a middleware
+
+Add a middleware to your app to protect your dashboard pages:
+
+```ts title="middleware.ts"
+export { auth as middleware } from './auth';
+
+export const config = {
+ // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+};
+```
+
+That's it! You now have Toolpad Core integrated into your Next.js App Router app with authentication setup:
+
+{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-app.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-app-dark.png", "alt": "Next.js App Router with Toolpad Core", "caption": "Next.js App Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }}
+
+:::info
+For a full working example with authentication included, see the [Toolpad Core Next.js App with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs/)
+:::
diff --git a/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md b/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md
new file mode 100644
index 00000000000..6eac0cbe05d
--- /dev/null
+++ b/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md
@@ -0,0 +1,434 @@
+---
+title: Next.js - Integration
+---
+
+# Next.js Pages Router
+
+This guide walks you through adding Toolpad Core to an existing Next.js app.
+
+## Prerequisites
+
+Ensure that you have `@mui/material` and `next` installed. You also need the following to make the integration work correctly:
+
+
+
+```bash npm
+npm install @mui/material-nextjs @emotion/cache
+```
+
+```bash pnpm
+pnpm add @mui/material-nextjs @emotion/cache
+```
+
+```bash yarn
+yarn add install @mui/material-nextjs @emotion/cache
+```
+
+
+
+## Wrap your application with `NextAppProvider`
+
+In your root layout file (for example, `pages/_app.tsx`), wrap your application with the `NextAppProvider`:
+
+```tsx title="pages/_app.tsx"
+import * as React from 'react';
+import { NextAppProvider } from '@toolpad/core/nextjs';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import Head from 'next/head';
+import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import type { Navigation } from '@toolpad/core/AppProvider';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ segment: '',
+ title: 'Dashboard',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+export default function App({ Component }: { Component: React.ElementType }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+:::info
+The `AppCacheProvider` component is not required to use Toolpad Core, but it's recommended.
+
+See the [Material UI Next.js Pages Router integration docs](https://mui.com/material-ui/integrations/nextjs/#configuration-2) for more details.
+:::
+
+## Modify `_document.tsx`
+
+Modify `_document.tsx` to include the `DocumentHeadTags` component:
+
+```tsx title="pages/_document.tsx"
+import * as React from 'react';
+import {
+ Html,
+ Head,
+ Main,
+ NextScript,
+ DocumentProps,
+ DocumentContext,
+} from 'next/document';
+import {
+ DocumentHeadTags,
+ DocumentHeadTagsProps,
+ documentGetInitialProps,
+} from '@mui/material-nextjs/v14-pagesRouter';
+
+export default function Document(props: DocumentProps & DocumentHeadTagsProps) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+Document.getInitialProps = async (ctx: DocumentContext) => {
+ const finalProps = await documentGetInitialProps(ctx);
+ return finalProps;
+};
+```
+
+## Add a dashboard page
+
+Create a dashboard page (for example, `pages/index.tsx`):
+
+```tsx title="pages/index.tsx"
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function HomePage() {
+ return Welcome to Toolpad! ;
+}
+```
+
+## (Optional) Add a second page
+
+Create a new page in the dashboard, for example, `pages/orders/index.tsx`:
+
+```tsx title="pages/orders/index.tsx"
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function OrdersPage() {
+ return Welcome to the orders page! ;
+}
+```
+
+To add this page to the navigation, add it to the `NAVIGATION` variable:
+
+```ts title="pages/_app.tsx"
+export const NAVIGATION = [
+ // ...
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+ // ...
+];
+```
+
+## (Optional) Set up authentication
+
+If you want to add authentication, you can use Auth.js with Toolpad Core. Here's an example setup:
+
+### Install the dependencies
+
+```bash
+npm install next-auth@beta
+```
+
+### Create an `auth.ts` file
+
+```ts title="auth.ts"
+import NextAuth from 'next-auth';
+import GitHub from 'next-auth/providers/github';
+import type { Provider } from 'next-auth/providers';
+
+const providers: Provider[] = [
+ GitHub({
+ clientId: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
+ }),
+];
+
+export const providerMap = providers.map((provider) => {
+ if (typeof provider === 'function') {
+ const providerData = provider();
+ return { id: providerData.id, name: providerData.name };
+ }
+ return { id: provider.id, name: provider.name };
+});
+
+export const { handlers, auth } = NextAuth({
+ providers,
+ secret: process.env.AUTH_SECRET,
+ pages: {
+ signIn: '/auth/signin',
+ },
+ callbacks: {
+ authorized({ auth: session, request: { nextUrl } }) {
+ const isLoggedIn = !!session?.user;
+ const isPublicPage = nextUrl.pathname.startsWith('/public');
+
+ if (isPublicPage || isLoggedIn) {
+ return true;
+ }
+
+ return false; // Redirect unauthenticated users to login page
+ },
+ },
+});
+```
+
+### Modify `_app.tsx`
+
+Modify `_app.tsx` to include the `authentication` prop and other helpers:
+
+```tsx title="pages/_app.tsx"
+import * as React from 'react';
+import { NextAppProvider } from '@toolpad/core/nextjs';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import Head from 'next/head';
+import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import type { NextPage } from 'next';
+import type { AppProps } from 'next/app';
+import type { Navigation } from '@toolpad/core/AppProvider';
+import { SessionProvider, signIn, signOut, useSession } from 'next-auth/react';
+import LinearProgress from '@mui/material/LinearProgress';
+
+export type NextPageWithLayout = NextPage
& {
+ getLayout?: (page: React.ReactElement) => React.ReactNode;
+ requireAuth?: boolean;
+};
+
+type AppPropsWithLayout = AppProps & {
+ Component: NextPageWithLayout;
+};
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ segment: '',
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+const AUTHENTICATION = {
+ signIn,
+ signOut,
+};
+
+function getDefaultLayout(page: React.ReactElement) {
+ return (
+
+ {page}
+
+ );
+}
+
+function RequireAuth({ children }: { children: React.ReactNode }) {
+ const { status } = useSession();
+
+ if (status === 'loading') {
+ return ;
+ }
+
+ return children;
+}
+
+function AppLayout({ children }: { children: React.ReactNode }) {
+ const { data: session } = useSession();
+ return (
+
+
+
+
+
+ {children}
+
+
+ );
+}
+
+export default function App(props: AppPropsWithLayout) {
+ const {
+ Component,
+ pageProps: { session, ...pageProps },
+ } = props;
+
+ const getLayout = Component.getLayout ?? getDefaultLayout;
+ const requireAuth = Component.requireAuth ?? true;
+
+ let pageContent = getLayout( );
+ if (requireAuth) {
+ pageContent = {pageContent} ;
+ }
+ pageContent = {pageContent} ;
+
+ return (
+
+ {pageContent}
+
+ );
+}
+```
+
+### Create a sign-in page
+
+Use the `SignInPage` component to add a sign-in page to your app. For example, `pages/auth/signin.tsx`:
+
+```tsx title="pages/auth/signin.tsx"
+import * as React from 'react';
+import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
+import Link from '@mui/material/Link';
+import { SignInPage } from '@toolpad/core/SignInPage';
+import { signIn } from 'next-auth/react';
+import { useRouter } from 'next/router';
+import { auth, providerMap } from '../../auth';
+
+export default function SignIn({
+ providers,
+}: InferGetServerSidePropsType) {
+ const router = useRouter();
+ return (
+ {
+ try {
+ const signInResponse = await signIn(provider.id, {
+ callbackUrl: callbackUrl ?? '/',
+ });
+ if (signInResponse && signInResponse.error) {
+ // Handle Auth.js errors
+ return {
+ error: signInResponse.error.message,
+ type: signInResponse.error,
+ };
+ }
+ return {};
+ } catch (error) {
+ // An error boundary must exist to handle unknown errors
+ return {
+ error: 'Something went wrong.',
+ type: 'UnknownError',
+ };
+ }
+ }}
+ />
+ );
+}
+
+SignIn.getLayout = (page: React.ReactNode) => page;
+
+SignIn.requireAuth = false;
+
+export async function getServerSideProps(context: GetServerSidePropsContext) {
+ const session = await auth(context);
+
+ // If the user is already logged in, redirect.
+ // Note: Make sure not to redirect to the same page
+ // To avoid an infinite loop!
+ if (session) {
+ return { redirect: { destination: '/' } };
+ }
+
+ return {
+ props: {
+ providers: providerMap,
+ },
+ };
+}
+```
+
+### Create a route handler for sign-in
+
+`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth].ts`:
+
+```ts title="app/api/auth/[...nextauth].ts"
+import { handlers } from '../../../../auth';
+
+export const { GET, POST } = handlers;
+```
+
+:::warning
+
+Note that this file is a route handler and must be placed in the `app` directory, even if the rest of your app is in the `pages` directory. Know more in the [Auth.js documentation](https://authjs.dev/getting-started/installation#configure).
+
+:::
+
+#### Add a middleware
+
+Add a middleware to your app to protect your dashboard pages:
+
+```ts title="middleware.ts"
+export { auth as middleware } from './auth';
+
+export const config = {
+ // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+};
+```
+
+That's it! You now have Toolpad Core integrated into your Next.js Pages Router app with authentication setup:
+
+{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-pages.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-pages-dark.png", "alt": "Next.js Pages Router with Toolpad Core", "caption": "Next.js Pages Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }}
+
+:::info
+For a full working example with authentication included, see the [Toolpad Core Next.js Pages app with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-pages/)
+:::
diff --git a/docs/data/toolpad/core/integrations/react-router.md b/docs/data/toolpad/core/integrations/react-router.md
new file mode 100644
index 00000000000..933af08b05d
--- /dev/null
+++ b/docs/data/toolpad/core/integrations/react-router.md
@@ -0,0 +1,585 @@
+---
+title: React Router - Integration
+---
+
+# React Router
+
+To integrate Toolpad Core into a single-page app (with Vite, for example) using React Router, follow these steps.
+
+## Wrap all your pages in a `ReactRouterAppProvider`
+
+In your router configuration (for example `src/main.tsx`), use a shared component or element (for example `src/App.tsx`) as a root **layout route** that wraps the whole application with the `ReactRouterAppProvider` from `@toolpad/core/react-router`.
+
+You must use the ` ` component from `react-router` in this root layout element or component.
+
+```tsx title="src/main.tsx"
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { createBrowserRouter, RouterProvider } from 'react-router';
+import App from './App';
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+
+const router = createBrowserRouter([
+ {
+ Component: App, // root layout route
+ },
+]);
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
+```
+
+```tsx title="src/App.tsx"
+import * as React from 'react';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { ReactRouterAppProvider } from '@toolpad/core/react-router';
+import { Outlet } from 'react-router';
+import type { Navigation } from '@toolpad/core';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+```
+
+## Create a dashboard layout
+
+Create a layout file for your dashboard pages (for example `src/layouts/dashboard.tsx`), to also be used as a layout route with the ` ` component from `react-router`:
+
+```tsx title="src/layouts/dashboard.tsx"
+import * as React from 'react';
+import { Outlet } from 'react-router';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default function Layout() {
+ return (
+
+
+
+
+
+ );
+}
+```
+
+The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation.
+
+You can then add this layout component to your React Router configuration (for example `src/main.tsx`), as a child of the root layout route created above.
+
+```tsx title="src/main.tsx"
+import Layout from './layouts/dashboard';
+
+//...
+const router = createBrowserRouter([
+ {
+ Component: App, // root layout route
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ },
+ ],
+ },
+]);
+//...
+```
+
+## Create pages
+
+Create a dashboard page (for example `src/pages/index.tsx`) and an orders page (`src/pages/orders.tsx`).
+
+```tsx title="src/pages/index.tsx"
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function DashboardPage() {
+ return Welcome to Toolpad! ;
+}
+```
+
+```tsx title="src/pages/orders.tsx"
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function OrdersPage() {
+ return Welcome to the Toolpad orders! ;
+}
+```
+
+You can then add these page components as routes to your React Router configuration (for example `src/main.tsx`). By adding them as children of the layout route created above, they are automatically wrapped with that dashboard layout:
+
+```tsx title="src/main.tsx"
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+
+//...
+const router = createBrowserRouter([
+ {
+ Component: App, // root layout route
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '',
+ Component: DashboardPage,
+ },
+ {
+ path: 'orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ ],
+ },
+]);
+//...
+```
+
+That's it! You now have Toolpad Core integrated into your single-page app with React Router!
+
+{{"demo": "ReactRouter.js", "height": 500, "iframe": true, "hideToolbar": true}}
+
+:::info
+For a full working example, see the [Toolpad Core Vite app with React Router example](https://github.com/mui/toolpad/tree/master/examples/core/vite/)
+:::
+
+## (Optional) Set up authentication
+
+You can use the `SignInPage` component to add authentication along with an external authentication provider of your choice. The following code demonstrates the code required to set up authentication with Firebase.
+
+### Define a `SessionContext` to act as the mock authentication provider
+
+```tsx title="src/SessionContext.ts"
+import * as React from 'react';
+
+export interface Session {
+ user: {
+ name?: string;
+ email?: string;
+ image?: string;
+ };
+}
+
+interface SessionContextType {
+ session: Session | null;
+ setSession: (session: Session) => void;
+ loading: boolean;
+}
+
+const SessionContext = React.createContext({
+ session: null,
+ setSession: () => {},
+ loading: true,
+});
+
+export default SessionContext;
+
+export const useSession = () => React.useContext(SessionContext);
+```
+
+### Add Firebase authentication
+
+```tsx title="src/firebase/firebaseConfig.ts"
+import { initializeApp } from 'firebase/app';
+import { getAuth } from 'firebase/auth';
+
+const app = initializeApp({
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
+ storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
+ messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGE_SENDER_ID,
+ appId: import.meta.env.VITE_FIREBASE_APP_ID,
+});
+
+export const firebaseAuth = getAuth(app);
+export default app;
+```
+
+```tsx title="src/firebase/auth.ts"
+import {
+ GoogleAuthProvider,
+ GithubAuthProvider,
+ signInWithPopup,
+ setPersistence,
+ browserSessionPersistence,
+ signInWithEmailAndPassword,
+ signOut,
+} from 'firebase/auth';
+import { firebaseAuth } from './firebaseConfig';
+
+const googleProvider = new GoogleAuthProvider();
+const githubProvider = new GithubAuthProvider();
+
+// Sign in with Google functionality
+export const signInWithGoogle = async () => {
+ try {
+ return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
+ const result = await signInWithPopup(firebaseAuth, googleProvider);
+ return {
+ success: true,
+ user: result.user,
+ error: null,
+ };
+ });
+ } catch (error: any) {
+ return {
+ success: false,
+ user: null,
+ error: error.message,
+ };
+ }
+};
+
+// Sign in with GitHub functionality
+export const signInWithGithub = async () => {
+ try {
+ return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
+ const result = await signInWithPopup(firebaseAuth, githubProvider);
+ return {
+ success: true,
+ user: result.user,
+ error: null,
+ };
+ });
+ } catch (error: any) {
+ return {
+ success: false,
+ user: null,
+ error: error.message,
+ };
+ }
+};
+
+// Sign in with email and password
+
+export async function signInWithCredentials(email: string, password: string) {
+ try {
+ return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
+ const userCredential = await signInWithEmailAndPassword(
+ firebaseAuth,
+ email,
+ password,
+ );
+ return {
+ success: true,
+ user: userCredential.user,
+ error: null,
+ };
+ });
+ } catch (error: any) {
+ return {
+ success: false,
+ user: null,
+ error: error.message || 'Failed to sign in with email/password',
+ };
+ }
+}
+
+// Sign out functionality
+export const firebaseSignOut = async () => {
+ try {
+ await signOut(firebaseAuth);
+ return { success: true };
+ } catch (error: any) {
+ return {
+ success: false,
+ error: error.message,
+ };
+ }
+};
+
+// Auth state observer
+export const onAuthStateChanged = (callback: (user: any) => void) => {
+ return firebaseAuth.onAuthStateChanged(callback);
+};
+```
+
+### Add authentication and session data to the `AppProvider`
+
+```tsx title="src/App.tsx"
+import * as React from 'react';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { ReactRouterAppProvider } from '@toolpad/core/react-router';
+import { Outlet, useNavigate } from 'react-router';
+import type { Navigation, Session } from '@toolpad/core';
+import {
+ firebaseSignOut,
+ signInWithGoogle,
+ onAuthStateChanged,
+} from './firebase/auth';
+import SessionContext, { type Session } from './SessionContext';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+const AUTHENTICATION: Authentication = {
+ signIn: signInWithGoogle,
+ signOut: firebaseSignOut,
+};
+
+export default function App() {
+ const [session, setSession] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+
+ const sessionContextValue = React.useMemo(
+ () => ({
+ session,
+ setSession,
+ loading,
+ }),
+ [session, loading],
+ );
+
+ React.useEffect(() => {
+ // Returns an `unsubscribe` function to be called during teardown
+ const unsubscribe = onAuthStateChanged((user: User | null) => {
+ if (user) {
+ setSession({
+ user: {
+ name: user.displayName || '',
+ email: user.email || '',
+ image: user.photoURL || '',
+ },
+ });
+ } else {
+ setSession(null);
+ }
+ setLoading(false);
+ });
+
+ return () => unsubscribe();
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+### Protect routes inside the dashboard layout
+
+```tsx title="src/layouts/dashboard.tsx"
+import * as React from 'react';
+import LinearProgress from '@mui/material/LinearProgress';
+import { Outlet, Navigate, useLocation } from 'react-router';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import { useSession } from '../SessionContext';
+
+export default function Layout() {
+ const { session, loading } = useSession();
+ const location = useLocation();
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (!session) {
+ // Add the `callbackUrl` search parameter
+ const redirectTo = `/sign-in?callbackUrl=${encodeURIComponent(location.pathname)}`;
+
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+You can protect any page or groups of pages through this mechanism.
+
+### Use the `SignInPage` component to create a sign-in page
+
+```tsx title="src/pages/signIn.tsx"
+'use client';
+import * as React from 'react';
+import { SignInPage } from '@toolpad/core/SignInPage';
+import LinearProgress from '@mui/material/LinearProgress';
+import { Navigate, useNavigate } from 'react-router';
+import { useSession, type Session } from '../SessionContext';
+import {
+ signInWithGoogle,
+ signInWithGithub,
+ signInWithCredentials,
+} from '../firebase/auth';
+
+export default function SignIn() {
+ const { session, setSession, loading } = useSession();
+ const navigate = useNavigate();
+
+ if (loading) {
+ return ;
+ }
+
+ if (session) {
+ return ;
+ }
+
+ return (
+ {
+ let result;
+ try {
+ if (provider.id === 'google') {
+ result = await signInWithGoogle();
+ }
+ if (provider.id === 'github') {
+ result = await signInWithGithub();
+ }
+ if (provider.id === 'credentials') {
+ const email = formData?.get('email') as string;
+ const password = formData?.get('password') as string;
+
+ if (!email || !password) {
+ return { error: 'Email and password are required' };
+ }
+
+ result = await signInWithCredentials(email, password);
+ }
+
+ if (result?.success && result?.user) {
+ // Convert Firebase user to Session format
+ const userSession: Session = {
+ user: {
+ name: result.user.displayName || '',
+ email: result.user.email || '',
+ image: result.user.photoURL || '',
+ },
+ };
+ setSession(userSession);
+ navigate(callbackUrl || '/', { replace: true });
+ return {};
+ }
+ return { error: result?.error || 'Failed to sign in' };
+ } catch (error) {
+ return {
+ error: error instanceof Error ? error.message : 'An error occurred',
+ };
+ }
+ }}
+ />
+ );
+}
+```
+
+### Add the sign in page to the router
+
+```tsx title="src/main.tsx"
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { createBrowserRouter, RouterProvider } from 'react-router';
+import App from './App';
+import Layout from './layouts/dashboard';
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+import SignInPage from './pages/signIn';
+
+const router = createBrowserRouter([
+ {
+ Component: App,
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ {
+ path: '/sign-in',
+ Component: SignInPage,
+ },
+ ],
+ },
+]);
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
+```
+
+:::info
+For a full working example, see the [Toolpad Core Vite app with React Router and Firebase authentication example](https://github.com/mui/toolpad/tree/master/examples/core/firebase-vite/)
+:::
diff --git a/docs/data/toolpad/core/introduction/base-concepts.md b/docs/data/toolpad/core/introduction/base-concepts.md
index 96409572c4a..ae008ad48dc 100644
--- a/docs/data/toolpad/core/introduction/base-concepts.md
+++ b/docs/data/toolpad/core/introduction/base-concepts.md
@@ -15,7 +15,7 @@ import Button from '@mui/material/Button';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
```
-## Component Hierarchy
+## Component hierarchy
The Toolpad Core library is designed to work under different React runtimes such as Next.js, Vite, or even your custom setup. Many of its components rely on functionality of the specific runtime they are used under. The key component in making the components runtime-aware is the `AppProvider`.
@@ -67,9 +67,9 @@ You can pass the router implementation to the `AppProvider` component using the
:::
:::success
-If you are using Next.js, use the `AppProvider` exported from `@toolpad/core/nextjs`.
+If you are using Next.js, use the `NextAppProvider` exported from `@toolpad/core/nextjs`.
-If you are building a single-page application (with [Vite](https://vite.dev/), for example) using React Router for routing, use the `AppProvider` exported from `@toolpad/core/react-router-dom`.
+If you are building a single-page application (with [Vite](https://vite.dev/), for example) using React Router for routing, use the `ReactRouterAppProvider` exported from `@toolpad/core/react-router`.
This automatically sets up the router for you, so that you don't need to pass the `router` prop.
:::
@@ -105,7 +105,3 @@ In this example:
- The `slots` prop allows you to replace entire parts of the component.
- The `slotProps` prop lets you pass additional props to specific slots.
-
-## Next Steps
-
-Now that you understand the basic concepts of Toolpad Core, you're ready to start integrating it into your project. Head over to the [integration docs](/toolpad/core/introduction/integration/) to learn more.
diff --git a/docs/data/toolpad/core/introduction/examples.md b/docs/data/toolpad/core/introduction/examples.md
index 352bfe44129..975af850692 100644
--- a/docs/data/toolpad/core/introduction/examples.md
+++ b/docs/data/toolpad/core/introduction/examples.md
@@ -5,8 +5,12 @@ title: Examples
# Toolpad Core - Examples
-Browse a collection of Toolpad Core examples to help you get started quickly:
+Browse a collection of Toolpad Core examples to help you get started quickly.
-
+## Featured examples
-{{"component": "modules/components/ExamplesGrid/ExamplesGrid.tsx", "examplesFile": "core-examples.ts"}}
+{{"component": "modules/components/examples/CoreFeaturedExamples.tsx"}}
+
+## Other examples
+
+{{"component": "modules/components/examples/CoreOtherExamples.tsx"}}
diff --git a/docs/data/toolpad/core/introduction/installation.md b/docs/data/toolpad/core/introduction/installation.md
index 6a8bc1a8bba..a53251c4146 100644
--- a/docs/data/toolpad/core/introduction/installation.md
+++ b/docs/data/toolpad/core/introduction/installation.md
@@ -4,24 +4,26 @@ title: Toolpad Core - Installation
# Installation
-## Manual Installation
+Learn how to install Toolpad Core in your local environment.
+
+## Manual installation
Use your preferred package manager to install `@toolpad/core` in your project:
```bash npm
-npm install -S @toolpad/core
-```
-
-```bash yarn
-yarn add @toolpad/core
+npm install @toolpad/core
```
```bash pnpm
pnpm add @toolpad/core
```
+```bash yarn
+yarn add @toolpad/core
+```
+
The Toolpad Core package has a peer dependency on `@mui/material` and `@mui/icons-material`. If you aren't using these already in your project, you can install them with:
@@ -29,22 +31,20 @@ The Toolpad Core package has a peer dependency on `@mui/material` and `@mui/icon
```bash npm
-npm install -S @mui/material @mui/icons-material @emotion/react @emotion/styled
-```
-
-```bash yarn
-yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled
+npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
```
```bash pnpm
pnpm add @mui/material @mui/icons-material @emotion/react @emotion/styled
```
-
+```bash yarn
+yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled
+```
-## Automatic Installation
+
-Learn how to install Toolpad Core in your local environment.
+## Automatic installation
1. Run the following command to start Toolpad Core:
@@ -109,6 +109,6 @@ yarn dev
and the following page appears when you run the project locally:
-{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/installation-1.png", "alt": "Toolpad Core entry point", "caption": "Starting with Toolpad Core", "zoom": true, "indent": 1 }}
+{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/bootstrap.png", "srcDark": "/static/toolpad/docs/core/bootstrap-dark.png","alt": "Toolpad Core entry point", "caption": "Starting with Toolpad Core", "zoom": true, "indent": 1 }}
-5. Installation is complete! Begin building your project by making edits to `(dashboard/page/page.tsx`. To understand how to leverage Toolpad Core to build dashboards quickly, [see the detailed tutorial](/toolpad/core/introduction/tutorial/).
+5. Installation is complete! Begin building your project by making edits to `(dashboard)/page/page.tsx`. To understand how to leverage Toolpad Core to build dashboards quickly, [see the detailed tutorial](/toolpad/core/introduction/tutorial/).
diff --git a/docs/data/toolpad/core/introduction/integration.md b/docs/data/toolpad/core/introduction/integration.md
deleted file mode 100644
index 21162f3989d..00000000000
--- a/docs/data/toolpad/core/introduction/integration.md
+++ /dev/null
@@ -1,839 +0,0 @@
----
-title: Toolpad Core - Integration
-description: How to integrate Toolpad Core into your existing project.
----
-
-# Integration
-
-This guide will walk you through the process of adding Toolpad Core to an existing project.
-
-## Installation
-
-
-
-```bash npm
-npm install -S @toolpad/core
-```
-
-```bash yarn
-yarn add @toolpad/core
-```
-
-```bash pnpm
-pnpm add @toolpad/core
-```
-
-
-
-## Next.js App Router
-
-Use the following steps to integrate Toolpad Core into your Next.js app:
-
-### 1. Wrap your application with `AppProvider`
-
-In your root layout file (e.g., `app/layout.tsx`), wrap your application with the `AppProvider`:
-
-```tsx title="app/layout.tsx"
-import { AppProvider } from '@toolpad/core/AppProvider';
-import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
-
-export default function RootLayout({ children }: { children: React.ReactNode }) {
- return (
-
-
- {children}
-
-
- );
-}
-```
-
-You can find details on the `AppProvider` props on the [AppProvider](/toolpad/core/react-app-provider/) page.
-
-:::info
-The `AppRouterCacheProvider` component is not required to use Toolpad Core, but it's recommended to use it to ensure that the styles are appended to the `` and not rendering in the ``.
-
-See the [Material UI Next.js integration docs](https://mui.com/material-ui/integrations/nextjs/) for more details.
-:::
-
-### 2. Create a dashboard layout
-
-Create a layout file for your dashboard pages (e.g., `app/(dashboard)/layout.tsx`):
-
-```tsx title="app/(dashboard)/layout.tsx"
-import * as React from 'react';
-import { DashboardLayout } from '@toolpad/core/DashboardLayout';
-import { PageContainer } from '@toolpad/core/PageContainer';
-
-export default function DashboardPagesLayout(props: { children: React.ReactNode }) {
- return (
-
- {props.children}
-
- );
-}
-```
-
-The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation.
-
-### 3. Create a dashboard page
-
-Now you can create pages within your dashboard. For example, a home page (`app/(dashboard)/page.tsx`):
-
-```tsx title="app/(dashboard)/page.tsx"
-import * as React from 'react';
-import Typography from '@mui/material/Typography';
-
-export default function Page() {
- return Welcome to a page in the dashboard! ;
-}
-```
-
-That's it! You have now integrated Toolpad Core into your Next.js app.
-
-### 4. (Optional) Add a second page
-
-Create a new page in the dashboard, for example, `app/(dashboard)/orders/page.tsx`:
-
-```tsx title="app/(dashboard)/orders/page.tsx"
-import * as React from 'react';
-import Typography from '@mui/material/Typography';
-
-export default function OrdersPage() {
- return Welcome to the orders page! ;
-}
-```
-
-To add this page to the navigation, add it to the `NAVIGATION` variable:
-
-```ts title="app/layout.tsx"
-export const NAVIGATION = [
- // ...
- {
- segment: 'orders',
- title: 'Orders',
- icon: ,
- },
- // ...
-];
-```
-
-### 5. (Optional) Set up authentication
-
-If you want to add authentication, you can use Auth.js with Toolpad Core. Here's an example setup:
-
-#### a. Install the dependencies
-
-```bash
-npm install next-auth@beta
-```
-
-#### b. Create an `auth.ts` file
-
-```ts title="auth.ts"
-import NextAuth from 'next-auth';
-import GitHub from 'next-auth/providers/github';
-import type { Provider } from 'next-auth/providers';
-
-const providers: Provider[] = [
- GitHub({
- clientId: process.env.GITHUB_CLIENT_ID,
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
- }),
-];
-
-export const providerMap = providers.map((provider) => {
- if (typeof provider === 'function') {
- const providerData = provider();
- return { id: providerData.id, name: providerData.name };
- }
- return { id: provider.id, name: provider.name };
-});
-
-export const { handlers, auth, signIn, signOut } = NextAuth({
- providers,
- secret: process.env.AUTH_SECRET,
- pages: {
- signIn: '/auth/signin',
- },
- callbacks: {
- authorized({ auth: session, request: { nextUrl } }) {
- const isLoggedIn = !!session?.user;
- const isPublicPage = nextUrl.pathname.startsWith('/public');
-
- if (isPublicPage || isLoggedIn) {
- return true;
- }
-
- return false; // Redirect unauthenticated users to login page
- },
- },
-});
-```
-
-#### c. Create a sign-in page
-
-Use the `SignInPage` component to add a sign-in page to your app. For example, `app/auth/signin/page.tsx`:
-
-```tsx title="app/auth/signin/page.tsx"
-import * as React from 'react';
-import { SignInPage, type AuthProvider } from '@toolpad/core/SignInPage';
-import { AuthError } from 'next-auth';
-import { providerMap, signIn } from '../../../auth';
-
-export default function SignIn() {
- return (
- {
- 'use server';
- try {
- return await signIn(provider.id, {
- redirectTo: callbackUrl ?? '/',
- });
- } catch (error) {
- // The desired flow for successful sign in in all cases
- // and unsuccessful sign in for OAuth providers will cause a `redirect`,
- // and `redirect` is a throwing function, so we need to re-throw
- // to allow the redirect to happen
- // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642
- // Detect a `NEXT_REDIRECT` error and re-throw it
- if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
- throw error;
- }
- // Handle Auth.js errors
- if (error instanceof AuthError) {
- return {
- error: error.message,
- type: error.type,
- };
- }
- // An error boundary must exist to handle unknown errors
- return {
- error: 'Something went wrong.',
- type: 'UnknownError',
- };
- }
- }}
- />
- );
-}
-```
-
-#### d. Create a route handler for sign-in
-
-`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth]/route.ts`:
-
-```ts title="app/api/auth/[...nextauth]/route.ts"
-import { handlers } from '../../../../auth';
-
-export const { GET, POST } = handlers;
-```
-
-#### e. Add a middleware
-
-Add a middleware to your app to protect your dashboard pages:
-
-```ts title="middleware.ts"
-export { auth as middleware } from './auth';
-
-export const config = {
- // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
- matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
-};
-```
-
-That's it! You now have Toolpad Core integrated into your Next.js App Router app with authentication setup:
-
-{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-app.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-app-dark.png", "alt": "Next.js App Router with Toolpad Core", "caption": "Next.js App Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }}
-
-:::info
-For a full working example with authentication included, see the [Toolpad Core Next.js App with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core-auth-nextjs)
-:::
-
-## Next.js Pages Router
-
-To integrate Toolpad Core into your Next.js Pages Router app, follow these steps:
-
-### 1. Wrap your application with `AppProvider`
-
-In your root layout file (e.g., `pages/_app.tsx`), wrap your application with the `AppProvider`:
-
-```tsx title="pages/_app.tsx"
-import * as React from 'react';
-import { AppProvider } from '@toolpad/core/nextjs';
-import { PageContainer } from '@toolpad/core/PageContainer';
-import { DashboardLayout } from '@toolpad/core/DashboardLayout';
-import Head from 'next/head';
-import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter';
-import DashboardIcon from '@mui/icons-material/Dashboard';
-import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
-import type { Navigation } from '@toolpad/core/AppProvider';
-
-const NAVIGATION: Navigation = [
- {
- kind: 'header',
- title: 'Main items',
- },
- {
- segment: '',
- title: 'Dashboard',
- icon: ,
- },
-];
-
-const BRANDING = {
- title: 'My Toolpad Core App',
-};
-
-export default function App({ Component }: { Component: React.ElementType }) {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-```
-
-:::info
-The `AppCacheProvider` component is not required to use Toolpad Core, but it's recommended.
-
-See the [Material UI Next.js Pages Router integration docs](https://mui.com/material-ui/integrations/nextjs/#configuration-2) for more details.
-:::
-
-### 2. Modify `_document.tsx`
-
-Modify `_document.tsx` to include the `DocumentHeadTags` component:
-
-```tsx title="pages/_document.tsx"
-import * as React from 'react';
-import {
- Html,
- Head,
- Main,
- NextScript,
- DocumentProps,
- DocumentContext,
-} from 'next/document';
-import {
- DocumentHeadTags,
- DocumentHeadTagsProps,
- documentGetInitialProps,
-} from '@mui/material-nextjs/v14-pagesRouter';
-
-export default function Document(props: DocumentProps & DocumentHeadTagsProps) {
- return (
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-Document.getInitialProps = async (ctx: DocumentContext) => {
- const finalProps = await documentGetInitialProps(ctx);
- return finalProps;
-};
-```
-
-### 3. Add a dashboard page
-
-Create a dashboard page (e.g., `pages/index.tsx`):
-
-```tsx title="pages/index.tsx"
-import * as React from 'react';
-import Typography from '@mui/material/Typography';
-
-export default function HomePage() {
- return Welcome to Toolpad! ;
-}
-```
-
-### 4. (Optional) Add a second page
-
-Create a new page in the dashboard, for example, `pages/orders/index.tsx`:
-
-```tsx title="pages/orders/index.tsx"
-import * as React from 'react';
-import Typography from '@mui/material/Typography';
-
-export default function OrdersPage() {
- return Welcome to the orders page! ;
-}
-```
-
-To add this page to the navigation, add it to the `NAVIGATION` variable:
-
-```ts title="pages/_app.tsx"
-export const NAVIGATION = [
- // ...
- {
- segment: 'orders',
- title: 'Orders',
- icon: ,
- },
- // ...
-];
-```
-
-### 5. (Optional) Set up authentication
-
-If you want to add authentication, you can use Auth.js with Toolpad Core. Here's an example setup:
-
-#### a. Install the dependencies
-
-```bash
-npm install next-auth@beta
-```
-
-#### b. Create an `auth.ts` file
-
-```ts title="auth.ts"
-import NextAuth from 'next-auth';
-import GitHub from 'next-auth/providers/github';
-import type { Provider } from 'next-auth/providers';
-
-const providers: Provider[] = [
- GitHub({
- clientId: process.env.GITHUB_CLIENT_ID,
- clientSecret: process.env.GITHUB_CLIENT_SECRET,
- }),
-];
-
-export const providerMap = providers.map((provider) => {
- if (typeof provider === 'function') {
- const providerData = provider();
- return { id: providerData.id, name: providerData.name };
- }
- return { id: provider.id, name: provider.name };
-});
-
-export const { handlers, auth } = NextAuth({
- providers,
- secret: process.env.AUTH_SECRET,
- pages: {
- signIn: '/auth/signin',
- },
- callbacks: {
- authorized({ auth: session, request: { nextUrl } }) {
- const isLoggedIn = !!session?.user;
- const isPublicPage = nextUrl.pathname.startsWith('/public');
-
- if (isPublicPage || isLoggedIn) {
- return true;
- }
-
- return false; // Redirect unauthenticated users to login page
- },
- },
-});
-```
-
-#### c. Modify `_app.tsx`
-
-Modify `_app.tsx` to include the `authentication` prop and other helpers:
-
-```tsx title="pages/_app.tsx"
-import * as React from 'react';
-import { AppProvider } from '@toolpad/core/nextjs';
-import { DashboardLayout } from '@toolpad/core/DashboardLayout';
-import { PageContainer } from '@toolpad/core/PageContainer';
-import Head from 'next/head';
-import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter';
-import DashboardIcon from '@mui/icons-material/Dashboard';
-import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
-import type { NextPage } from 'next';
-import type { AppProps } from 'next/app';
-import type { Navigation } from '@toolpad/core/AppProvider';
-import { SessionProvider, signIn, signOut, useSession } from 'next-auth/react';
-import LinearProgress from '@mui/material/LinearProgress';
-
-export type NextPageWithLayout = NextPage
& {
- getLayout?: (page: React.ReactElement) => React.ReactNode;
- requireAuth?: boolean;
-};
-
-type AppPropsWithLayout = AppProps & {
- Component: NextPageWithLayout;
-};
-
-const NAVIGATION: Navigation = [
- {
- kind: 'header',
- title: 'Main items',
- },
- {
- segment: '',
- title: 'Dashboard',
- icon: ,
- },
- {
- segment: 'orders',
- title: 'Orders',
- icon: ,
- },
-];
-
-const BRANDING = {
- title: 'My Toolpad Core App',
-};
-
-const AUTHENTICATION = {
- signIn,
- signOut,
-};
-
-function getDefaultLayout(page: React.ReactElement) {
- return (
-
- {page}
-
- );
-}
-
-function RequireAuth({ children }: { children: React.ReactNode }) {
- const { status } = useSession();
-
- if (status === 'loading') {
- return ;
- }
-
- return children;
-}
-
-function AppLayout({ children }: { children: React.ReactNode }) {
- const { data: session } = useSession();
- return (
-
-
-
-
-
- {children}
-
-
- );
-}
-
-export default function App(props: AppPropsWithLayout) {
- const {
- Component,
- pageProps: { session, ...pageProps },
- } = props;
-
- const getLayout = Component.getLayout ?? getDefaultLayout;
- const requireAuth = Component.requireAuth ?? true;
-
- let pageContent = getLayout( );
- if (requireAuth) {
- pageContent = {pageContent} ;
- }
- pageContent = {pageContent} ;
-
- return (
-
- {pageContent}
-
- );
-}
-```
-
-#### d. Create a sign-in page
-
-Use the `SignInPage` component to add a sign-in page to your app. For example, `pages/auth/signin.tsx`:
-
-```tsx title="pages/auth/signin.tsx"
-import * as React from 'react';
-import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
-import Link from '@mui/material/Link';
-import { SignInPage } from '@toolpad/core/SignInPage';
-import { signIn } from 'next-auth/react';
-import { useRouter } from 'next/router';
-import { auth, providerMap } from '../../auth';
-
-export default function SignIn({
- providers,
-}: InferGetServerSidePropsType) {
- const router = useRouter();
- return (
- {
- try {
- const signInResponse = await signIn(provider.id, {
- callbackUrl: callbackUrl ?? '/',
- });
- if (signInResponse && signInResponse.error) {
- // Handle Auth.js errors
- return {
- error: signInResponse.error.message,
- type: signInResponse.error,
- };
- }
- return {};
- } catch (error) {
- // An error boundary must exist to handle unknown errors
- return {
- error: 'Something went wrong.',
- type: 'UnknownError',
- };
- }
- }}
- />
- );
-}
-
-SignIn.getLayout = (page: React.ReactNode) => page;
-
-SignIn.requireAuth = false;
-
-export async function getServerSideProps(context: GetServerSidePropsContext) {
- const session = await auth(context);
-
- // If the user is already logged in, redirect.
- // Note: Make sure not to redirect to the same page
- // To avoid an infinite loop!
- if (session) {
- return { redirect: { destination: '/' } };
- }
-
- return {
- props: {
- providers: providerMap,
- },
- };
-}
-```
-
-#### e. Create a route handler for sign-in
-
-`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth].ts`:
-
-```ts title="app/api/auth/[...nextauth].ts"
-import { handlers } from '../../../../auth';
-
-export const { GET, POST } = handlers;
-```
-
-:::warning
-
-Note that this file is a route handler and must be placed in the `app` directory, even if the rest of your app is in the `pages` directory. Know more in the [Auth.js documentation](https://authjs.dev/getting-started/installation#configure).
-
-:::
-
-#### f. Add a middleware
-
-Add a middleware to your app to protect your dashboard pages:
-
-```ts title="middleware.ts"
-export { auth as middleware } from './auth';
-
-export const config = {
- // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
- matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
-};
-```
-
-That's it! You now have Toolpad Core integrated into your Next.js Pages Router app with authentication setup:
-
-{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-pages.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-pages-dark.png", "alt": "Next.js Pages Router with Toolpad Core", "caption": "Next.js Pages Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }}
-
-:::info
-For a full working example with authentication included, see the [Toolpad Core Next.js Pages app with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core-auth-nextjs-pages)
-:::
-
-## React Router
-
-To integrate Toolpad Core into a single-page app (with [Vite](https://vite.dev/), for example) using **React Router**, follow these steps:
-
-### 1. Wrap all your pages in an `AppProvider`
-
-In your router configuration (e.g.: `src/main.tsx`), use a shared component or element (e.g.: `src/App.tsx`) as a root **layout route** that will wrap the whole application with the `AppProvider` from `@toolpad/core/react-router-dom`.
-
-You must use the ` ` component from `react-router-dom` in this root layout element or component.
-
-```tsx title="src/main.tsx"
-import * as React from 'react';
-import * as ReactDOM from 'react-dom/client';
-import { createBrowserRouter, RouterProvider } from 'react-router-dom';
-import App from './App';
-import DashboardPage from './pages';
-import OrdersPage from './pages/orders';
-
-const router = createBrowserRouter([
- {
- Component: App, // root layout route
- },
-]);
-
-ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
- ,
-);
-```
-
-```tsx title="src/App.tsx"
-import * as React from 'react';
-import DashboardIcon from '@mui/icons-material/Dashboard';
-import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
-import { AppProvider } from '@toolpad/core/react-router-dom';
-import { Outlet } from 'react-router-dom';
-import type { Navigation } from '@toolpad/core';
-
-const NAVIGATION: Navigation = [
- {
- kind: 'header',
- title: 'Main items',
- },
- {
- title: 'Dashboard',
- icon: ,
- },
- {
- segment: 'orders',
- title: 'Orders',
- icon: ,
- },
-];
-
-const BRANDING = {
- title: 'My Toolpad Core App',
-};
-
-export default function App() {
- return (
-
-
-
- );
-}
-```
-
-### 2. Create a dashboard layout
-
-Create a layout file for your dashboard pages (e.g.: `src/layouts/dashboard.tsx`), to also be used as a layout route with the ` ` component from `react-router-dom`:
-
-```tsx title="src/layouts/dashboard.tsx"
-import * as React from 'react';
-import { Outlet } from 'react-router-dom';
-import { DashboardLayout } from '@toolpad/core/DashboardLayout';
-import { PageContainer } from '@toolpad/core/PageContainer';
-
-export default function Layout() {
- return (
-
-
-
-
-
- );
-}
-```
-
-The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation.
-
-You can then add this layout component to your React Router configuration (e.g.: `src/main.tsx`), as a child of the root layout route created in step 1.
-
-```tsx title="src/main.tsx"
-import Layout from './layouts/dashboard';
-
-//...
-const router = createBrowserRouter([
- {
- Component: App, // root layout route
- children: [
- {
- path: '/',
- Component: Layout,
- },
- ],
- },
-]);
-//...
-```
-
-### 3. Create pages
-
-Create a dashboard page (e.g.: `src/pages/index.tsx`) and an orders page (`src/pages/orders.tsx`).
-
-```tsx title="src/pages/index.tsx"
-import * as React from 'react';
-import Typography from '@mui/material/Typography';
-
-export default function DashboardPage() {
- return Welcome to Toolpad! ;
-}
-```
-
-```tsx title="src/pages/orders.tsx"
-import * as React from 'react';
-import Typography from '@mui/material/Typography';
-
-export default function OrdersPage() {
- return Welcome to the Toolpad orders! ;
-}
-```
-
-You can then add these page components as routes to your React Router configuration (e.g.: `src/main.tsx`). By adding them as children of the layout route created in step 2, they will automatically be wrapped with that dashboard layout:
-
-```tsx title="src/main.tsx"
-import DashboardPage from './pages';
-import OrdersPage from './pages/orders';
-
-//...
-const router = createBrowserRouter([
- {
- Component: App, // root layout route
- children: [
- {
- path: '/',
- Component: Layout,
- children: [
- {
- path: '/',
- Component: DashboardPage,
- },
- {
- path: '/orders',
- Component: OrdersPage,
- },
- ],
- },
- ],
- },
-]);
-//...
-```
-
-That's it! You now have Toolpad Core integrated into your single-page app with React Router!
-
-{{"demo": "ReactRouter.js", "height": 500, "iframe": true, "hideToolbar": true}}
-
-:::info
-For a full working example, see the [Toolpad Core Vite app with React Router example](https://github.com/mui/toolpad/tree/master/examples/core-vite)
-:::
diff --git a/docs/data/toolpad/core/introduction/tutorial.md b/docs/data/toolpad/core/introduction/tutorial.md
index f777fc246f7..2da0269d33c 100644
--- a/docs/data/toolpad/core/introduction/tutorial.md
+++ b/docs/data/toolpad/core/introduction/tutorial.md
@@ -15,15 +15,15 @@ title: Tutorial
```bash npm
-npx create-toolpad-app@latest --example core-tutorial
+npx create-toolpad-app@latest --example tutorial
```
```bash pnpm
-pnpm dlx create toolpad-app --example core-tutorial
+pnpm dlx create toolpad-app --example tutorial
```
```bash yarn
-yarn create toolpad-app --example core-tutorial
+yarn create toolpad-app --example tutorial
```
@@ -79,7 +79,7 @@ export default function Home() {
```tsx title="app/layout.tsx"
// Add the following import:
-import TimelineIcon from '@mui/icons-material/TimelineIcon';
+import TimelineIcon from '@mui/icons-material/Timeline';
// ...
diff --git a/docs/data/toolpad/core/pages.ts b/docs/data/toolpad/core/pages.ts
index e0fb156d619..4e8d6831433 100644
--- a/docs/data/toolpad/core/pages.ts
+++ b/docs/data/toolpad/core/pages.ts
@@ -20,10 +20,6 @@ const pages: MuiPage[] = [
pathname: '/toolpad/core/introduction/base-concepts',
title: 'Base concepts',
},
- {
- pathname: '/toolpad/core/introduction/integration',
- title: 'Integration',
- },
{
pathname: '/toolpad/core/introduction/tutorial',
title: 'Tutorial',
@@ -31,6 +27,7 @@ const pages: MuiPage[] = [
{
pathname: '/toolpad/core/introduction/examples',
title: 'Examples',
+ newFeature: true,
},
{
pathname: '/toolpad/core/introduction/roadmap',
@@ -42,6 +39,24 @@ const pages: MuiPage[] = [
},
],
},
+ {
+ pathname: '/toolpad/core/integrations-group',
+ title: 'Integrations',
+ children: [
+ {
+ pathname: '/toolpad/core/integrations/nextjs-approuter',
+ title: 'Next.js App Router',
+ },
+ {
+ pathname: '/toolpad/core/integrations/nextjs-pagesrouter',
+ title: 'Next.js Pages Router',
+ },
+ {
+ pathname: '/toolpad/core/integrations/react-router',
+ title: 'Vite with React Router',
+ },
+ ],
+ },
{
pathname: '/toolpad/core/components-group',
title: 'Components',
@@ -106,7 +121,7 @@ const pages: MuiPage[] = [
children: [
{
pathname: '/toolpad/core/react-crud-page',
- title: 'CRUD Page',
+ title: 'CRUD',
planned: true,
},
{
@@ -128,6 +143,10 @@ const pages: MuiPage[] = [
pathname: '/toolpad/core/react-use-notifications',
title: 'useNotifications',
},
+ {
+ pathname: '/toolpad/core/react-use-session',
+ title: 'useSession',
+ },
{
pathname: '/toolpad/core/react-persistent-state',
title: 'Persisted state',
@@ -161,6 +180,10 @@ const pages: MuiPage[] = [
pathname: '/toolpad/core/react-persistent-state/use-local-storage-state-api',
title: 'useLocalStorageState',
},
+ {
+ pathname: '/toolpad/core/react-use-session/api',
+ title: 'useSession',
+ },
{
pathname: '/toolpad/core/react-persistent-state/use-session-storage-state-api',
title: 'useSessionStorageState',
diff --git a/docs/data/toolpad/core/pagesApi.js b/docs/data/toolpad/core/pagesApi.js
index 346ba5b4a23..c36b8e189db 100644
--- a/docs/data/toolpad/core/pagesApi.js
+++ b/docs/data/toolpad/core/pagesApi.js
@@ -1,10 +1,18 @@
module.exports = [
{ pathname: '/toolpad/core/api/account' },
+ { pathname: '/toolpad/core/api/account-popover-footer' },
+ { pathname: '/toolpad/core/api/account-popover-header' },
+ { pathname: '/toolpad/core/api/account-preview' },
{ pathname: '/toolpad/core/api/app-provider' },
{ pathname: '/toolpad/core/api/dashboard-layout' },
{ pathname: '/toolpad/core/api/dialogs-provider' },
{ pathname: '/toolpad/core/api/notifications-provider' },
{ pathname: '/toolpad/core/api/page-container' },
- { pathname: '/toolpad/core/api/page-container-toolbar' },
+ { pathname: '/toolpad/core/api/page-header' },
+ { pathname: '/toolpad/core/api/page-header-toolbar' },
+ { pathname: '/toolpad/core/api/sign-in-button' },
{ pathname: '/toolpad/core/api/sign-in-page' },
+ { pathname: '/toolpad/core/api/sign-out-button' },
+ { pathname: '/toolpad/core/api/theme-switcher' },
+ { pathname: '/toolpad/core/api/toolbar-actions' },
];
diff --git a/docs/data/toolpad/studio/components/button/button.md b/docs/data/toolpad/studio/components/button/button.md
index f8fa061b407..403820def36 100644
--- a/docs/data/toolpad/studio/components/button/button.md
+++ b/docs/data/toolpad/studio/components/button/button.md
@@ -17,7 +17,7 @@ Button component becomes usable through an **onClick** event handler property. I
You can write any valid JavaScript that you want to execute on the click of button. It can change component state or call backend code.
Below video shows how on a button click, the user input can be shown on a page in a text field component:
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/components/data-grid/data-grid.md b/docs/data/toolpad/studio/components/data-grid/data-grid.md
index 06da60b1ad2..30229dd94a2 100644
--- a/docs/data/toolpad/studio/components/data-grid/data-grid.md
+++ b/docs/data/toolpad/studio/components/data-grid/data-grid.md
@@ -16,7 +16,7 @@ A Data Grid is an essential component in an internal application. The following
Rows property requires an array of data to show inside the data grid. It can be configured either by clicking on it and providing JSON or by binding it to a query output. The video below shows how to bind data to data grid using rows linking:
-
+
Your browser does not support the video tag.
@@ -26,7 +26,7 @@ Rows property requires an array of data to show inside the data grid. It can be
Columns property is used to configure the columns to be displayed. Choose a column and you'll get a pop-up window to configure its header name, width, alignment and data type.
From the `type` drop down you can also [customize a column](/toolpad/studio/how-to-guides/customize-datagrid/).
-
+
Your browser does not support the video tag.
@@ -39,7 +39,7 @@ Id field property is used to identify which column contains the id column. By de
This property shows the currently selected row or `null` in case no row has been selected. It is available to be bound to take any action on the selected row like [deleting a row](/toolpad/studio/how-to-guides/delete-datagrid-row/) from data grid.
-
+
Your browser does not support the video tag.
@@ -48,7 +48,7 @@ This property shows the currently selected row or `null` in case no row has been
Loading property is used to to inform the user when the data is being prepared. It can be [bound to](/toolpad/studio/how-to-guides/delete-datagrid-row/#configure-loading-states-optional) React query properties like isFetching, isLoading.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/components/date-picker/date-picker.md b/docs/data/toolpad/studio/components/date-picker/date-picker.md
index dd25237563e..06168dff97b 100644
--- a/docs/data/toolpad/studio/components/date-picker/date-picker.md
+++ b/docs/data/toolpad/studio/components/date-picker/date-picker.md
@@ -26,7 +26,7 @@ The current selected date. It shows the format in which it is being provided to
Allows setting a default value. Example:
-
+
Your browser does not support the video tag.
@@ -71,7 +71,7 @@ Disabled property shows the state of the component so that end user is aware tha
isRequired is useful when the action can't be perfomed without a user provided date.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/components/list/list.md b/docs/data/toolpad/studio/components/list/list.md
index 8cf3c7f2fa0..87fc99c100e 100644
--- a/docs/data/toolpad/studio/components/list/list.md
+++ b/docs/data/toolpad/studio/components/list/list.md
@@ -10,7 +10,7 @@ A List is an essential component in an application. It had one key property that
It is the number of occurences that the list is supposed to render.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/components/text-field/text-field.md b/docs/data/toolpad/studio/components/text-field/text-field.md
index f77fdd9b699..bb65e7e1d3c 100644
--- a/docs/data/toolpad/studio/components/text-field/text-field.md
+++ b/docs/data/toolpad/studio/components/text-field/text-field.md
@@ -12,7 +12,7 @@ TextField is a text input component. It takes user input and provides the value
It is one of the most used input component. The video below uses some props to demonstrate its usage.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/building-ui.md b/docs/data/toolpad/studio/concepts/building-ui.md
index b62f7cf480d..cce64d888da 100644
--- a/docs/data/toolpad/studio/concepts/building-ui.md
+++ b/docs/data/toolpad/studio/concepts/building-ui.md
@@ -6,7 +6,7 @@
To access the list of available Toolpad Studio components, hover your cursor over the vertical bar labeled **Component library** to expand it.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/custom-components.md b/docs/data/toolpad/studio/concepts/custom-components.md
index e54237535b0..199544c5768 100644
--- a/docs/data/toolpad/studio/concepts/custom-components.md
+++ b/docs/data/toolpad/studio/concepts/custom-components.md
@@ -15,7 +15,7 @@ The steps below explain how to create and use a custom component in Toolpad Stud
In case it doesn't open, check [troubleshoot missing editor](https://mui.com/toolpad/studio/how-to-guides/editor-path/).
:::
-
+
Your browser does not support the video tag.
@@ -89,7 +89,7 @@ export default createComponent(HelloWorld, {
This component, when dragged to the canvas, has a **msg** property that you can't bind to. But you can now bind to this property from other components.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/custom-functions.md b/docs/data/toolpad/studio/concepts/custom-functions.md
index 0fd2058d389..8f94ec716d0 100644
--- a/docs/data/toolpad/studio/concepts/custom-functions.md
+++ b/docs/data/toolpad/studio/concepts/custom-functions.md
@@ -4,7 +4,7 @@
The most powerful way of bringing data into Toolpad Studio is through your own code. You can define functions inside `toolpad/resources` and use them when creating a query of this type. The following video shows how you can use this feature to read data from PostgreSQL.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/custom-server.md b/docs/data/toolpad/studio/concepts/custom-server.md
index debb51c182a..4a3ec5389ed 100644
--- a/docs/data/toolpad/studio/concepts/custom-server.md
+++ b/docs/data/toolpad/studio/concepts/custom-server.md
@@ -9,7 +9,7 @@ An improved integration method is being worked on. You'll be able to run Toolpad
The Toolpad Studio `dev` command comes with its own built-in server. However, sometimes you'd want more control over the way Toolpad Studio applications are hosted within your application. The Toolpad Studio custom server integration API allows you to run a Toolpad Studio application programmatically within an existing node.js server.
:::info
-Check out the [example on GitHub](https://github.com/mui/toolpad/tree/master/examples/custom-server) for a full demonstration of how to set up a custom server integration.
+Check out the [example on GitHub](https://github.com/mui/toolpad/tree/master/examples/studio/custom-server/) for a full demonstration of how to set up a custom server integration.
:::
The following code illustrates how it works:
diff --git a/docs/data/toolpad/studio/concepts/data-binding.md b/docs/data/toolpad/studio/concepts/data-binding.md
index 9f4a3f48d1c..e0e8a6f6a21 100644
--- a/docs/data/toolpad/studio/concepts/data-binding.md
+++ b/docs/data/toolpad/studio/concepts/data-binding.md
@@ -26,7 +26,7 @@ You can change the format of a value like:
You can write strings inside backticks (`) to write embedded expressions and multiline text. These can be used to read values from other components.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/data-providers.md b/docs/data/toolpad/studio/concepts/data-providers.md
index 69784b907f0..fb5fc4afed1 100644
--- a/docs/data/toolpad/studio/concepts/data-providers.md
+++ b/docs/data/toolpad/studio/concepts/data-providers.md
@@ -6,7 +6,7 @@ Toolpad Studio functions are great to bring some backend state to the page, but
Follow these steps to create a new data provider:
-
+
Your browser does not support the video tag.
@@ -25,7 +25,7 @@ export default createDataProvider({
});
```
-
+
Your browser does not support the video tag.
@@ -100,7 +100,7 @@ For example, this could print the following if the corresponding column filters
Now the backend function receives the grid filter from the UI in its parameters.
-
+
Your browser does not support the video tag.
@@ -129,7 +129,7 @@ Depending on which column has been set to sort by, this results in:
Now the backend function receives the grid sorting model from the UI in its parameters.
-
+
Your browser does not support the video tag.
@@ -156,7 +156,7 @@ export default createDataProvider({
When this method is available in the data provider, each row gets an edit button. This edit button brings the row in edit mode. To commit the changes press the save button on the row that is in edit mode. To discard the changes use the cancel button.
-
+
Your browser does not support the video tag.
@@ -183,7 +183,7 @@ export default createDataProvider({
After you make this method available in the data provider, an "Add record" button appears in the data grid toolbar. Click this button and a new editable row appears at the top of the grid. Fill in the values and click the "Save" button to finish creating the row. You'll have to return the newly created row from the `createRecord` method so that the grid can update accordingly.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/deployment.md b/docs/data/toolpad/studio/concepts/deployment.md
index 6ad3c392bad..f217eb9dd51 100644
--- a/docs/data/toolpad/studio/concepts/deployment.md
+++ b/docs/data/toolpad/studio/concepts/deployment.md
@@ -26,6 +26,10 @@ Install required depdencies via:
npm install
```
+```bash pnpm
+pnpm install
+```
+
```bash yarn
yarn
```
@@ -42,14 +46,14 @@ This command will create an optimized production build for the Toolpad Studio ap
npm run build
```
-```bash yarn
-yarn build
-```
-
```bash pnpm
pnpm build
```
+```bash yarn
+yarn build
+```
+
## Start step
@@ -64,6 +68,10 @@ To serve the app once built, run:
npm run start
```
+```bash pnpm
+pnpm start
+```
+
```bash yarn
yarn start
```
diff --git a/docs/data/toolpad/studio/concepts/event-handling.md b/docs/data/toolpad/studio/concepts/event-handling.md
index 0fede225db6..3a1009debb1 100644
--- a/docs/data/toolpad/studio/concepts/event-handling.md
+++ b/docs/data/toolpad/studio/concepts/event-handling.md
@@ -10,7 +10,7 @@ Some event handling scenarios:
### Change component state
-
+
Your browser does not support the video tag.
@@ -25,7 +25,7 @@ You can call single or multiple queries sequentially to fetch data on the page o
#### Multiple queries
-
+
Your browser does not support the video tag.
@@ -38,7 +38,7 @@ See the [deleting data grid row](/toolpad/studio/how-to-guides/delete-datagrid-r
Custom functions allow you to write custom code in your editor. You can write a `console.log` statement that interacts with the data on the page and prints logs.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/file-structure.md b/docs/data/toolpad/studio/concepts/file-structure.md
index c05dd46e621..993157f61f3 100644
--- a/docs/data/toolpad/studio/concepts/file-structure.md
+++ b/docs/data/toolpad/studio/concepts/file-structure.md
@@ -1,6 +1,6 @@
# File structure
-Toolpad Studio's accessibility to the file-system makes it powerful. Here's how:
+Toolpad Studio's accessibility to the file-system makes it powerful. Here's how.
Toolpad Studio is file-system based, which means that an app's entire configuration is stored in files within your project. You can inspect and edit them from your IDE. You'll also use your own tools to author custom functions and components. This is how the project structure looks like in the file-system:
diff --git a/docs/data/toolpad/studio/concepts/http-requests.md b/docs/data/toolpad/studio/concepts/http-requests.md
index dcadf0208c8..e961f88152a 100644
--- a/docs/data/toolpad/studio/concepts/http-requests.md
+++ b/docs/data/toolpad/studio/concepts/http-requests.md
@@ -65,7 +65,7 @@ You can define these in the interface available in the HTTP Request query editor
As Toolpad Studio HTTP requests are running server-side, they can make use of the available secrets that are stored in environment variables. The mechanism works similar by defining a parameter and instead of binding it to a UI value, bind it to an environment variable. Whenever the request executes, Toolpad Studio will feed the value of the environment variable to the parameter.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/page-properties.md b/docs/data/toolpad/studio/concepts/page-properties.md
index 4b0a3aea38b..31ce9a23625 100644
--- a/docs/data/toolpad/studio/concepts/page-properties.md
+++ b/docs/data/toolpad/studio/concepts/page-properties.md
@@ -45,7 +45,7 @@ Available values are **xs**, **sm**, **md**, **lg**, **xl**, or **None** to conf
Page parameters allow you to pass external data into the Toolpad Studio page state via the URL query parameters.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/concepts/theming.md b/docs/data/toolpad/studio/concepts/theming.md
index ac201358a07..a8a51543233 100644
--- a/docs/data/toolpad/studio/concepts/theming.md
+++ b/docs/data/toolpad/studio/concepts/theming.md
@@ -4,7 +4,7 @@
## Adding a global theme
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/getting-started/examples-overview.md b/docs/data/toolpad/studio/getting-started/examples-overview.md
index 95335641cf4..575ca15647c 100644
--- a/docs/data/toolpad/studio/getting-started/examples-overview.md
+++ b/docs/data/toolpad/studio/getting-started/examples-overview.md
@@ -5,12 +5,10 @@ title: Examples
# Overview
-Browse our collection of Toolpad Studio examples that help you familiarise with Toolpad Studio concepts.
-
-
+Browse a collection of Toolpad Studio examples that help you familiarise with Toolpad Studio concepts.
This collection includes apps that showcase connecting to APIs, adding custom components, adding secrets, adding authentication, working with ORMs and more.
If you're interested in how we, at MUI, use Toolpad Studio to build internal apps, check out the [blog post](https://mui.com/blog/toolpad-use-cases/).
-{{"component": "modules/components/ExamplesGrid/ExamplesGrid.tsx", "examplesFile": "studio-examples.ts"}}
+{{"component": "modules/components/examples/StudioExamples.tsx"}}
diff --git a/docs/data/toolpad/studio/getting-started/installation.md b/docs/data/toolpad/studio/getting-started/installation.md
index c177d3ff2f5..57c9c909d38 100644
--- a/docs/data/toolpad/studio/getting-started/installation.md
+++ b/docs/data/toolpad/studio/getting-started/installation.md
@@ -13,14 +13,14 @@ Then run the command:
npx create-toolpad-app@latest --studio my-toolpad-app
```
-```bash yarn
-yarn create toolpad-app --studio my-toolpad-app
-```
-
```bash pnpm
pnpm create toolpad-app --studio my-toolpad-app
```
+```bash yarn
+yarn create toolpad-app --studio my-toolpad-app
+```
+
This will run the `create-toolpad-app` CLI which initializes the directory `./my-toolpad-app` with a Toolpad Studio application.
@@ -41,14 +41,14 @@ Then start the development mode
npm run dev
```
-```bash yarn
-yarn dev
-```
-
```bash pnpm
pnpm dev
```
+```bash yarn
+yarn dev
+```
+
This starts the development server on port `3000` or the first available port after that and opens the browser to the Toolpad Studio editor.
@@ -60,17 +60,17 @@ Start by installing the required dependencies:
```bash npm
-npm install -S @toolpad/studio
-```
-
-```bash yarn
-yarn add @toolpad/studio
+npm install @toolpad/studio
```
```bash pnpm
pnpm add @toolpad/studio
```
+```bash yarn
+yarn add @toolpad/studio
+```
+
Then you'll have to add the Toolpad Studio scripts to your `package.json`:
@@ -94,14 +94,14 @@ Now you can start your Toolpad Studio application using one of the commands:
npm run toolpad-studio:dev
```
-```bash yarn
-yarn toolpad-studio:dev
-```
-
```bash pnpm
pnpm toolpad-studio:dev
```
+```bash yarn
+yarn toolpad-studio:dev
+```
+
When you run this command, Toolpad Studio will automatically initialize a new application in the **./my-toolpad-app** folder.
diff --git a/docs/data/toolpad/studio/how-to-guides/connect-to-googlesheets.md b/docs/data/toolpad/studio/how-to-guides/connect-to-googlesheets.md
index 40046677dd1..79148adda99 100644
--- a/docs/data/toolpad/studio/how-to-guides/connect-to-googlesheets.md
+++ b/docs/data/toolpad/studio/how-to-guides/connect-to-googlesheets.md
@@ -6,7 +6,7 @@ You can write a custom function to read or write data from a Google sheet. We'll
There are many ways to authenticate Google APIs, as mentioned in `google-auth-library`. This guide uses JWTs (JSON Web Tokens), which are appropriate for a server-based application.
-
+
Your browser does not support the video tag.
diff --git a/docs/data/toolpad/studio/how-to-guides/embed-pages.md b/docs/data/toolpad/studio/how-to-guides/embed-pages.md
index abe8f98bc88..55d326f371c 100644
--- a/docs/data/toolpad/studio/how-to-guides/embed-pages.md
+++ b/docs/data/toolpad/studio/how-to-guides/embed-pages.md
@@ -18,9 +18,9 @@
using
```html
-
+
```
you can embed a Toolpad Studio page, like so:
-
+
diff --git a/docs/data/toolpad/studio/how-to-guides/railway-deploy.md b/docs/data/toolpad/studio/how-to-guides/railway-deploy.md
index 1c06c6ce0ed..d13861161cd 100644
--- a/docs/data/toolpad/studio/how-to-guides/railway-deploy.md
+++ b/docs/data/toolpad/studio/how-to-guides/railway-deploy.md
@@ -6,7 +6,7 @@ This guide walks you through the deployment of a Toolpad Studio app from a GitHu
## Prerequisites
-- A [Railway](https://railway.app/) account
+- A [Railway](https://railway.com/) account
- A [GitHub](https://github.com) account
- A GitHub repository containing your Toolpad Studio app. Check out [pushing your Toolpad Studio app to GitHub](/toolpad/studio/how-to-guides/render-deploy/#pushing-your-toolpad-studio-app-to-github) for this step.
@@ -49,7 +49,7 @@ This guide walks you through the deployment of a Toolpad Studio app from a GitHu
That's it! The app is up and running. Make changes, push to GitHub, and your app automatically redeploys each time. You may deploy to any other hosting provider of your choice as well.
-Check out the Railway documentation for more advanced settings, like adding [variables](https://docs.railway.app/guides/variables) to your app.
+Check out the Railway documentation for more advanced settings, like adding [variables](https://docs.railway.com/guides/variables) to your app.
### Common Pitfalls
diff --git a/docs/data/toolpad/studio/how-to-guides/render-deploy.md b/docs/data/toolpad/studio/how-to-guides/render-deploy.md
index 19bc2d9fd62..52abfcadab2 100644
--- a/docs/data/toolpad/studio/how-to-guides/render-deploy.md
+++ b/docs/data/toolpad/studio/how-to-guides/render-deploy.md
@@ -78,7 +78,7 @@
:::info
-See the [Render documentation](https://docs.render.com/node-version) on Node versions for more information.
+See the [Render documentation](https://render.com/docs/node-version) on Node versions for more information.
:::
@@ -92,4 +92,4 @@ That's it! We're up and running in a few minutes.
Make changes, push to GitHub, and your app automatically redeploys each time. You may deploy to any other hosting provider of your choice as well.
-Check out the Render documentation for more advanced settings, like adding [environment variables](https://docs.render.com/configure-environment-variables) to your app.
+Check out the Render documentation for more advanced settings, like adding [environment variables](https://render.com/docs/configure-environment-variables) to your app.
diff --git a/docs/data/toolpad/studio/index.md b/docs/data/toolpad/studio/index.md
index 2aaaedb27fa..b8d2322b975 100644
--- a/docs/data/toolpad/studio/index.md
+++ b/docs/data/toolpad/studio/index.md
@@ -4,6 +4,6 @@ title: Toolpad Studio docs - Home
# Toolpad Studio
-Toolpad Studio docs
+Toolpad Studio docs.
Low code tool, for developers, powered by MUI.
diff --git a/docs/next-env.d.ts b/docs/next-env.d.ts
index a4a7b3f5cfa..52e831b4342 100644
--- a/docs/next-env.d.ts
+++ b/docs/next-env.d.ts
@@ -2,4 +2,4 @@
///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
+// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
diff --git a/docs/package.json b/docs/package.json
index 897b0a3bf22..7d3f04039c8 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,11 +1,11 @@
{
"name": "docs",
- "version": "0.7.0",
+ "version": "0.12.0",
"private": true,
"author": "MUI Toolpad",
"license": "MIT",
"scripts": {
- "build": "rimraf docs/export && cross-env NODE_ENV=production next build --profile && pnpm build-sw",
+ "build": "rimraf ./export && cross-env NODE_ENV=production next build --profile && pnpm build-sw",
"build:clean": "rimraf .next && pnpm build",
"build-sw": "node ./scripts/buildServiceWorker.js",
"dev": "next dev --port 3003",
@@ -16,33 +16,32 @@
"typescript:transpile:dev": "cross-env BABEL_ENV=development babel-node --extensions \".tsx,.ts,.js\" scripts/formattedTSDemos --watch"
},
"dependencies": {
- "@babel/plugin-transform-object-assign": "7.25.7",
- "@babel/runtime-corejs2": "7.25.7",
+ "@babel/plugin-transform-object-assign": "7.25.9",
+ "@babel/runtime-corejs2": "7.26.0",
"@date-io/date-fns-jalali": "3.1.1",
- "@docsearch/react": "3.6.2",
- "@emotion/cache": "11.13.1",
- "@emotion/react": "11.13.3",
+ "@docsearch/react": "3.8.2",
+ "@emotion/cache": "11.14.0",
+ "@emotion/react": "11.14.0",
"@emotion/server": "11.11.0",
- "@emotion/styled": "11.13.0",
- "@mui/base": "5.0.0-beta.58",
- "@mui/docs": "6.1.3",
- "@mui/icons-material": "6.1.3",
- "@mui/internal-markdown": "^1.0.16",
- "@mui/joy": "5.0.0-beta.48",
- "@mui/lab": "6.0.0-beta.11",
- "@mui/material": "6.1.3",
- "@mui/material-nextjs": "6.1.3",
- "@mui/styles": "6.1.3",
- "@mui/system": "6.1.3",
- "@mui/utils": "6.1.3",
- "@mui/x-date-pickers": "7.21.0",
- "@mui/x-date-pickers-pro": "7.21.0",
- "@mui/x-license": "7.21.0",
+ "@emotion/styled": "11.14.0",
+ "@mui/base": "5.0.0-beta.68",
+ "@mui/docs": "6.3.1",
+ "@mui/icons-material": "6.3.1",
+ "@mui/internal-markdown": "1.0.24",
+ "@mui/joy": "5.0.0-beta.51",
+ "@mui/lab": "6.0.0-beta.22",
+ "@mui/material": "6.3.1",
+ "@mui/material-nextjs": "6.3.1",
+ "@mui/styles": "6.3.1",
+ "@mui/system": "6.3.1",
+ "@mui/utils": "6.3.1",
+ "@mui/x-date-pickers": "7.23.6",
+ "@mui/x-date-pickers-pro": "7.23.6",
+ "@mui/x-license": "7.23.6",
"@toolpad/core": "workspace:*",
"@toolpad/studio": "workspace:*",
"@trendmicro/react-interpolate": "0.5.5",
- "@types/lodash": "4.17.10",
- "@types/react-router-dom": "5.3.3",
+ "@types/lodash": "4.17.14",
"ast-types": "0.14.2",
"autoprefixer": "10.4.20",
"babel-plugin-module-resolver": "5.0.2",
@@ -66,25 +65,24 @@
"jss-rtl": "0.3.0",
"lodash": "4.17.21",
"lz-string": "1.5.0",
- "markdown-to-jsx": "7.5.0",
- "next": "^14.2.15",
+ "markdown-to-jsx": "7.7.3",
+ "next": "^15.1.4",
"nprogress": "0.2.0",
- "postcss": "8.4.47",
+ "postcss": "8.4.49",
"prismjs": "1.29.0",
"prop-types": "15.8.1",
- "react": "18.3.1",
- "react-docgen": "7.0.3",
- "react-dom": "18.3.1",
- "react-hook-form": "7.53.0",
- "react-is": "18.3.1",
- "react-router": "6.26.2",
- "react-router-dom": "6.26.2",
+ "react": "^19.0.0",
+ "react-docgen": "5.4.3",
+ "react-dom": "^19.0.0",
+ "react-hook-form": "7.53.2",
+ "react-is": "^19.0.0",
+ "react-router": "7.1.0",
"react-runner": "1.0.5",
"react-simple-code-editor": "0.14.1",
"react-simple-typewriter": "5.0.1",
"react-transition-group": "4.4.5",
"recast": "0.23.9",
- "rimraf": "5.0.10",
+ "rimraf": "6.0.1",
"styled-components": "6.1.13",
"stylis": "4.3.4",
"stylis-plugin-rtl": "npm:stylis-plugin-rtl@^2.1.1",
@@ -93,20 +91,19 @@
"zod": "3.23.8"
},
"devDependencies": {
- "@babel/core": "7.25.8",
- "@babel/plugin-transform-react-constant-elements": "7.25.7",
- "@babel/preset-typescript": "7.25.7",
+ "@babel/core": "7.26.0",
+ "@babel/plugin-transform-react-constant-elements": "7.25.9",
+ "@babel/preset-typescript": "7.26.0",
"@types/babel__core": "^7.20.5",
"@types/doctrine": "0.0.9",
"@types/json-schema": "7.0.15",
- "@types/react": "18.3.11",
- "@types/react-dom": "18.3.1",
- "@types/react-is": "18.3.0",
- "@types/react-swipeable-views": "^0.13.5",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.3",
+ "@types/react-is": "^19.0.0",
+ "@types/react-swipeable-views": "^0.13.6",
"cpy-cli": "5.0.0",
"cross-fetch": "4.0.0",
- "gm": "1.25.0",
- "typescript": "5.6.2",
+ "typescript": "5.5.4",
"typescript-to-proptypes": "2.2.1"
}
}
diff --git a/docs/pages/_app.js b/docs/pages/_app.js
index 943506c9b4b..32d8b1aa307 100644
--- a/docs/pages/_app.js
+++ b/docs/pages/_app.js
@@ -19,6 +19,7 @@ import findActivePage from 'docs/src/modules/utils/findActivePage';
import { pathnameToLanguage } from 'docs/src/modules/utils/helpers';
import getProductInfoFromUrl from 'docs/src/modules/utils/getProductInfoFromUrl';
import { DocsProvider } from '@mui/docs/DocsProvider';
+import { mapTranslations } from '@mui/docs/i18n';
import toolpadStudioPages from '../data/toolpad/studio/pages';
import toolpadCorePages from '../data/toolpad/core/pages';
import config from '../config';
@@ -265,7 +266,11 @@ function AppWrapper(props) {
-
+
@@ -310,6 +315,9 @@ MyApp.propTypes = {
MyApp.getInitialProps = async ({ ctx, Component }) => {
let pageProps = {};
+ const req = require.context('docs-toolpad/translations', false, /\.\/translations.*\.json$/);
+ const translations = mapTranslations(req);
+
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
@@ -317,6 +325,7 @@ MyApp.getInitialProps = async ({ ctx, Component }) => {
return {
pageProps: {
userLanguage: ctx.query.userLanguage || 'en',
+ translations,
...pageProps,
},
};
diff --git a/docs/pages/toolpad/core/api/page-container-toolbar.js b/docs/pages/toolpad/core/api/account-popover-footer.js
similarity index 76%
rename from docs/pages/toolpad/core/api/page-container-toolbar.js
rename to docs/pages/toolpad/core/api/account-popover-footer.js
index c34ccdc8262..c3052dec403 100644
--- a/docs/pages/toolpad/core/api/page-container-toolbar.js
+++ b/docs/pages/toolpad/core/api/account-popover-footer.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
-import jsonPageContent from './page-container-toolbar.json';
+import jsonPageContent from './account-popover-footer.json';
export default function Page(props) {
const { descriptions, pageContent } = props;
@@ -10,9 +10,9 @@ export default function Page(props) {
Page.getInitialProps = () => {
const req = require.context(
- 'docs-toolpad/translations/api-docs/page-container-toolbar',
+ 'docs-toolpad/translations/api-docs/account-popover-footer',
false,
- /\.\/page-container-toolbar.*.json$/,
+ /\.\/account-popover-footer.*.json$/,
);
const descriptions = mapApiPageTranslations(req);
diff --git a/docs/pages/toolpad/core/api/account-popover-footer.json b/docs/pages/toolpad/core/api/account-popover-footer.json
new file mode 100644
index 00000000000..a10949e29d7
--- /dev/null
+++ b/docs/pages/toolpad/core/api/account-popover-footer.json
@@ -0,0 +1,19 @@
+{
+ "props": {
+ "sx": {
+ "type": {
+ "name": "union",
+ "description": "Array<func | object | bool> | func | object"
+ },
+ "additionalInfo": { "sx": true }
+ }
+ },
+ "name": "AccountPopoverFooter",
+ "imports": ["import { AccountPopoverFooter } from '@toolpad/core/Account';"],
+ "classes": [],
+ "muiName": "AccountPopoverFooter",
+ "filename": "/packages/toolpad-core/src/Account/AccountPopoverFooter.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/account-popover-header.js b/docs/pages/toolpad/core/api/account-popover-header.js
new file mode 100644
index 00000000000..4206c8ca54a
--- /dev/null
+++ b/docs/pages/toolpad/core/api/account-popover-header.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './account-popover-header.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/account-popover-header',
+ false,
+ /\.\/account-popover-header.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/account-popover-header.json b/docs/pages/toolpad/core/api/account-popover-header.json
new file mode 100644
index 00000000000..1289db98b2e
--- /dev/null
+++ b/docs/pages/toolpad/core/api/account-popover-header.json
@@ -0,0 +1,11 @@
+{
+ "props": { "children": { "type": { "name": "node" } } },
+ "name": "AccountPopoverHeader",
+ "imports": ["import { AccountPopoverHeader } from '@toolpad/core/Account';"],
+ "classes": [],
+ "muiName": "AccountPopoverHeader",
+ "filename": "/packages/toolpad-core/src/Account/AccountPopoverHeader.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/account-preview.js b/docs/pages/toolpad/core/api/account-preview.js
new file mode 100644
index 00000000000..44cb65528d1
--- /dev/null
+++ b/docs/pages/toolpad/core/api/account-preview.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './account-preview.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/account-preview',
+ false,
+ /\.\/account-preview.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/account-preview.json b/docs/pages/toolpad/core/api/account-preview.json
new file mode 100644
index 00000000000..724124fc863
--- /dev/null
+++ b/docs/pages/toolpad/core/api/account-preview.json
@@ -0,0 +1,60 @@
+{
+ "props": {
+ "handleClick": { "type": { "name": "func" } },
+ "open": { "type": { "name": "bool" }, "default": "false" },
+ "slotProps": {
+ "type": {
+ "name": "shape",
+ "description": "{ avatar?: object, avatarIconButton?: object, moreIconButton?: object }"
+ }
+ },
+ "slots": {
+ "type": {
+ "name": "shape",
+ "description": "{ avatar?: elementType, avatarIconButton?: elementType, moreIconButton?: elementType }"
+ },
+ "additionalInfo": { "slotsApi": true }
+ },
+ "sx": {
+ "type": {
+ "name": "union",
+ "description": "Array<func | object | bool> | func | object"
+ },
+ "additionalInfo": { "sx": true }
+ },
+ "variant": {
+ "type": { "name": "enum", "description": "'condensed' | 'expanded'" },
+ "default": "'condensed'"
+ }
+ },
+ "name": "AccountPreview",
+ "imports": ["import { AccountPreview } from '@toolpad/core/Account';"],
+ "slots": [
+ {
+ "name": "avatar",
+ "description": "The component used for the Avatar",
+ "default": "Avatar",
+ "class": null
+ },
+ {
+ "name": "moreIconButton",
+ "description": "The component used for the overflow icon button in the expanded variant",
+ "default": "IconButton",
+ "class": null
+ },
+ {
+ "name": "avatarIconButton",
+ "description": "The component used for the avatar icon button in the condensed variant",
+ "default": "IconButton",
+ "class": null
+ }
+ ],
+ "classes": [],
+ "spread": true,
+ "themeDefaultProps": null,
+ "muiName": "AccountPreview",
+ "filename": "/packages/toolpad-core/src/Account/AccountPreview.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/account.json b/docs/pages/toolpad/core/api/account.json
index 16f12682220..b762be1d771 100644
--- a/docs/pages/toolpad/core/api/account.json
+++ b/docs/pages/toolpad/core/api/account.json
@@ -1,19 +1,21 @@
{
"props": {
"localeText": {
- "type": { "name": "shape", "description": "{ signInLabel: string, signOutLabel: string }" },
- "default": "DEFAULT_LOCALE_TEXT"
+ "type": {
+ "name": "shape",
+ "description": "{ iconButtonAriaLabel?: string, signInLabel?: string, signOutLabel?: string }"
+ }
},
"slotProps": {
"type": {
"name": "shape",
- "description": "{ iconButton?: object, signInButton?: object, signOutButton?: object }"
+ "description": "{ popover?: object, popoverContent?: object, preview?: { handleClick?: func, open?: bool, slotProps?: { avatar?: object, avatarIconButton?: object, moreIconButton?: object }, slots?: { avatar?: elementType, avatarIconButton?: elementType, moreIconButton?: elementType }, sx?: Array<func | object | bool> | func | object, variant?: 'condensed' | 'expanded' }, signInButton?: object, signOutButton?: object }"
}
},
"slots": {
"type": {
"name": "shape",
- "description": "{ menuItems?: elementType, signInButton?: elementType, signOutButton?: elementType }"
+ "description": "{ popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType }"
},
"additionalInfo": { "slotsApi": true }
}
@@ -24,6 +26,24 @@
"import { Account } from '@toolpad/core';"
],
"slots": [
+ {
+ "name": "preview",
+ "description": "The component used for the account preview",
+ "default": "AccountPreview",
+ "class": null
+ },
+ {
+ "name": "popover",
+ "description": "The component used for the account popover menu",
+ "default": "Popover",
+ "class": null
+ },
+ {
+ "name": "popoverContent",
+ "description": "The component used for the content of account popover",
+ "default": "Stack",
+ "class": null
+ },
{
"name": "signInButton",
"description": "The component used for the sign in button.",
@@ -33,13 +53,7 @@
{
"name": "signOutButton",
"description": "The component used for the sign out button.",
- "default": "MenuItem",
- "class": null
- },
- {
- "name": "menuItems",
- "description": "The component used for the custom menu items.",
- "default": "null",
+ "default": "Button",
"class": null
}
],
diff --git a/docs/pages/toolpad/core/api/app-provider.json b/docs/pages/toolpad/core/api/app-provider.json
index a439e4bee49..6918852f106 100644
--- a/docs/pages/toolpad/core/api/app-provider.json
+++ b/docs/pages/toolpad/core/api/app-provider.json
@@ -6,7 +6,10 @@
"default": "null"
},
"branding": {
- "type": { "name": "shape", "description": "{ logo?: node, title?: string }" },
+ "type": {
+ "name": "shape",
+ "description": "{ homeUrl?: string, logo?: node, title?: string }"
+ },
"default": "null"
},
"navigation": {
@@ -36,7 +39,7 @@
"name": "AppProvider",
"imports": [
"import { AppProvider } from '@toolpad/core/AppProvider';",
- "import { AppProvider } from '@toolpad/core';\nimport { AppProvider } from '@toolpad/core/nextjs'; // Next.js\nimport { AppProvider } from '@toolpad/core/react-router-dom'; // React Router"
+ "import { AppProvider } from '@toolpad/core';\nimport { NextAppProvider } from '@toolpad/core/nextjs'; // Next.js\nimport { ReactRouterAppProvider } from '@toolpad/core/react-router'; // React Router"
],
"classes": [],
"spread": true,
diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json
index fdb76804d9b..6cb95c85aa3 100644
--- a/docs/pages/toolpad/core/api/dashboard-layout.json
+++ b/docs/pages/toolpad/core/api/dashboard-layout.json
@@ -1,20 +1,38 @@
{
"props": {
"children": { "type": { "name": "node" }, "required": true },
+ "branding": {
+ "type": {
+ "name": "shape",
+ "description": "{ homeUrl?: string, logo?: node, title?: string }"
+ },
+ "default": "null"
+ },
"defaultSidebarCollapsed": { "type": { "name": "bool" }, "default": "false" },
"disableCollapsibleSidebar": { "type": { "name": "bool" }, "default": "false" },
"hideNavigation": { "type": { "name": "bool" }, "default": "false" },
+ "navigation": {
+ "type": {
+ "name": "arrayOf",
+ "description": "Array<{ action?: node, children?: Array<object | { kind: 'header', title: string } | { kind: 'divider' }>, icon?: node, kind?: 'page', pattern?: string, segment?: string, title?: string } | { kind: 'header', title: string } | { kind: 'divider' }>"
+ },
+ "default": "[]"
+ },
+ "sidebarExpandedWidth": {
+ "type": { "name": "union", "description": "number | string" },
+ "default": "320"
+ },
"slotProps": {
"type": {
"name": "shape",
- "description": "{ sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: { signInLabel: string, signOutLabel: string }, slotProps?: { iconButton?: object, signInButton?: object, signOutButton?: object }, slots?: { menuItems?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }"
+ "description": "{ appTitle?: { branding?: { homeUrl?: string, logo?: node, title?: string } }, sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: { iconButtonAriaLabel?: string, signInLabel?: string, signOutLabel?: string }, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }"
},
"default": "{}"
},
"slots": {
"type": {
"name": "shape",
- "description": "{ sidebarFooter?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }"
+ "description": "{ appTitle?: elementType, sidebarFooter?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }"
},
"default": "{}",
"additionalInfo": { "slotsApi": true }
@@ -33,6 +51,12 @@
"import { DashboardLayout } from '@toolpad/core';"
],
"slots": [
+ {
+ "name": "appTitle",
+ "description": "The component used for the app title section in the layout header.",
+ "default": "Link",
+ "class": null
+ },
{
"name": "toolbarActions",
"description": "The toolbar actions component used in the layout header.",
diff --git a/docs/pages/toolpad/core/api/page-container-toolbar.json b/docs/pages/toolpad/core/api/page-container-toolbar.json
deleted file mode 100644
index 4d789399e61..00000000000
--- a/docs/pages/toolpad/core/api/page-container-toolbar.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "props": {},
- "name": "PageContainerToolbar",
- "imports": ["import { PageContainerToolbar } from '@toolpad/core/PageContainer';"],
- "classes": [],
- "spread": true,
- "themeDefaultProps": false,
- "muiName": "PageContainerToolbar",
- "filename": "/packages/toolpad-core/src/PageContainer/PageContainerToolbar.tsx",
- "inheritance": null,
- "demos": "",
- "cssComponent": false
-}
diff --git a/docs/pages/toolpad/core/api/page-container.json b/docs/pages/toolpad/core/api/page-container.json
index e7a4835c201..7102f8ecba1 100644
--- a/docs/pages/toolpad/core/api/page-container.json
+++ b/docs/pages/toolpad/core/api/page-container.json
@@ -1,13 +1,25 @@
{
"props": {
"breadcrumbs": {
- "type": { "name": "arrayOf", "description": "Array<{ path: string, title: string }>" }
+ "type": { "name": "arrayOf", "description": "Array<{ path?: string, title: string }>" }
+ },
+ "slotProps": {
+ "type": {
+ "name": "shape",
+ "description": "{ header: { breadcrumbs?: Array<{ path?: string, title: string }>, slotProps?: { toolbar: object }, slots?: { toolbar?: elementType }, title?: string } }"
+ }
},
- "slotProps": { "type": { "name": "shape", "description": "{ toolbar: { children?: node } }" } },
"slots": {
- "type": { "name": "shape", "description": "{ toolbar?: elementType }" },
+ "type": { "name": "shape", "description": "{ header?: elementType }" },
"additionalInfo": { "slotsApi": true }
},
+ "sx": {
+ "type": {
+ "name": "union",
+ "description": "Array<func | object | bool> | func | object"
+ },
+ "additionalInfo": { "sx": true }
+ },
"title": { "type": { "name": "string" } }
},
"name": "PageContainer",
@@ -17,9 +29,9 @@
],
"slots": [
{
- "name": "toolbar",
- "description": "The component that renders the actions toolbar.",
- "default": "Snackbar",
+ "name": "header",
+ "description": "The component that renders the page header.",
+ "default": "PageHeader",
"class": null
}
],
diff --git a/docs/pages/toolpad/core/api/page-header-toolbar.js b/docs/pages/toolpad/core/api/page-header-toolbar.js
new file mode 100644
index 00000000000..01adee5b6bd
--- /dev/null
+++ b/docs/pages/toolpad/core/api/page-header-toolbar.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './page-header-toolbar.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/page-header-toolbar',
+ false,
+ /\.\/page-header-toolbar.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/page-header-toolbar.json b/docs/pages/toolpad/core/api/page-header-toolbar.json
new file mode 100644
index 00000000000..6824539c9d3
--- /dev/null
+++ b/docs/pages/toolpad/core/api/page-header-toolbar.json
@@ -0,0 +1,13 @@
+{
+ "props": {},
+ "name": "PageHeaderToolbar",
+ "imports": ["import { PageHeaderToolbar } from '@toolpad/core/PageContainer';"],
+ "classes": [],
+ "spread": true,
+ "themeDefaultProps": false,
+ "muiName": "PageHeaderToolbar",
+ "filename": "/packages/toolpad-core/src/PageContainer/PageHeaderToolbar.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/page-header.js b/docs/pages/toolpad/core/api/page-header.js
new file mode 100644
index 00000000000..0ed0e513800
--- /dev/null
+++ b/docs/pages/toolpad/core/api/page-header.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './page-header.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/page-header',
+ false,
+ /\.\/page-header.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/page-header.json b/docs/pages/toolpad/core/api/page-header.json
new file mode 100644
index 00000000000..b6217f6ebc8
--- /dev/null
+++ b/docs/pages/toolpad/core/api/page-header.json
@@ -0,0 +1,29 @@
+{
+ "props": {
+ "breadcrumbs": {
+ "type": { "name": "arrayOf", "description": "Array<{ path?: string, title: string }>" }
+ },
+ "slotProps": { "type": { "name": "shape", "description": "{ toolbar: { children?: node } }" } },
+ "slots": {
+ "type": { "name": "shape", "description": "{ toolbar?: elementType }" },
+ "additionalInfo": { "slotsApi": true }
+ },
+ "title": { "type": { "name": "string" } }
+ },
+ "name": "PageHeader",
+ "imports": ["import { PageHeader } from '@toolpad/core/PageContainer';"],
+ "slots": [
+ {
+ "name": "toolbar",
+ "description": "The component that renders the actions toolbar.",
+ "default": "PageHeaderToolbar",
+ "class": null
+ }
+ ],
+ "classes": [],
+ "muiName": "PageHeader",
+ "filename": "/packages/toolpad-core/src/PageContainer/PageHeader.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/sign-in-button.js b/docs/pages/toolpad/core/api/sign-in-button.js
new file mode 100644
index 00000000000..aab4d5111ca
--- /dev/null
+++ b/docs/pages/toolpad/core/api/sign-in-button.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './sign-in-button.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/sign-in-button',
+ false,
+ /\.\/sign-in-button.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/sign-in-button.json b/docs/pages/toolpad/core/api/sign-in-button.json
new file mode 100644
index 00000000000..0d34a3b5cf4
--- /dev/null
+++ b/docs/pages/toolpad/core/api/sign-in-button.json
@@ -0,0 +1,369 @@
+{
+ "props": { "children": { "type": { "name": "node" } } },
+ "name": "SignInButton",
+ "imports": ["import { SignInButton } from '@toolpad/core/Account';"],
+ "classes": [
+ {
+ "key": "colorError",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"error\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"info\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"inherit\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"primary\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"secondary\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"success\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"warning\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "contained",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "containedError",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"error\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"info\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"inherit\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"primary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"secondary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"` and `variant=\"contained\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"` and `variant=\"contained\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"` and `variant=\"contained\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"success\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"warning\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "disabled",
+ "className": "",
+ "description": "State class applied to the root element if `disabled={true}`.",
+ "isGlobal": false
+ },
+ {
+ "key": "disableElevation",
+ "className": "",
+ "description": "Styles applied to the root element if `disableElevation={true}`.",
+ "isGlobal": false
+ },
+ {
+ "key": "endIcon",
+ "className": "",
+ "description": "Styles applied to the endIcon element if supplied.",
+ "isGlobal": false
+ },
+ {
+ "key": "focusVisible",
+ "className": "",
+ "description": "State class applied to the ButtonBase root element if the button is keyboard focused.",
+ "isGlobal": false
+ },
+ {
+ "key": "fullWidth",
+ "className": "",
+ "description": "Styles applied to the root element if `fullWidth={true}`.",
+ "isGlobal": false
+ },
+ {
+ "key": "icon",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied",
+ "isGlobal": false
+ },
+ {
+ "key": "iconSizeLarge",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied and `size=\"large\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "iconSizeMedium",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied and `size=\"medium\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "iconSizeSmall",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied and `size=\"small\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlined",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "outlinedError",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"error\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"info\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"inherit\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"primary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"secondary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"` and `variant=\"outlined\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"` and `variant=\"outlined\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"` and `variant=\"outlined\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"success\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"warning\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "root",
+ "className": "",
+ "description": "Styles applied to the root element.",
+ "isGlobal": false
+ },
+ {
+ "key": "sizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "sizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "sizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "startIcon",
+ "className": "",
+ "description": "Styles applied to the startIcon element if supplied.",
+ "isGlobal": false
+ },
+ {
+ "key": "text",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "textError",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"error\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"info\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"inherit\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"primary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"secondary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"` and `variant=\"text\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"` and `variant=\"text\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"` and `variant=\"text\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"success\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"warning\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ }
+ ],
+ "muiName": "SignInButton",
+ "filename": "/packages/toolpad-core/src/Account/SignInButton.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/sign-in-page.json b/docs/pages/toolpad/core/api/sign-in-page.json
index 5b65fa61ec8..132fb1607bc 100644
--- a/docs/pages/toolpad/core/api/sign-in-page.json
+++ b/docs/pages/toolpad/core/api/sign-in-page.json
@@ -1,10 +1,7 @@
{
"props": {
"providers": {
- "type": {
- "name": "arrayOf",
- "description": "Array<{ id: 'apple' | 'auth0' | 'cognito' | 'credentials' | 'discord' | 'facebook' | 'fusionauth' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'line' | 'linkedin' | 'microsoft-entra-id' | 'okta' | 'slack' | 'spotify' | 'tiktok' | 'twitch' | 'twitter', name: string }>"
- },
+ "type": { "name": "arrayOf", "description": "Array<{ id: string, name: string }>" },
"default": "[]"
},
"signIn": {
@@ -18,17 +15,24 @@
"slotProps": {
"type": {
"name": "shape",
- "description": "{ emailField?: object, forgotPasswordLink?: object, passwordField?: object, signUpLink?: object, submitButton?: object }"
+ "description": "{ emailField?: object, forgotPasswordLink?: object, passwordField?: object, rememberMe?: object, signUpLink?: object, submitButton?: object }"
},
"default": "{}"
},
"slots": {
"type": {
"name": "shape",
- "description": "{ emailField?: elementType, forgotPasswordLink?: elementType, passwordField?: elementType, signUpLink?: elementType, submitButton?: elementType }"
+ "description": "{ emailField?: elementType, forgotPasswordLink?: elementType, passwordField?: elementType, rememberMe?: elementType, signUpLink?: elementType, submitButton?: elementType, subtitle?: elementType, title?: elementType }"
},
"default": "{}",
"additionalInfo": { "slotsApi": true }
+ },
+ "sx": {
+ "type": {
+ "name": "union",
+ "description": "Array<func | object | bool> | func | object"
+ },
+ "additionalInfo": { "sx": true }
}
},
"name": "SignInPage",
@@ -66,6 +70,24 @@
"description": "The custom sign up link component used in the credentials form.",
"default": "Link",
"class": null
+ },
+ {
+ "name": "title",
+ "description": "A component to override the default title section",
+ "default": "Typography",
+ "class": null
+ },
+ {
+ "name": "subtitle",
+ "description": "A component to override the default subtitle section",
+ "default": "Typography",
+ "class": null
+ },
+ {
+ "name": "rememberMe",
+ "description": "A component to override the default \"Remember me\" checkbox in the Credentials form",
+ "default": "FormControlLabel",
+ "class": null
}
],
"classes": [],
diff --git a/docs/pages/toolpad/core/api/sign-out-button.js b/docs/pages/toolpad/core/api/sign-out-button.js
new file mode 100644
index 00000000000..8f0e9e7e8fe
--- /dev/null
+++ b/docs/pages/toolpad/core/api/sign-out-button.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './sign-out-button.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/sign-out-button',
+ false,
+ /\.\/sign-out-button.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/sign-out-button.json b/docs/pages/toolpad/core/api/sign-out-button.json
new file mode 100644
index 00000000000..66fd2b0b73d
--- /dev/null
+++ b/docs/pages/toolpad/core/api/sign-out-button.json
@@ -0,0 +1,369 @@
+{
+ "props": { "children": { "type": { "name": "node" } } },
+ "name": "SignOutButton",
+ "imports": ["import { SignOutButton } from '@toolpad/core/Account';"],
+ "classes": [
+ {
+ "key": "colorError",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"error\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"info\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"inherit\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"primary\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"secondary\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"success\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "colorWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `color=\"warning\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "contained",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "containedError",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"error\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"info\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"inherit\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"primary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"secondary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"` and `variant=\"contained\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"` and `variant=\"contained\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"` and `variant=\"contained\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"success\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "containedWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"contained\"` and `color=\"warning\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "disabled",
+ "className": "",
+ "description": "State class applied to the root element if `disabled={true}`.",
+ "isGlobal": false
+ },
+ {
+ "key": "disableElevation",
+ "className": "",
+ "description": "Styles applied to the root element if `disableElevation={true}`.",
+ "isGlobal": false
+ },
+ {
+ "key": "endIcon",
+ "className": "",
+ "description": "Styles applied to the endIcon element if supplied.",
+ "isGlobal": false
+ },
+ {
+ "key": "focusVisible",
+ "className": "",
+ "description": "State class applied to the ButtonBase root element if the button is keyboard focused.",
+ "isGlobal": false
+ },
+ {
+ "key": "fullWidth",
+ "className": "",
+ "description": "Styles applied to the root element if `fullWidth={true}`.",
+ "isGlobal": false
+ },
+ {
+ "key": "icon",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied",
+ "isGlobal": false
+ },
+ {
+ "key": "iconSizeLarge",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied and `size=\"large\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "iconSizeMedium",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied and `size=\"medium\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "iconSizeSmall",
+ "className": "",
+ "description": "Styles applied to the icon element if supplied and `size=\"small\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlined",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "outlinedError",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"error\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"info\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"inherit\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"primary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"secondary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"` and `variant=\"outlined\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"` and `variant=\"outlined\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"` and `variant=\"outlined\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"success\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "outlinedWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"outlined\"` and `color=\"warning\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "root",
+ "className": "",
+ "description": "Styles applied to the root element.",
+ "isGlobal": false
+ },
+ {
+ "key": "sizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "sizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "sizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "startIcon",
+ "className": "",
+ "description": "Styles applied to the startIcon element if supplied.",
+ "isGlobal": false
+ },
+ {
+ "key": "text",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"`.",
+ "isGlobal": false
+ },
+ {
+ "key": "textError",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"error\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textInfo",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"info\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textInherit",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"inherit\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textPrimary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"primary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSecondary",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"secondary\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSizeLarge",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"large\"` and `variant=\"text\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSizeMedium",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"medium\"` and `variant=\"text\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSizeSmall",
+ "className": "",
+ "description": "Styles applied to the root element if `size=\"small\"` and `variant=\"text\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textSuccess",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"success\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ },
+ {
+ "key": "textWarning",
+ "className": "",
+ "description": "Styles applied to the root element if `variant=\"text\"` and `color=\"warning\"`.",
+ "isGlobal": false,
+ "isDeprecated": true
+ }
+ ],
+ "muiName": "SignOutButton",
+ "filename": "/packages/toolpad-core/src/Account/SignOutButton.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/theme-switcher.js b/docs/pages/toolpad/core/api/theme-switcher.js
new file mode 100644
index 00000000000..61c6592d7c7
--- /dev/null
+++ b/docs/pages/toolpad/core/api/theme-switcher.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './theme-switcher.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/theme-switcher',
+ false,
+ /\.\/theme-switcher.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/theme-switcher.json b/docs/pages/toolpad/core/api/theme-switcher.json
new file mode 100644
index 00000000000..6869b76640d
--- /dev/null
+++ b/docs/pages/toolpad/core/api/theme-switcher.json
@@ -0,0 +1,11 @@
+{
+ "props": {},
+ "name": "ThemeSwitcher",
+ "imports": ["import { ThemeSwitcher } from '@toolpad/core/DashboardLayout';"],
+ "classes": [],
+ "muiName": "ThemeSwitcher",
+ "filename": "/packages/toolpad-core/src/DashboardLayout/ThemeSwitcher.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/api/toolbar-actions.js b/docs/pages/toolpad/core/api/toolbar-actions.js
new file mode 100644
index 00000000000..91cfb0d3c0d
--- /dev/null
+++ b/docs/pages/toolpad/core/api/toolbar-actions.js
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import ApiPage from 'docs/src/modules/components/ApiPage';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './toolbar-actions.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return ;
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docs-toolpad/translations/api-docs/toolbar-actions',
+ false,
+ /\.\/toolbar-actions.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/toolpad/core/api/toolbar-actions.json b/docs/pages/toolpad/core/api/toolbar-actions.json
new file mode 100644
index 00000000000..b5b794315c2
--- /dev/null
+++ b/docs/pages/toolpad/core/api/toolbar-actions.json
@@ -0,0 +1,11 @@
+{
+ "props": {},
+ "name": "ToolbarActions",
+ "imports": ["import { ToolbarActions } from '@toolpad/core/DashboardLayout';"],
+ "classes": [],
+ "muiName": "ToolbarActions",
+ "filename": "/packages/toolpad-core/src/DashboardLayout/ToolbarActions.tsx",
+ "inheritance": null,
+ "demos": "",
+ "cssComponent": false
+}
diff --git a/docs/pages/toolpad/core/integrations/nextjs-approuter.js b/docs/pages/toolpad/core/integrations/nextjs-approuter.js
new file mode 100644
index 00000000000..e8bbf70089c
--- /dev/null
+++ b/docs/pages/toolpad/core/integrations/nextjs-approuter.js
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-approuter.md?muiMarkdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js b/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js
new file mode 100644
index 00000000000..8be2843cb83
--- /dev/null
+++ b/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-pagesrouter.md?muiMarkdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/pages/toolpad/core/introduction/integration.js b/docs/pages/toolpad/core/integrations/react-router.js
similarity index 65%
rename from docs/pages/toolpad/core/introduction/integration.js
rename to docs/pages/toolpad/core/integrations/react-router.js
index 2e29678e8da..b34ba5230fb 100644
--- a/docs/pages/toolpad/core/introduction/integration.js
+++ b/docs/pages/toolpad/core/integrations/react-router.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
-import * as pageProps from '../../../../data/toolpad/core/introduction/integration.md?muiMarkdown';
+import * as pageProps from '../../../../data/toolpad/core/integrations/react-router.md?muiMarkdown';
export default function Page() {
return ;
diff --git a/docs/pages/toolpad/core/react-use-session/api.js b/docs/pages/toolpad/core/react-use-session/api.js
new file mode 100644
index 00000000000..d583ed0de3e
--- /dev/null
+++ b/docs/pages/toolpad/core/react-use-session/api.js
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import * as pageProps from 'docs-toolpad/data/toolpad/core/components/use-session/use-session-api.md?muiMarkdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/pages/toolpad/core/react-use-session/index.js b/docs/pages/toolpad/core/react-use-session/index.js
new file mode 100644
index 00000000000..b77a30a4fe5
--- /dev/null
+++ b/docs/pages/toolpad/core/react-use-session/index.js
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import * as pageProps from 'docs-toolpad/data/toolpad/core/components/use-session/use-session.md?muiMarkdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/pages/toolpad/studio/examples/basic-crud-app.js b/docs/pages/toolpad/studio/examples/basic-crud-app.js
index cb548eaded6..ffa57ad6ba2 100644
--- a/docs/pages/toolpad/studio/examples/basic-crud-app.js
+++ b/docs/pages/toolpad/studio/examples/basic-crud-app.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
-import * as pageProps from '../../../../../examples/basic-crud-app/README.md?muiMarkdown';
+import * as pageProps from '../../../../../examples/studio/basic-crud-app/README.md?muiMarkdown';
export default function Page() {
return ;
diff --git a/docs/pages/toolpad/studio/examples/npm-stats.js b/docs/pages/toolpad/studio/examples/npm-stats.js
index 60a2c4d02b7..9b3cb82b22b 100644
--- a/docs/pages/toolpad/studio/examples/npm-stats.js
+++ b/docs/pages/toolpad/studio/examples/npm-stats.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
-import * as pageProps from '../../../../../examples/npm-stats/README.md?muiMarkdown';
+import * as pageProps from '../../../../../examples/studio/npm-stats/README.md?muiMarkdown';
export default function Page() {
return ;
diff --git a/docs/pages/toolpad/studio/examples/qr-generator.js b/docs/pages/toolpad/studio/examples/qr-generator.js
index b2d466cc5f1..bdda5992fe2 100644
--- a/docs/pages/toolpad/studio/examples/qr-generator.js
+++ b/docs/pages/toolpad/studio/examples/qr-generator.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
-import * as pageProps from '../../../../../examples/qr-generator/README.md?muiMarkdown';
+import * as pageProps from '../../../../../examples/studio/qr-generator/README.md?muiMarkdown';
export default function Page() {
return ;
diff --git a/docs/public/_redirects b/docs/public/_redirects
index 5c30e22554c..ac291c13de5 100644
--- a/docs/public/_redirects
+++ b/docs/public/_redirects
@@ -1,6 +1,14 @@
# Avoid conflicts with the other Next.js apps hosted under https://mui.com/
/toolpad/_next/* /_next/:splat 200
+
+# Template app assets (_next chunks)
+/toolpad/core/templates/nextjs-dashboard/_next/* https://toolpad-core-nextjs-themed.vercel.app/_next/:splat 200
+
+# Template page routes
+/toolpad/core/templates/nextjs-dashboard/* https://toolpad-core-nextjs-themed.vercel.app/:splat 200
+
+
# Legacy redirection
# 2022
/toolpad/getting-started/setup/ /toolpad/studio/getting-started/ 301
@@ -43,6 +51,7 @@
/toolpad/how-to-guides/* /toolpad/studio/how-to-guides/:splat 301
/toolpad/reference/* /toolpad/studio/reference/:splat 301
/toolpad/core/installation/ /toolpad/core/introduction/installation/ 301
+/toolpad/core/introduction/integration/ /toolpad/core/integrations/nextjs-approuter/ 301
# Create separate namespace on https://mui.com
/ /toolpad/
diff --git a/docs/public/static/toolpad/docs/core/auth-next-dark.png b/docs/public/static/toolpad/docs/core/auth-next-dark.png
index a4b127c9b6c..527474a4a77 100644
Binary files a/docs/public/static/toolpad/docs/core/auth-next-dark.png and b/docs/public/static/toolpad/docs/core/auth-next-dark.png differ
diff --git a/docs/public/static/toolpad/docs/core/auth-next-passkey-dark.png b/docs/public/static/toolpad/docs/core/auth-next-passkey-dark.png
new file mode 100644
index 00000000000..0aa6dccde2b
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/auth-next-passkey-dark.png differ
diff --git a/docs/public/static/toolpad/docs/core/auth-next-passkey.png b/docs/public/static/toolpad/docs/core/auth-next-passkey.png
new file mode 100644
index 00000000000..cdbf598ab14
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/auth-next-passkey.png differ
diff --git a/docs/public/static/toolpad/docs/core/auth-next-themed-dark.png b/docs/public/static/toolpad/docs/core/auth-next-themed-dark.png
new file mode 100644
index 00000000000..d897cc5777e
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/auth-next-themed-dark.png differ
diff --git a/docs/public/static/toolpad/docs/core/auth-next-themed.png b/docs/public/static/toolpad/docs/core/auth-next-themed.png
new file mode 100644
index 00000000000..a89b35fa42e
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/auth-next-themed.png differ
diff --git a/docs/public/static/toolpad/docs/core/auth-next.png b/docs/public/static/toolpad/docs/core/auth-next.png
index 57238099e1b..fc266f89294 100644
Binary files a/docs/public/static/toolpad/docs/core/auth-next.png and b/docs/public/static/toolpad/docs/core/auth-next.png differ
diff --git a/docs/public/static/toolpad/docs/core/firebase-vite-dark.png b/docs/public/static/toolpad/docs/core/firebase-vite-dark.png
new file mode 100644
index 00000000000..ecc3b723d2f
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/firebase-vite-dark.png differ
diff --git a/docs/public/static/toolpad/docs/core/firebase-vite-light.png b/docs/public/static/toolpad/docs/core/firebase-vite-light.png
new file mode 100644
index 00000000000..4cdf95d2202
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/firebase-vite-light.png differ
diff --git a/docs/public/static/toolpad/docs/core/functional-dashboard-dark.png b/docs/public/static/toolpad/docs/core/functional-dashboard-dark.png
new file mode 100644
index 00000000000..76b9d3cc227
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/functional-dashboard-dark.png differ
diff --git a/docs/public/static/toolpad/docs/core/functional-dashboard.png b/docs/public/static/toolpad/docs/core/functional-dashboard.png
new file mode 100644
index 00000000000..ffd6ce30dbd
Binary files /dev/null and b/docs/public/static/toolpad/docs/core/functional-dashboard.png differ
diff --git a/docs/src/components/landing-studio/GithubStars.js b/docs/src/components/landing-studio/GithubStars.js
index c7a0f38f72b..65c4b848beb 100644
--- a/docs/src/components/landing-studio/GithubStars.js
+++ b/docs/src/components/landing-studio/GithubStars.js
@@ -75,8 +75,12 @@ export default function GithubStars() {
sx={{ mt: stars ? 0.1 : 0, mr: stars ? 0.5 : 0 }}
fontSize="small"
/>
- {fetching ? : stars}
+ {fetching ? : formatNumber(stars)}
);
+
+ function formatNumber(num) {
+ return `${(num / 1000).toFixed(1)}k`;
+ }
}
diff --git a/docs/src/components/landing/Examples.js b/docs/src/components/landing/Examples.js
index 40baf467eee..9962847b506 100644
--- a/docs/src/components/landing/Examples.js
+++ b/docs/src/components/landing/Examples.js
@@ -71,7 +71,7 @@ function ContentCard({ icon, title, description, href }) {
{description}
- View more
+ Visit tutorial
@@ -112,7 +112,7 @@ export default function Examples() {
}
title="Admin app"
- description="This app shows you to get started with Toolpad Core and use basic layout and navigation features."
+ description="This app shows you how to get started with Toolpad Core and use basic layout and navigation features."
href="https://mui.com/toolpad/core/introduction/tutorial/"
/>
@@ -160,7 +160,7 @@ export default function Examples() {
Learn how to build these and many other apps using Toolpad Core!
- Check out docs
+ Explore more examples
diff --git a/docs/src/components/landing/Hero.js b/docs/src/components/landing/Hero.js
index 22716225254..5ba743870ea 100644
--- a/docs/src/components/landing/Hero.js
+++ b/docs/src/components/landing/Hero.js
@@ -69,7 +69,7 @@ export default function Hero() {
>
From the creators of Material UI, Toolpad Core offers the components needed for your
next admin panel and internal tools project. Bootstrap from scratch in our CLI with well
- chosen defaults, or drop Toolpad Core into your existing Next.js or Vite* project.
+ chosen defaults, or drop Toolpad Core into your existing Next.js or Vite project.
([]);
-
- React.useEffect(() => {
- const importExamples = async () => {
- const exampleContent = await import(`./${examplesFile}`);
- setExamples(exampleContent.default);
- };
- importExamples();
- }, [examplesFile]);
-
- return (
-
- {examples.map((example) => (
-
- ({
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- px: 2,
- pt: 2,
- pb: 1,
- gap: 1.5,
- borderRadius: 1,
- backgroundColor: `${alpha(theme.palette.grey[50], 0.4)}`,
- borderColor: 'divider',
- ...theme.applyStyles('dark', {
- backgroundColor: `${alpha(theme.palette.primary.dark, 0.1)}`,
- borderColor: 'divider',
- }),
- })}
- variant="outlined"
- >
- ({
- height: 0,
- pt: '65%',
- borderRadius: 0.5,
- bgcolor: 'currentColor',
- border: '1px solid',
- borderColor: 'grey.100',
- color: 'grey.100',
- ...theme.applyStyles('dark', {
- borderColor: 'grey.900',
- color: 'primaryDark.900',
- }),
- })}
- />
-
-
- {example.title}
-
-
- {example.description}
-
-
-
-
- Source code
-
-
-
-
- ))}
-
- );
-}
-
-export default Templates;
diff --git a/docs/src/modules/components/ExamplesGrid/core-examples.ts b/docs/src/modules/components/ExamplesGrid/core-examples.ts
deleted file mode 100644
index b99860b01a8..00000000000
--- a/docs/src/modules/components/ExamplesGrid/core-examples.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-export default function examples() {
- return [
- {
- title: 'Tutorial app',
- description:
- 'This app shows you to get started with Toolpad Core and use basic layout and navigation features.',
- src: '/static/toolpad/docs/core/tutorial-1.png',
- href: 'https://mui.com/toolpad/core/introduction/tutorial/',
- source: 'https://github.com/mui/toolpad/tree/master/examples/core-tutorial',
- },
- {
- title: 'Auth.js with Next.js App router',
- description:
- 'This app shows you to how to get started using Toolpad Core with Auth.js and the Next.js App router',
- src: '/static/toolpad/docs/core/auth-next.png',
- source: 'https://github.com/mui/toolpad/tree/master/examples/core-auth-nextjs',
- },
- {
- title: 'Auth.js with Next.js Pages router',
- description:
- 'This app shows you to how to get started using Toolpad Core with Auth.js and the Next.js Pages router',
- src: '/static/toolpad/docs/core/auth-next.png',
- source: 'https://github.com/mui/toolpad/tree/master/examples/core-auth-nextjs-pages',
- },
- {
- title: 'Vite with React Router',
- description:
- 'This app shows you to how to get started using Toolpad Core with Vite and React Router',
- src: '/static/toolpad/docs/core/vite-react-router.png',
- source: 'https://github.com/mui/toolpad/tree/master/examples/core-vite',
- },
- ];
-}
diff --git a/docs/src/modules/components/ExamplesGrid/studio-examples.ts b/docs/src/modules/components/ExamplesGrid/studio-examples.ts
deleted file mode 100644
index 9926cdaab7c..00000000000
--- a/docs/src/modules/components/ExamplesGrid/studio-examples.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-export default function examples() {
- return [
- {
- title: 'npm stats',
- description:
- 'This analytics dashboard shows how to track a KPI from a third-party data source.',
- src: '/static/toolpad/docs/studio/examples/npm-stats.png',
- href: 'https://mui.com/toolpad/studio/examples/npm-stats/',
- source: 'https://github.com/mui/toolpad/tree/master/examples/npm-stats',
- },
- {
- title: 'Basic CRUD application',
- description: 'An admin application to showcase how CRUD operations work in Toolpad Studio.',
- src: '/static/toolpad/docs/studio/examples/basic-crud-app.png',
- href: 'https://mui.com/toolpad/studio/examples/basic-crud-app/',
- source: 'https://github.com/mui/toolpad/tree/master/examples/basic-crud-app',
- },
- {
- title: 'QR Code generator',
- description:
- 'A basic Toolpad Studio application that can be used to turn any text or URL into a QR code.',
- src: '/static/toolpad/docs/studio/examples/qr-generator.png',
- href: 'https://mui.com/toolpad/studio/examples/qr-generator/',
- source: 'https://github.com/mui/toolpad/tree/master/examples/qr-generator',
- },
- {
- title: 'With Prisma',
- description:
- 'A basic Toolpad Studio application that demonstrates how to integrate with Prisma.',
- src: '/static/toolpad/marketing/with-prisma-hero.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/with-prisma',
- source: 'https://github.com/mui/toolpad/tree/master/examples/with-prisma',
- },
- {
- title: 'Google Sheet',
- description: 'Quickly fetch data from Google Sheets to build a Toolpad Studio app.',
- src: '/static/toolpad/marketing/google-sheet.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/google-sheet',
- source: 'https://github.com/mui/toolpad/tree/master/examples/google-sheet',
- },
- {
- title: 'Dog app',
- description: 'An app that shows dog images based on selected breeds or sub-breeds.',
- src: '/static/toolpad/docs/studio/getting-started/first-app/step-13.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/dog-app',
- source: 'https://github.com/mui/toolpad/tree/master/examples/dog-app',
- },
- {
- title: 'Customized data grid',
- description:
- 'A basic Toolpad Studio app that shows how to customize a data grid column using a custom code component.',
- src: '/static/toolpad/marketing/custom-datagrid-column.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/custom-datagrid-column',
- source: 'https://github.com/mui/toolpad/tree/master/examples/custom-datagrid-column',
- },
- {
- title: 'GraphQL app',
- description: 'An app that shows latest 100 stargazers info for any GitHub repository.',
- src: '/static/toolpad/marketing/graphql.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/graphql',
- source: 'https://github.com/mui/toolpad/tree/master/examples/graphql',
- },
- {
- title: 'With WASM',
- description:
- 'A basic Toolpad Studio application that demonstrates integrating with WASM modules.',
- src: '/static/toolpad/marketing/with-wasm.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/with-wasm',
- source: 'https://github.com/mui/toolpad/tree/master/examples/with-wasm',
- },
- {
- title: 'Data provider with prisma',
- description:
- 'A basic Toolpad Studio application that demonstrates how to use data providers with Prisma.',
- src: '/static/toolpad/marketing/with-prisma-data-provider.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/with-prisma-data-provider',
- source: 'https://github.com/mui/toolpad/tree/master/examples/with-prisma-data-provider',
- },
- {
- title: 'With Supabase',
- description:
- 'A Toolpad Studio app that fetches data from Supabase and shows it in a list component.',
- src: '/static/toolpad/marketing/supabase.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/supabase',
- source: 'https://github.com/mui/toolpad/tree/master/examples/supabase',
- },
- {
- title: 'Stripe invoice downloader',
- description: 'A Stripe app to fetch and download invoices.',
- src: '/static/toolpad/marketing/stripe-script.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/stripe-script',
- source: 'https://github.com/mui/toolpad/tree/master/examples/stripe-script',
- },
- {
- title: 'Charts',
- description:
- 'A basic Toolpad Studio application that demonstrates how to use chart component.',
- src: '/static/toolpad/marketing/charts.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/charts',
- source: 'https://github.com/mui/toolpad/tree/master/examples/charts',
- },
- {
- title: 'Google Authentication',
- description: 'An app that shows how to set up Google authentication in Toolpad Studio.',
- src: '/static/toolpad/marketing/auth-google.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/auth-google',
- source: 'https://github.com/mui/toolpad/tree/master/examples/auth-google',
- },
- {
- title: 'Custom server',
- description: 'An app that shows how to use Toolpad Studio with a custom server.',
- src: '/static/toolpad/marketing/custom-server.png',
- href: 'https://github.com/mui/toolpad/tree/master/examples/custom-server',
- source: 'https://github.com/mui/toolpad/tree/master/examples/custom-server',
- },
- ];
-}
diff --git a/docs/src/modules/components/SchemaReference.tsx b/docs/src/modules/components/SchemaReference.tsx
index 8ceceb0aab8..f8aa3d78179 100644
--- a/docs/src/modules/components/SchemaReference.tsx
+++ b/docs/src/modules/components/SchemaReference.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { useTranslate } from '@mui/docs/i18n';
import { MarkdownElement } from '@mui/docs/MarkdownElement';
import AppLayoutDocs from '@mui/monorepo/docs/src/modules/components/AppLayoutDocs';
import Ad from '@mui/monorepo/docs/src/modules/components/Ad';
@@ -154,6 +155,8 @@ interface JsonSchemaTypeDisplayProps {
}
function JsonSchemaTypeDisplay({ schema, open, onOpenChange }: JsonSchemaTypeDisplayProps) {
+ const t = useTranslate();
+
let types: string[] = [];
if (typeof schema.const !== 'undefined') {
return (
@@ -182,19 +185,19 @@ function JsonSchemaTypeDisplay({ schema, open, onOpenChange }: JsonSchemaTypeDis
if (schema.type === 'object') {
return (
- object
+ {t('object').toLowerCase()}
);
}
if (schema.type === 'array') {
- return array of ;
+ return {t('arrayOf').toLowerCase()} ;
}
if (schema.anyOf) {
return (
- any of{' '}
+ {t('anyOf').toLowerCase()}{' '}
);
}
@@ -471,6 +474,8 @@ const description = 'An exhaustive reference for the Toolpad Studio file formats
export default function SchemaReference(props: SchemaReferenceProps) {
const { definitions, disableAd, location } = props;
+ const t = useTranslate();
+
const toc = [
{
text: 'Files',
@@ -502,12 +507,12 @@ export default function SchemaReference(props: SchemaReferenceProps) {
disableAd={disableAd}
disableToc={false}
location={location}
- title="Schema reference"
+ title={t('schemaReference')}
toc={toc}
>
- Schema reference
+ {t('schemaReference')}
;
+}
diff --git a/docs/src/modules/components/examples/CoreOtherExamples.tsx b/docs/src/modules/components/examples/CoreOtherExamples.tsx
new file mode 100644
index 00000000000..14a65c35045
--- /dev/null
+++ b/docs/src/modules/components/examples/CoreOtherExamples.tsx
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import ExamplesGrid from 'docs-toolpad/src/modules/components/examples/ExamplesGrid';
+import coreExamples from 'docs-toolpad/src/modules/components/examples/coreExamples';
+
+export default function CoreOtherExamples() {
+ return ;
+}
diff --git a/docs/src/modules/components/examples/ExamplesFeatured.tsx b/docs/src/modules/components/examples/ExamplesFeatured.tsx
new file mode 100644
index 00000000000..9aa7e3ae46f
--- /dev/null
+++ b/docs/src/modules/components/examples/ExamplesFeatured.tsx
@@ -0,0 +1,184 @@
+import * as React from 'react';
+import { useTranslate } from '@mui/docs/i18n';
+import Box from '@mui/material/Box';
+import Card from '@mui/material/Card';
+import CardMedia from '@mui/material/CardMedia';
+import Chip from '@mui/material/Chip';
+import Button from '@mui/material/Button';
+import IconButton from '@mui/material/IconButton';
+import Link from '@mui/material/Link';
+import Tooltip from '@mui/material/Tooltip';
+import Typography from '@mui/material/Typography';
+import SvgIcon from '@mui/material/SvgIcon';
+import Visibility from '@mui/icons-material/Visibility';
+import CodeRoundedIcon from '@mui/icons-material/CodeRounded';
+import OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded';
+import { useTheme } from '@mui/material/styles';
+import { sxChip } from 'docs/src/modules/components/AppNavDrawerItem';
+import { Example, versionGitHubLink } from './examplesUtils';
+
+interface FeaturedExamplesProps {
+ examples: Example[];
+}
+
+export default function ExamplesFeatured(props: FeaturedExamplesProps) {
+ const t = useTranslate();
+
+ const examples = props.examples.filter((example: Example) => example.featured === true);
+ const docsTheme = useTheme();
+
+ return (
+
+ {examples.map((example: Example) => {
+ const computedSrc =
+ docsTheme?.palette?.mode === 'dark' && example.srcDark ? example.srcDark : example.src;
+ return (
+
+
+ {example.title}
+ {example.new && }
+
+
+ {example.description}
+
+
+ .MuiCardMedia-root': {
+ filter: 'blur(4px)',
+ },
+ '&:hover > .MuiButtonBase-root': {
+ opacity: 1,
+ },
+ }}
+ >
+
+ }
+ data-ga-event-category="toolpad-core-template"
+ data-ga-event-label={example.title}
+ data-ga-event-action="preview-img"
+ sx={{
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ opacity: 0,
+ transition: 'opacity 0.5s ease',
+ backgroundColor: 'background.paper',
+ '&:hover': {
+ backgroundColor: 'background.default',
+ },
+ }}
+ >
+ {t('seeLivePreview')}
+
+
+
+
+ {example.stackBlitz === true ? (
+
+
+
+
+
+
+
+ ) : null}
+ {example.codeSandbox === true ? (
+
+
+
+
+
+
+
+ ) : null}
+
+
+
+
+
+
+ }
+ data-ga-event-category="toolpad-core-template"
+ data-ga-event-label={example.title}
+ data-ga-event-action="preview-img"
+ sx={{ alignSelf: 'self-start' }}
+ >
+ {t('livePreview')}
+
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/docs/src/modules/components/examples/ExamplesGrid.tsx b/docs/src/modules/components/examples/ExamplesGrid.tsx
new file mode 100644
index 00000000000..d6f61cc194a
--- /dev/null
+++ b/docs/src/modules/components/examples/ExamplesGrid.tsx
@@ -0,0 +1,148 @@
+import * as React from 'react';
+import { useTranslate } from '@mui/docs/i18n';
+import Card from '@mui/material/Card';
+import CardActions from '@mui/material/CardActions';
+import CardContent from '@mui/material/CardContent';
+import CardMedia from '@mui/material/CardMedia';
+import Button from '@mui/material/Button';
+import Grid from '@mui/material/Grid';
+import Typography from '@mui/material/Typography';
+import { alpha, useTheme } from '@mui/material/styles';
+import IconButton from '@mui/material/IconButton';
+import Tooltip from '@mui/material/Tooltip';
+import Stack from '@mui/material/Stack';
+import SvgIcon from '@mui/material/SvgIcon';
+import { Example, versionGitHubLink } from './examplesUtils';
+
+interface ExamplesGridProps {
+ examples: Example[];
+}
+
+function StackBlitzIcon() {
+ return (
+
+
+
+ );
+}
+
+function CodeSandboxIcon() {
+ return (
+
+
+
+ );
+}
+
+export default function ExamplesGrid(props: ExamplesGridProps) {
+ const t = useTranslate();
+
+ const examples = props.examples.filter((example: Example) => example.featured !== true);
+ const docsTheme = useTheme();
+
+ return (
+
+ {examples.map((example) => {
+ const computedSrc =
+ docsTheme?.palette?.mode === 'dark' && example.srcDark ? example.srcDark : example.src;
+ return (
+
+ ({
+ height: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ px: 2,
+ pt: 2,
+ pb: 1,
+ gap: 1.5,
+ borderRadius: 1,
+ backgroundColor: `${alpha(theme.palette.grey[50], 0.4)}`,
+ borderColor: 'divider',
+ ...theme.applyStyles('dark', {
+ backgroundColor: `${alpha(theme.palette.primary.dark, 0.1)}`,
+ borderColor: 'divider',
+ }),
+ })}
+ variant="outlined"
+ >
+ ({
+ height: 0,
+ pt: '65%',
+ borderRadius: 0.5,
+ bgcolor: 'currentColor',
+ border: '1px solid',
+ borderColor: 'grey.100',
+ color: 'grey.100',
+ ...theme.applyStyles('dark', {
+ borderColor: 'grey.900',
+ color: 'primaryDark.900',
+ }),
+ })}
+ />
+
+
+ {example.title}
+
+
+ {example.description}
+
+
+
+
+ {t('source')}
+
+
+ {example.stackBlitz === true ? (
+
+
+
+
+
+ ) : null}
+ {example.codeSandbox === true ? (
+
+
+
+
+
+ ) : null}
+
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/docs/src/modules/components/examples/StudioExamples.tsx b/docs/src/modules/components/examples/StudioExamples.tsx
new file mode 100644
index 00000000000..92adacac090
--- /dev/null
+++ b/docs/src/modules/components/examples/StudioExamples.tsx
@@ -0,0 +1,137 @@
+import * as React from 'react';
+import ExamplesGrid from 'docs-toolpad/src/modules/components/examples/ExamplesGrid';
+import type { Example } from './examplesUtils';
+
+const examples = [
+ {
+ title: 'npm stats',
+ description:
+ 'This analytics dashboard shows how to track a KPI from a third-party data source.',
+ src: '/static/toolpad/docs/studio/examples/npm-stats.png',
+ srcDark: '/static/toolpad/docs/studio/examples/npm-stats.png', // TODO fix
+ href: 'https://mui.com/toolpad/studio/examples/npm-stats/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/npm-stats/',
+ },
+ {
+ title: 'Basic CRUD application',
+ description: 'An admin application to showcase how CRUD operations work in Toolpad Studio.',
+ src: '/static/toolpad/docs/studio/examples/basic-crud-app.png',
+ srcDark: '/static/toolpad/docs/studio/examples/basic-crud-app.png', // TODO fix
+ href: 'https://mui.com/toolpad/studio/examples/basic-crud-app/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/basic-crud-app/',
+ },
+ {
+ title: 'QR Code generator',
+ description:
+ 'A basic Toolpad Studio application that can be used to turn any text or URL into a QR code.',
+ src: '/static/toolpad/docs/studio/examples/qr-generator.png',
+ srcDark: '/static/toolpad/docs/studio/examples/qr-generator.png', // TODO fix
+ href: 'https://mui.com/toolpad/studio/examples/qr-generator/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/qr-generator/',
+ },
+ {
+ title: 'With Prisma',
+ description:
+ 'A basic Toolpad Studio application that demonstrates how to integrate with Prisma.',
+ src: '/static/toolpad/marketing/with-prisma-hero.png',
+ srcDark: '/static/toolpad/marketing/with-prisma-hero.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/with-prisma/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/with-prisma/',
+ },
+ {
+ title: 'Google Sheet',
+ description: 'Quickly fetch data from Google Sheets to build a Toolpad Studio app.',
+ src: '/static/toolpad/marketing/google-sheet.png',
+ srcDark: '/static/toolpad/marketing/google-sheet.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/google-sheet/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/google-sheet/',
+ },
+ {
+ title: 'Dog app',
+ description: 'An app that shows dog images based on selected breeds or sub-breeds.',
+ src: '/static/toolpad/docs/studio/getting-started/first-app/step-13.png',
+ srcDark: '/static/toolpad/docs/studio/getting-started/first-app/step-13.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/dog-app/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/dog-app/',
+ },
+ {
+ title: 'Customized data grid',
+ description:
+ 'A basic Toolpad Studio app that shows how to customize a data grid column using a custom code component.',
+ src: '/static/toolpad/marketing/custom-datagrid-column.png',
+ srcDark: '/static/toolpad/marketing/custom-datagrid-column.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/custom-datagrid-column',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/custom-datagrid-column/',
+ },
+ {
+ title: 'GraphQL app',
+ description: 'An app that shows latest 100 stargazers info for any GitHub repository.',
+ src: '/static/toolpad/marketing/graphql.png',
+ srcDark: '/static/toolpad/marketing/graphql.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/graphql/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/graphql/',
+ },
+ {
+ title: 'With WASM',
+ description:
+ 'A basic Toolpad Studio application that demonstrates integrating with WASM modules.',
+ src: '/static/toolpad/marketing/with-wasm.png',
+ srcDark: '/static/toolpad/marketing/with-wasm.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/with-wasm/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/with-wasm/',
+ },
+ {
+ title: 'Data provider with prisma',
+ description:
+ 'A basic Toolpad Studio application that demonstrates how to use data providers with Prisma.',
+ src: '/static/toolpad/marketing/with-prisma-data-provider.png',
+ srcDark: '/static/toolpad/marketing/with-prisma-data-provider.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/with-prisma-data-provider/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/with-prisma-data-provider/',
+ },
+ {
+ title: 'With Supabase',
+ description:
+ 'A Toolpad Studio app that fetches data from Supabase and shows it in a list component.',
+ src: '/static/toolpad/marketing/supabase.png',
+ srcDark: '/static/toolpad/marketing/supabase.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/supabase/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/supabase/',
+ },
+ {
+ title: 'Stripe invoice downloader',
+ description: 'A Stripe app to fetch and download invoices.',
+ src: '/static/toolpad/marketing/stripe-script.png',
+ srcDark: '/static/toolpad/marketing/stripe-script.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/stripe-script/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/stripe-script/',
+ },
+ {
+ title: 'Charts',
+ description: 'A basic Toolpad Studio application that demonstrates how to use chart component.',
+ src: '/static/toolpad/marketing/charts.png',
+ srcDark: '/static/toolpad/marketing/charts.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/charts/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/charts/',
+ },
+ {
+ title: 'Google Authentication',
+ description: 'An app that shows how to set up Google authentication in Toolpad Studio.',
+ src: '/static/toolpad/marketing/auth-google.png',
+ srcDark: '/static/toolpad/marketing/auth-google.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/auth-google/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/auth-google/',
+ },
+ {
+ title: 'Custom server',
+ description: 'An app that shows how to use Toolpad Studio with a custom server.',
+ src: '/static/toolpad/marketing/custom-server.png',
+ srcDark: '/static/toolpad/marketing/custom-server.png', // TODO fix
+ href: 'https://github.com/mui/toolpad/tree/master/examples/studio/custom-server/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/studio/custom-server/',
+ },
+] satisfies Example[];
+
+export default function StudioExamples() {
+ return ;
+}
diff --git a/docs/src/modules/components/examples/coreExamples.ts b/docs/src/modules/components/examples/coreExamples.ts
new file mode 100644
index 00000000000..71449535b2a
--- /dev/null
+++ b/docs/src/modules/components/examples/coreExamples.ts
@@ -0,0 +1,118 @@
+import type { Example } from './examplesUtils';
+
+const coreExamples = [
+ {
+ title: 'Functional dashboard',
+ description:
+ 'This example shows you how to get started building a dashboard with Toolpad Core, Next.js App Router, Auth.js and Material UI components in a customized theme.',
+ src: '/static/toolpad/docs/core/functional-dashboard.png',
+ href: 'https://mui.com/toolpad/core/templates/nextjs-dashboard/',
+ srcDark: '/static/toolpad/docs/core/functional-dashboard-dark.png',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-themed',
+ featured: true,
+ new: true,
+ codeSandbox: true,
+ stackBlitz: true,
+ },
+ {
+ title: 'Vite with React Router and Firebase Auth',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Vite, React Router, and authentication using Firebase.',
+ src: '/static/toolpad/docs/core/firebase-vite-light.png',
+ srcDark: '/static/toolpad/docs/core/firebase-vite-dark.png',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/firebase-vite',
+ // Show nothing
+ codeSandbox: false,
+ stackBlitz: false,
+ },
+ {
+ title: 'Vite with React Router and mock authentication',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Vite, React Router and any external authentication provider.',
+ src: '/static/toolpad/docs/core/vite-react-router.png',
+ srcDark: '/static/toolpad/docs/core/vite-react-router.png', // TODO Fix
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/auth-vite',
+ codeSandbox: true,
+ // Show nothing
+ stackBlitz: false,
+ },
+ {
+ title: 'Next.js App Router with Auth.js Passkey',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Auth.js Passkeys and the Next.js App Router.',
+ src: '/static/toolpad/docs/core/auth-next-passkey.png',
+ srcDark: '/static/toolpad/docs/core/auth-next-passkey-dark.png',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-passkey/',
+ // Crash with Prisma
+ codeSandbox: false,
+ stackBlitz: false,
+ },
+ {
+ title: 'Auth.js v4 with Next.js Pages Router',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Auth.js v4 and the Next.js Pages router.',
+ src: '/static/toolpad/docs/core/auth-next.png',
+ srcDark: '/static/toolpad/docs/core/auth-next-dark.png',
+ source:
+ 'https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-pages-nextauth-4/',
+ codeSandbox: true,
+ // Show nothing
+ stackBlitz: false,
+ },
+ {
+ title: 'Vite with React Router',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Vite and React Router.',
+ src: '/static/toolpad/docs/core/vite-react-router.png',
+ srcDark: '/static/toolpad/docs/core/vite-react-router.png', // TODO Fix
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/vite/',
+ codeSandbox: true,
+ // Show nothing
+ stackBlitz: false,
+ },
+ {
+ title: 'Auth.js Magic Link with Next.js App Router',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Auth.js Magic Links and the Next.js App Router.',
+ src: '/static/toolpad/docs/core/auth-next.png',
+ srcDark: '/static/toolpad/docs/core/auth-next.png', // TODO Fix
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-email',
+ // Crash with Prisma
+ codeSandbox: false,
+ stackBlitz: false,
+ },
+ {
+ title: 'Auth.js with Next.js Pages Router',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Auth.js and the Next.js Pages router.',
+ src: '/static/toolpad/docs/core/auth-next.png',
+ srcDark: '/static/toolpad/docs/core/auth-next-dark.png',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-pages/',
+ // infinite redirection
+ codeSandbox: false,
+ stackBlitz: false,
+ },
+ {
+ title: 'Auth.js with Next.js App Router',
+ description:
+ 'This app shows you to how to get started using Toolpad Core with Auth.js and the Next.js App Router.',
+ src: '/static/toolpad/docs/core/auth-next.png',
+ srcDark: '/static/toolpad/docs/core/auth-next-dark.png',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs/',
+ codeSandbox: true,
+ stackBlitz: true,
+ },
+ {
+ title: 'Tutorial app',
+ description:
+ 'This app shows you to get started with Toolpad Core and use basic layout and navigation features.',
+ src: '/static/toolpad/docs/core/tutorial-1.png',
+ srcDark: '/static/toolpad/docs/core/tutorial-1.png', // TODO Fix
+ href: 'https://mui.com/toolpad/core/introduction/tutorial/',
+ source: 'https://github.com/mui/toolpad/tree/master/examples/core/tutorial/',
+ codeSandbox: true,
+ stackBlitz: true,
+ },
+] satisfies Example[];
+
+export default coreExamples;
diff --git a/docs/src/modules/components/examples/examplesUtils.ts b/docs/src/modules/components/examples/examplesUtils.ts
new file mode 100644
index 00000000000..54404bbcf6a
--- /dev/null
+++ b/docs/src/modules/components/examples/examplesUtils.ts
@@ -0,0 +1,31 @@
+// TODO move to docs-infra as a shared helper
+/**
+ * This function allows to turn link from the docs to GitHub to be closer to permalink.
+ * Meaning, its purpose is so that we can version the docs while having it continue to work, or
+ * be able to introduce breaking changes on a next active branch without breaking the docs experience
+ * for stable version docs users.
+ */
+export function versionGitHubLink(href: string) {
+ // Bailed out, not a link that needs to be handled.
+ if (!href.startsWith(`${process.env.SOURCE_CODE_REPO}/tree/master`)) {
+ return href;
+ }
+
+ return href.replace(
+ `${process.env.SOURCE_CODE_REPO}/tree/master`,
+ `${process.env.SOURCE_CODE_REPO}/blob/v${process.env.LIB_VERSION}`,
+ );
+}
+
+export interface Example {
+ title: string;
+ description: string;
+ src: string;
+ srcDark: string;
+ href?: string;
+ source: string;
+ codeSandbox?: boolean;
+ stackBlitz?: boolean;
+ new?: boolean;
+ featured?: boolean;
+}
diff --git a/docs/translations/api-docs/account-popover-footer/account-popover-footer.json b/docs/translations/api-docs/account-popover-footer/account-popover-footer.json
new file mode 100644
index 00000000000..6302728c133
--- /dev/null
+++ b/docs/translations/api-docs/account-popover-footer/account-popover-footer.json
@@ -0,0 +1,9 @@
+{
+ "componentDescription": "",
+ "propDescriptions": {
+ "sx": {
+ "description": "The system prop that allows defining system overrides as well as additional CSS styles."
+ }
+ },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/account-popover-header/account-popover-header.json b/docs/translations/api-docs/account-popover-header/account-popover-header.json
new file mode 100644
index 00000000000..6d480ad874c
--- /dev/null
+++ b/docs/translations/api-docs/account-popover-header/account-popover-header.json
@@ -0,0 +1,5 @@
+{
+ "componentDescription": "",
+ "propDescriptions": { "children": { "description": "The content of the component." } },
+ "classDescriptions": {}
+}
diff --git a/docs/translations/api-docs/account-preview/account-preview.json b/docs/translations/api-docs/account-preview/account-preview.json
new file mode 100644
index 00000000000..03e316bb4e3
--- /dev/null
+++ b/docs/translations/api-docs/account-preview/account-preview.json
@@ -0,0 +1,17 @@
+{
+ "componentDescription": "The AccountPreview component displays user account information.",
+ "propDescriptions": {
+ "handleClick": { "description": "The handler used when the preview is expanded" },
+ "open": { "description": "The state of the Account popover" },
+ "slotProps": { "description": "The props used for each slot inside." },
+ "slots": { "description": "The components used for each slot inside." },
+ "sx": { "description": "The prop used to customize the styling of the preview" },
+ "variant": { "description": "The type of account details to display." }
+ },
+ "classDescriptions": {},
+ "slotDescriptions": {
+ "avatar": "The component used for the Avatar",
+ "avatarIconButton": "The component used for the avatar icon button in the condensed variant",
+ "moreIconButton": "The component used for the overflow icon button in the expanded variant"
+ }
+}
diff --git a/docs/translations/api-docs/account/account.json b/docs/translations/api-docs/account/account.json
index 58620d755a2..de7187c1fcc 100644
--- a/docs/translations/api-docs/account/account.json
+++ b/docs/translations/api-docs/account/account.json
@@ -7,7 +7,9 @@
},
"classDescriptions": {},
"slotDescriptions": {
- "menuItems": "The component used for the custom menu items.",
+ "popover": "The component used for the account popover menu",
+ "popoverContent": "The component used for the content of account popover",
+ "preview": "The component used for the account preview",
"signInButton": "The component used for the sign in button.",
"signOutButton": "The component used for the sign out button."
}
diff --git a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json
index c54326d399f..a9668478927 100644
--- a/docs/translations/api-docs/dashboard-layout/dashboard-layout.json
+++ b/docs/translations/api-docs/dashboard-layout/dashboard-layout.json
@@ -1,6 +1,7 @@
{
"componentDescription": "",
"propDescriptions": {
+ "branding": { "description": "Branding options for the dashboard." },
"children": { "description": "The content of the dashboard." },
"defaultSidebarCollapsed": {
"description": "Whether the sidebar should start collapsed in desktop size screens."
@@ -11,6 +12,8 @@
"hideNavigation": {
"description": "Whether the navigation bar and menu icon should be hidden"
},
+ "navigation": { "description": "Navigation definition for the dashboard." },
+ "sidebarExpandedWidth": { "description": "Width of the sidebar when expanded." },
"slotProps": { "description": "The props used for each slot inside." },
"slots": { "description": "The components used for each slot inside." },
"sx": {
@@ -19,6 +22,7 @@
},
"classDescriptions": {},
"slotDescriptions": {
+ "appTitle": "The component used for the app title section in the layout header.",
"sidebarFooter": "Optional footer component used in the layout sidebar.",
"toolbarAccount": "The toolbar account component used in the layout header.",
"toolbarActions": "The toolbar actions component used in the layout header."
diff --git a/docs/translations/api-docs/page-container/page-container.json b/docs/translations/api-docs/page-container/page-container.json
index 3ff8ccbdbd2..80156f5a92c 100644
--- a/docs/translations/api-docs/page-container/page-container.json
+++ b/docs/translations/api-docs/page-container/page-container.json
@@ -6,6 +6,9 @@
},
"slotProps": { "description": "The props used for each slot inside." },
"slots": { "description": "The components used for each slot inside." },
+ "sx": {
+ "description": "The system prop that allows defining system overrides as well as additional CSS styles."
+ },
"title": { "description": "The title of the page. Leave blank to use the active page title." }
},
"classDescriptions": {
@@ -46,5 +49,5 @@
},
"root": { "description": "Styles applied to the root element." }
},
- "slotDescriptions": { "toolbar": "The component that renders the actions toolbar." }
+ "slotDescriptions": { "header": "The component that renders the page header." }
}
diff --git a/docs/translations/api-docs/page-container-toolbar/page-container-toolbar.json b/docs/translations/api-docs/page-header-toolbar/page-header-toolbar.json
similarity index 100%
rename from docs/translations/api-docs/page-container-toolbar/page-container-toolbar.json
rename to docs/translations/api-docs/page-header-toolbar/page-header-toolbar.json
diff --git a/docs/translations/api-docs/page-header/page-header.json b/docs/translations/api-docs/page-header/page-header.json
new file mode 100644
index 00000000000..0e7d13e55a2
--- /dev/null
+++ b/docs/translations/api-docs/page-header/page-header.json
@@ -0,0 +1,13 @@
+{
+ "componentDescription": "A header component to provide a title and breadcrumbs for your pages.",
+ "propDescriptions": {
+ "breadcrumbs": {
+ "description": "The breadcrumbs of the page. Leave blank to use the active page breadcrumbs."
+ },
+ "slotProps": { "description": "The props used for each slot inside." },
+ "slots": { "description": "The components used for each slot inside." },
+ "title": { "description": "The title of the page. Leave blank to use the active page title." }
+ },
+ "classDescriptions": {},
+ "slotDescriptions": { "toolbar": "The component that renders the actions toolbar." }
+}
diff --git a/docs/translations/api-docs/sign-in-button/sign-in-button.json b/docs/translations/api-docs/sign-in-button/sign-in-button.json
new file mode 100644
index 00000000000..77a6eff5224
--- /dev/null
+++ b/docs/translations/api-docs/sign-in-button/sign-in-button.json
@@ -0,0 +1,301 @@
+{
+ "componentDescription": "",
+ "propDescriptions": { "children": { "description": "The content of the component." } },
+ "classDescriptions": {
+ "colorError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"error\"
"
+ },
+ "colorInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"info\"
"
+ },
+ "colorInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"inherit\"
"
+ },
+ "colorPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"primary\"
"
+ },
+ "colorSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"secondary\"
"
+ },
+ "colorSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"success\"
"
+ },
+ "colorWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"warning\"
"
+ },
+ "contained": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
"
+ },
+ "containedError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"error\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorError classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"info\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorInfo classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"inherit\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorInherit classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"primary\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorPrimary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"secondary\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorSecondary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
and variant=\"contained\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeLarge and .MuiButton-contained classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
and variant=\"contained\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeMedium and .MuiButton-contained classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
and variant=\"contained\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeSmall and .MuiButton-contained classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"success\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorSuccess classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"warning\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorWarning classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "disabled": {
+ "description": "State class applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "disabled={true}
"
+ },
+ "disableElevation": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "disableElevation={true}
"
+ },
+ "endIcon": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the endIcon element",
+ "conditions": "supplied"
+ },
+ "focusVisible": {
+ "description": "State class applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the ButtonBase root element",
+ "conditions": "the button is keyboard focused"
+ },
+ "fullWidth": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "fullWidth={true}
"
+ },
+ "icon": { "description": "Styles applied to the icon element if supplied" },
+ "iconSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the icon element",
+ "conditions": "supplied and size=\"large\"
",
+ "deprecationInfo": "Combine the .MuiButton-icon and .MuiButtonSizeLarge classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "iconSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the icon element",
+ "conditions": "supplied and size=\"medium\"
",
+ "deprecationInfo": "Combine the .MuiButton-icon and .MuiButtonSizeMedium classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "iconSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the icon element",
+ "conditions": "supplied and size=\"small\"
",
+ "deprecationInfo": "Combine the .MuiButton-icon and .MuiButtonSizeSmall classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlined": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
"
+ },
+ "outlinedError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"error\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorError classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"info\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorInfo classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"inherit\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorInherit classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"primary\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorPrimary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"secondary\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorSecondary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
and variant=\"outlined\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeLarge and .MuiButton-outlined classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
and variant=\"outlined\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeMedium and .MuiButton-outlined classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
and variant=\"outlined\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeSmall and .MuiButton-outlined classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"success\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorSuccess classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"warning\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorWarning classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "root": { "description": "Styles applied to the root element." },
+ "sizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
"
+ },
+ "sizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
"
+ },
+ "sizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
"
+ },
+ "startIcon": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the startIcon element",
+ "conditions": "supplied"
+ },
+ "text": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
"
+ },
+ "textError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"error\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorError classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"info\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorInfo classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"inherit\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorInherit classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"primary\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorPrimary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"secondary\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorSecondary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
and variant=\"text\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeLarge and .MuiButton-text classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
and variant=\"text\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeMedium and .MuiButton-text classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
and variant=\"text\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeSmall and .MuiButton-text classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"success\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorSuccess classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"warning\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorWarning classes instead. See Migrating from deprecated APIs for more details."
+ }
+ }
+}
diff --git a/docs/translations/api-docs/sign-in-page/sign-in-page.json b/docs/translations/api-docs/sign-in-page/sign-in-page.json
index 066ea0ed30d..de36472fb55 100644
--- a/docs/translations/api-docs/sign-in-page/sign-in-page.json
+++ b/docs/translations/api-docs/sign-in-page/sign-in-page.json
@@ -11,14 +11,20 @@
}
},
"slotProps": { "description": "The props used for each slot inside." },
- "slots": { "description": "The components used for each slot inside." }
+ "slots": { "description": "The components used for each slot inside." },
+ "sx": {
+ "description": "The prop used to customize the styles on the SignInPage
container"
+ }
},
"classDescriptions": {},
"slotDescriptions": {
"emailField": "The custom email field component used in the credentials form.",
"forgotPasswordLink": "The custom forgot password link component used in the credentials form.",
"passwordField": "The custom password field component used in the credentials form.",
+ "rememberMe": "A component to override the default "Remember me" checkbox in the Credentials form",
"signUpLink": "The custom sign up link component used in the credentials form.",
- "submitButton": "The custom submit button component used in the credentials form."
+ "submitButton": "The custom submit button component used in the credentials form.",
+ "subtitle": "A component to override the default subtitle section",
+ "title": "A component to override the default title section"
}
}
diff --git a/docs/translations/api-docs/sign-out-button/sign-out-button.json b/docs/translations/api-docs/sign-out-button/sign-out-button.json
new file mode 100644
index 00000000000..77a6eff5224
--- /dev/null
+++ b/docs/translations/api-docs/sign-out-button/sign-out-button.json
@@ -0,0 +1,301 @@
+{
+ "componentDescription": "",
+ "propDescriptions": { "children": { "description": "The content of the component." } },
+ "classDescriptions": {
+ "colorError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"error\"
"
+ },
+ "colorInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"info\"
"
+ },
+ "colorInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"inherit\"
"
+ },
+ "colorPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"primary\"
"
+ },
+ "colorSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"secondary\"
"
+ },
+ "colorSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"success\"
"
+ },
+ "colorWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "color=\"warning\"
"
+ },
+ "contained": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
"
+ },
+ "containedError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"error\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorError classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"info\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorInfo classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"inherit\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorInherit classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"primary\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorPrimary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"secondary\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorSecondary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
and variant=\"contained\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeLarge and .MuiButton-contained classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
and variant=\"contained\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeMedium and .MuiButton-contained classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
and variant=\"contained\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeSmall and .MuiButton-contained classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"success\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorSuccess classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "containedWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"contained\"
and color=\"warning\"
",
+ "deprecationInfo": "Combine the .MuiButton-contained and .MuiButton-colorWarning classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "disabled": {
+ "description": "State class applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "disabled={true}
"
+ },
+ "disableElevation": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "disableElevation={true}
"
+ },
+ "endIcon": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the endIcon element",
+ "conditions": "supplied"
+ },
+ "focusVisible": {
+ "description": "State class applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the ButtonBase root element",
+ "conditions": "the button is keyboard focused"
+ },
+ "fullWidth": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "fullWidth={true}
"
+ },
+ "icon": { "description": "Styles applied to the icon element if supplied" },
+ "iconSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the icon element",
+ "conditions": "supplied and size=\"large\"
",
+ "deprecationInfo": "Combine the .MuiButton-icon and .MuiButtonSizeLarge classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "iconSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the icon element",
+ "conditions": "supplied and size=\"medium\"
",
+ "deprecationInfo": "Combine the .MuiButton-icon and .MuiButtonSizeMedium classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "iconSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the icon element",
+ "conditions": "supplied and size=\"small\"
",
+ "deprecationInfo": "Combine the .MuiButton-icon and .MuiButtonSizeSmall classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlined": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
"
+ },
+ "outlinedError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"error\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorError classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"info\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorInfo classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"inherit\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorInherit classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"primary\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorPrimary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"secondary\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorSecondary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
and variant=\"outlined\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeLarge and .MuiButton-outlined classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
and variant=\"outlined\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeMedium and .MuiButton-outlined classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
and variant=\"outlined\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeSmall and .MuiButton-outlined classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"success\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorSuccess classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "outlinedWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"outlined\"
and color=\"warning\"
",
+ "deprecationInfo": "Combine the .MuiButton-outlined and .MuiButton-colorWarning classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "root": { "description": "Styles applied to the root element." },
+ "sizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
"
+ },
+ "sizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
"
+ },
+ "sizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
"
+ },
+ "startIcon": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the startIcon element",
+ "conditions": "supplied"
+ },
+ "text": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
"
+ },
+ "textError": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"error\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorError classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textInfo": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"info\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorInfo classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textInherit": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"inherit\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorInherit classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textPrimary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"primary\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorPrimary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSecondary": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"secondary\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorSecondary classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSizeLarge": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"large\"
and variant=\"text\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeLarge and .MuiButton-text classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSizeMedium": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"medium\"
and variant=\"text\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeMedium and .MuiButton-text classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSizeSmall": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "size=\"small\"
and variant=\"text\"
",
+ "deprecationInfo": "Combine the .MuiButton-sizeSmall and .MuiButton-text classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textSuccess": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"success\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorSuccess classes instead. See Migrating from deprecated APIs for more details."
+ },
+ "textWarning": {
+ "description": "Styles applied to {{nodeName}} if {{conditions}}.",
+ "nodeName": "the root element",
+ "conditions": "variant=\"text\"
and color=\"warning\"
",
+ "deprecationInfo": "Combine the .MuiButton-text and .MuiButton-colorWarning classes instead. See Migrating from deprecated APIs for more details."
+ }
+ }
+}
diff --git a/docs/translations/api-docs/theme-switcher/theme-switcher.json b/docs/translations/api-docs/theme-switcher/theme-switcher.json
new file mode 100644
index 00000000000..f93d4cbd8c7
--- /dev/null
+++ b/docs/translations/api-docs/theme-switcher/theme-switcher.json
@@ -0,0 +1 @@
+{ "componentDescription": "", "propDescriptions": {}, "classDescriptions": {} }
diff --git a/docs/translations/api-docs/toolbar-actions/toolbar-actions.json b/docs/translations/api-docs/toolbar-actions/toolbar-actions.json
new file mode 100644
index 00000000000..f93d4cbd8c7
--- /dev/null
+++ b/docs/translations/api-docs/toolbar-actions/toolbar-actions.json
@@ -0,0 +1 @@
+{ "componentDescription": "", "propDescriptions": {}, "classDescriptions": {} }
diff --git a/docs/translations/translations.json b/docs/translations/translations.json
index 0967ef424bc..ec93f1ea90c 100644
--- a/docs/translations/translations.json
+++ b/docs/translations/translations.json
@@ -1 +1,11 @@
-{}
+{
+ "anyOf": "Any of",
+ "arrayOf": "Array of",
+ "codesandboxPlayground": "CodeSandbox playground",
+ "livePreview": "Live preview",
+ "object": "Object",
+ "schemaReference": "Schema reference",
+ "seeLivePreview": "See live preview",
+ "source": "Source",
+ "stackblitzPlayground": "StackBlitz playground"
+}
diff --git a/eslintWebpackResolverConfig.js b/eslintWebpackResolverConfig.js
index eed621fd384..c0f97db54d3 100644
--- a/eslintWebpackResolverConfig.js
+++ b/eslintWebpackResolverConfig.js
@@ -17,6 +17,7 @@ module.exports = {
'@toolpad/studio-runtime': path.resolve(__dirname, './packages/toolpad-studio-runtime/src'),
'@toolpad/utils': path.resolve(__dirname, './packages/toolpad-utils/src'),
'@toolpad/core': path.resolve(__dirname, './packages/toolpad-core/src'),
+ '@toolpad/studio-tests': path.resolve(__dirname, './test'),
docs: path.resolve(__dirname, './node_modules/@mui/monorepo/docs'),
'docs-toolpad': path.resolve(__dirname, './docs'),
},
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/.eslintrc.json b/examples/core/auth-nextjs-email/.eslintrc.json
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/.eslintrc.json
rename to examples/core/auth-nextjs-email/.eslintrc.json
diff --git a/examples/core/auth-nextjs-email/.gitignore b/examples/core/auth-nextjs-email/.gitignore
new file mode 100644
index 00000000000..68c5d18f00d
--- /dev/null
+++ b/examples/core/auth-nextjs-email/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/examples/core/auth-nextjs-email/Dockerfile b/examples/core/auth-nextjs-email/Dockerfile
new file mode 100644
index 00000000000..a79b85781fd
--- /dev/null
+++ b/examples/core/auth-nextjs-email/Dockerfile
@@ -0,0 +1,45 @@
+# Use Node.js 20 Alpine as the base image
+FROM node:20-alpine AS builder
+
+# Set working directory
+WORKDIR /app
+
+# Copy package.json and package-lock.json
+COPY package*.json ./
+
+# Install dependencies
+RUN npm install
+
+# Copy all files
+COPY . .
+
+# Build the Next.js app
+RUN npm run build
+
+# Start a new stage for a smaller final image
+FROM node:20-alpine AS runner
+
+WORKDIR /app
+
+# Copy built assets from the builder stage
+COPY --from=builder /app/.next ./.next
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/package.json ./package.json
+COPY --from=builder /app/src/prisma ./prisma
+
+# Set environment variables
+ENV NODE_ENV production
+ENV PORT 3000
+
+# Copy the entrypoint script
+COPY entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+# Set the entrypoint
+ENTRYPOINT ["/entrypoint.sh"]
+
+# Expose the port Next.js runs on
+EXPOSE 3000
+
+# Run the Next.js app
+CMD ["npm", "start"]
diff --git a/examples/core/auth-nextjs-email/README.md b/examples/core/auth-nextjs-email/README.md
new file mode 100644
index 00000000000..ef8a4f3cdce
--- /dev/null
+++ b/examples/core/auth-nextjs-email/README.md
@@ -0,0 +1,72 @@
+# Toolpad Core - Next.js App Router app with email provider
+
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+1. You need to have a Postgres database running. You can use the following docker command to start a Postgres database:
+
+```bash
+docker run --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres
+```
+
+2. For the database created above, the connection string is `postgresql://postgres:postgres@localhost:5432/postgres`.
+
+3. Update the `DATABASE_URL` environment variable in the `.env` file with the connection string for the database you created above.
+
+4. Then, generate the Prisma Client:
+
+```bash
+npx prisma migrate dev --schema=./src/prisma/schema.prisma
+```
+
+5. You also need to supply the following enviroment variables for the email server to work:
+
+```bash
+EMAIL_SERVER_HOST=
+EMAIL_SERVER_PORT=
+EMAIL_SERVER_USER=
+EMAIL_SERVER_PASSWORD=
+EMAIL_FROM=
+```
+
+6. Finally, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+7. Open [http://localhost:3000](http://localhost:3000) with your browser to see the app running.
+
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example auth-nextjs-email
+# or
+pnpm dlx create-toolpad-app@latest --example auth-nextjs-email
+```
+
+and follow the instructions in the terminal.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/examples/core/auth-nextjs-email/docker-compose.yml b/examples/core/auth-nextjs-email/docker-compose.yml
new file mode 100644
index 00000000000..29b8ce23cc1
--- /dev/null
+++ b/examples/core/auth-nextjs-email/docker-compose.yml
@@ -0,0 +1,52 @@
+version: '3.8'
+
+services:
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - '3000:3000'
+ environment:
+ - DATABASE_URL=${DATABASE_URL}
+ - AUTH_URL=${AUTH_URL}
+ - AUTH_TRUST_HOST=true
+ - NODE_ENV=production
+ - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
+ - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
+ - AUTH_SECRET=${AUTH_SECRET}
+ - EMAIL_SERVER_HOST=${EMAIL_SERVER_HOST}
+ - EMAIL_SERVER_PORT=${EMAIL_SERVER_PORT}
+ - EMAIL_SERVER_USER=${EMAIL_SERVER_USER}
+ - EMAIL_SERVER_PASSWORD=${EMAIL_SERVER_PASSWORD}
+ - EMAIL_FROM=${EMAIL_FROM}
+ depends_on:
+ db:
+ condition: service_healthy
+ networks:
+ - app-network
+
+ db:
+ image: postgres:13
+ environment:
+ - POSTGRES_USER=${POSTGRES_USER}
+ - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+ - POSTGRES_DB=${POSTGRES_DB}
+ ports:
+ - '${POSTGRES_PORT}:5432'
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ networks:
+ - app-network
+
+volumes:
+ postgres_data:
+
+networks:
+ app-network:
+ driver: bridge
diff --git a/examples/core/auth-nextjs-email/entrypoint.sh b/examples/core/auth-nextjs-email/entrypoint.sh
new file mode 100644
index 00000000000..3b20a40d898
--- /dev/null
+++ b/examples/core/auth-nextjs-email/entrypoint.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# Wait for the database to be ready
+until nc -z db 5432; do
+ echo "Waiting for database to be ready..."
+ sleep 2
+done
+
+# Run migrations
+npx prisma migrate deploy
+npx prisma generate
+
+# Start the application
+exec npm start
\ No newline at end of file
diff --git a/examples/core-tutorial/next-env.d.ts b/examples/core/auth-nextjs-email/next-env.d.ts
similarity index 100%
rename from examples/core-tutorial/next-env.d.ts
rename to examples/core/auth-nextjs-email/next-env.d.ts
diff --git a/examples/core-auth-nextjs/next.config.mjs b/examples/core/auth-nextjs-email/next.config.mjs
similarity index 100%
rename from examples/core-auth-nextjs/next.config.mjs
rename to examples/core/auth-nextjs-email/next.config.mjs
diff --git a/examples/core/auth-nextjs-email/package.json b/examples/core/auth-nextjs-email/package.json
new file mode 100644
index 00000000000..90f0cc9e6b8
--- /dev/null
+++ b/examples/core/auth-nextjs-email/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "playground-nextjs-email",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@emotion/react": "^11",
+ "@emotion/styled": "^11",
+ "@mui/icons-material": "^6",
+ "@mui/material": "^6",
+ "@mui/material-nextjs": "^6",
+ "@toolpad/core": "latest",
+ "@prisma/client": "^5",
+ "@auth/prisma-adapter": "^2",
+ "next": "^15",
+ "next-auth": "5.0.0-beta.25",
+ "nodemailer": "^6",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "typescript": "^5",
+ "@types/node": "^20.17.12",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "eslint": "^8",
+ "prisma": "^5",
+ "eslint-config-next": "^15"
+ }
+}
diff --git a/examples/core/auth-nextjs-email/src/app/(dashboard)/layout.tsx b/examples/core/auth-nextjs-email/src/app/(dashboard)/layout.tsx
new file mode 100644
index 00000000000..e481f628f1c
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/app/(dashboard)/layout.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default function DashboardPagesLayout(props: { children: React.ReactNode }) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/examples/core/auth-nextjs-email/src/app/(dashboard)/orders/page.tsx b/examples/core/auth-nextjs-email/src/app/(dashboard)/orders/page.tsx
new file mode 100644
index 00000000000..53104a52d33
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/app/(dashboard)/orders/page.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+import { redirect } from 'next/navigation';
+import { headers } from 'next/headers';
+import { auth } from '../../../auth';
+
+export default async function OrdersPage() {
+ const session = await auth();
+ const currentUrl =
+ (await headers()).get('referer') || process.env.AUTH_URL || 'http://localhost:3000';
+
+ if (!session) {
+ // Get the current URL to redirect to signIn with `callbackUrl`
+ const redirectUrl = new URL('/auth/signin', currentUrl);
+ redirectUrl.searchParams.set('callbackUrl', currentUrl);
+
+ redirect(redirectUrl.toString());
+ }
+ return Welcome to the Toolpad orders! ;
+}
diff --git a/examples/core/auth-nextjs-email/src/app/(dashboard)/page.tsx b/examples/core/auth-nextjs-email/src/app/(dashboard)/page.tsx
new file mode 100644
index 00000000000..d5ff3334cd2
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/app/(dashboard)/page.tsx
@@ -0,0 +1,24 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+import { redirect } from 'next/navigation';
+import { headers } from 'next/headers';
+import { auth } from '../../auth';
+
+export default async function HomePage() {
+ const session = await auth();
+ const currentUrl =
+ (await headers()).get('referer') || process.env.AUTH_URL || 'http://localhost:3000';
+
+ if (!session) {
+ // Get the current URL to redirect to signIn with `callbackUrl`
+ const redirectUrl = new URL('/auth/signin', currentUrl);
+ redirectUrl.searchParams.set('callbackUrl', currentUrl);
+
+ redirect(redirectUrl.toString());
+ }
+ return (
+
+ Welcome to Toolpad, {session?.user?.name || session?.user?.email || 'User'}!
+
+ );
+}
diff --git a/examples/core-auth-nextjs-pages/src/app/api/auth/[...nextauth]/route.ts b/examples/core/auth-nextjs-email/src/app/api/auth/[...nextauth]/route.ts
similarity index 100%
rename from examples/core-auth-nextjs-pages/src/app/api/auth/[...nextauth]/route.ts
rename to examples/core/auth-nextjs-email/src/app/api/auth/[...nextauth]/route.ts
diff --git a/examples/core/auth-nextjs-email/src/app/auth/signin/actions.ts b/examples/core/auth-nextjs-email/src/app/auth/signin/actions.ts
new file mode 100644
index 00000000000..7672bd32886
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/app/auth/signin/actions.ts
@@ -0,0 +1,47 @@
+'use server';
+import { AuthError } from 'next-auth';
+import type { AuthProvider } from '@toolpad/core';
+import { signIn as signInAction } from '../../../auth';
+
+async function signIn(provider: AuthProvider, formData: FormData, callbackUrl?: string) {
+ try {
+ return await signInAction(provider.id, {
+ ...(formData && { email: formData.get('email'), password: formData.get('password') }),
+ redirectTo: callbackUrl ?? '/',
+ });
+ } catch (error) {
+ // The desired flow for successful sign in in all cases
+ // and unsuccessful sign in for OAuth providers will cause a `redirect`,
+ // and `redirect` is a throwing function, so we need to re-throw
+ // to allow the redirect to happen
+ // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642
+ // Detect a `NEXT_REDIRECT` error and re-throw it
+ if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
+ // For the nodemailer provider, we want to return a success message
+ // instead of redirecting to a `verify-request` page
+ if (provider.id === 'nodemailer' && (error as any).digest?.includes('verify-request')) {
+ return {
+ success: 'Check your email for a verification link.',
+ };
+ }
+ throw error;
+ }
+ // Handle Auth.js errors
+ if (error instanceof AuthError) {
+ return {
+ error:
+ error.type === 'CredentialsSignin'
+ ? 'Invalid credentials.'
+ : 'An error with Auth.js occurred.',
+ type: error.type,
+ };
+ }
+ // An error boundary must exist to handle unknown errors
+ return {
+ error: 'Something went wrong.',
+ type: 'UnknownError',
+ };
+ }
+}
+
+export default signIn;
diff --git a/examples/core/auth-nextjs-email/src/app/auth/signin/page.tsx b/examples/core/auth-nextjs-email/src/app/auth/signin/page.tsx
new file mode 100644
index 00000000000..e1838f9807e
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/app/auth/signin/page.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import { SignInPage } from '@toolpad/core/SignInPage';
+import { providerMap } from '../../../auth';
+import signIn from './actions';
+
+export default function SignIn() {
+ return (
+
+ ;
+
+ );
+}
diff --git a/examples/core/auth-nextjs-email/src/app/layout.tsx b/examples/core/auth-nextjs-email/src/app/layout.tsx
new file mode 100644
index 00000000000..cfc6d97e016
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/app/layout.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react';
+import { NextAppProvider } from '@toolpad/core/nextjs';
+import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import type { Navigation } from '@toolpad/core';
+import { SessionProvider, signIn, signOut } from 'next-auth/react';
+import { auth } from '../auth';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ segment: '',
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core Next.js App',
+};
+
+const AUTHENTICATION = {
+ signIn,
+ signOut,
+};
+
+export default async function RootLayout(props: { children: React.ReactNode }) {
+ const session = await auth();
+
+ return (
+
+
+
+
+
+ {props.children}
+
+
+
+
+
+ );
+}
diff --git a/examples/core-auth-nextjs/src/app/(dashboard)/layout.tsx b/examples/core/auth-nextjs-email/src/app/public/layout.tsx
similarity index 100%
rename from examples/core-auth-nextjs/src/app/(dashboard)/layout.tsx
rename to examples/core/auth-nextjs-email/src/app/public/layout.tsx
diff --git a/examples/core-auth-nextjs/src/app/public/page.tsx b/examples/core/auth-nextjs-email/src/app/public/page.tsx
similarity index 100%
rename from examples/core-auth-nextjs/src/app/public/page.tsx
rename to examples/core/auth-nextjs-email/src/app/public/page.tsx
diff --git a/examples/core/auth-nextjs-email/src/auth.ts b/examples/core/auth-nextjs-email/src/auth.ts
new file mode 100644
index 00000000000..af08addd63f
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/auth.ts
@@ -0,0 +1,66 @@
+import NextAuth from 'next-auth';
+import GitHub from 'next-auth/providers/github';
+
+import Nodemailer from 'next-auth/providers/nodemailer';
+import { PrismaAdapter } from '@auth/prisma-adapter';
+import type { Provider } from 'next-auth/providers';
+import { prisma } from './prisma';
+
+const providers: Provider[] = [
+ GitHub({
+ clientId: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
+ }),
+ Nodemailer({
+ server: {
+ host: process.env.EMAIL_SERVER_HOST,
+ port: process.env.EMAIL_SERVER_PORT,
+ auth: {
+ user: process.env.EMAIL_SERVER_USER,
+ pass: process.env.EMAIL_SERVER_PASSWORD,
+ },
+ secure: true,
+ },
+ from: process.env.EMAIL_FROM,
+ }),
+];
+
+export const providerMap = providers.map((provider) => {
+ if (typeof provider === 'function') {
+ const providerData = provider();
+ return {
+ id: providerData.id,
+ name: providerData.name,
+ };
+ }
+ return { id: provider.id, name: provider.name };
+});
+
+if (!process.env.GITHUB_CLIENT_ID) {
+ console.warn('Missing environment variable "GITHUB_CLIENT_ID"');
+}
+if (!process.env.GITHUB_CLIENT_SECRET) {
+ console.warn('Missing environment variable "GITHUB_CLIENT_ID"');
+}
+
+export const { handlers, auth, signIn, signOut } = NextAuth({
+ providers,
+ adapter: PrismaAdapter(prisma),
+ session: { strategy: 'jwt' },
+ secret: process.env.AUTH_SECRET,
+ pages: {
+ signIn: '/auth/signin',
+ },
+ callbacks: {
+ authorized({ auth: session, request: { nextUrl } }) {
+ const isLoggedIn = !!session?.user;
+ const isPublicPage = nextUrl.pathname.startsWith('/public');
+
+ if (isPublicPage || isLoggedIn) {
+ return true;
+ }
+
+ return false; // Redirect unauthenticated users to login page
+ },
+ },
+});
diff --git a/examples/core/auth-nextjs-email/src/prisma.ts b/examples/core/auth-nextjs-email/src/prisma.ts
new file mode 100644
index 00000000000..ee2f97d763c
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/prisma.ts
@@ -0,0 +1,9 @@
+import { PrismaClient } from '@prisma/client';
+
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
+
+export const prisma = globalForPrisma.prisma || new PrismaClient();
+
+if (process.env.NODE_ENV !== 'production') {
+ globalForPrisma.prisma = prisma;
+}
diff --git a/examples/core/auth-nextjs-email/src/prisma/migrations/20240913094851_init/migration.sql b/examples/core/auth-nextjs-email/src/prisma/migrations/20240913094851_init/migration.sql
new file mode 100644
index 00000000000..9a11d832759
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/prisma/migrations/20240913094851_init/migration.sql
@@ -0,0 +1,61 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "id" TEXT NOT NULL,
+ "name" TEXT,
+ "email" TEXT NOT NULL,
+ "emailVerified" TIMESTAMP(3),
+ "image" TEXT,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Account" (
+ "userId" TEXT NOT NULL,
+ "type" TEXT NOT NULL,
+ "provider" TEXT NOT NULL,
+ "providerAccountId" TEXT NOT NULL,
+ "refresh_token" TEXT,
+ "access_token" TEXT,
+ "expires_at" INTEGER,
+ "token_type" TEXT,
+ "scope" TEXT,
+ "id_token" TEXT,
+ "session_state" TEXT,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "Account_pkey" PRIMARY KEY ("provider","providerAccountId")
+);
+
+-- CreateTable
+CREATE TABLE "Session" (
+ "sessionToken" TEXT NOT NULL,
+ "userId" TEXT NOT NULL,
+ "expires" TIMESTAMP(3) NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL
+);
+
+-- CreateTable
+CREATE TABLE "VerificationToken" (
+ "identifier" TEXT NOT NULL,
+ "token" TEXT NOT NULL,
+ "expires" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "VerificationToken_pkey" PRIMARY KEY ("identifier","token")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
+
+-- AddForeignKey
+ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/examples/core/auth-nextjs-email/src/prisma/migrations/migration_lock.toml b/examples/core/auth-nextjs-email/src/prisma/migrations/migration_lock.toml
new file mode 100644
index 00000000000..fbffa92c2bb
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file
diff --git a/examples/core/auth-nextjs-email/src/prisma/schema.prisma b/examples/core/auth-nextjs-email/src/prisma/schema.prisma
new file mode 100644
index 00000000000..9b35bb87717
--- /dev/null
+++ b/examples/core/auth-nextjs-email/src/prisma/schema.prisma
@@ -0,0 +1,60 @@
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+model User {
+ id String @id @default(cuid())
+ name String?
+ email String @unique
+ emailVerified DateTime?
+ image String?
+ accounts Account[]
+ sessions Session[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+model Account {
+ userId String
+ type String
+ provider String
+ providerAccountId String
+ refresh_token String?
+ access_token String?
+ expires_at Int?
+ token_type String?
+ scope String?
+ id_token String?
+ session_state String?
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@id([provider, providerAccountId])
+}
+
+model Session {
+ sessionToken String @unique
+ userId String
+ expires DateTime
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+model VerificationToken {
+ identifier String
+ token String
+ expires DateTime
+
+ @@id([identifier, token])
+}
\ No newline at end of file
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/tsconfig.json b/examples/core/auth-nextjs-email/tsconfig.json
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/tsconfig.json
rename to examples/core/auth-nextjs-email/tsconfig.json
diff --git a/examples/core-auth-nextjs-pages/.eslintrc.json b/examples/core/auth-nextjs-pages-nextauth-4/.eslintrc.json
similarity index 100%
rename from examples/core-auth-nextjs-pages/.eslintrc.json
rename to examples/core/auth-nextjs-pages-nextauth-4/.eslintrc.json
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/README.md b/examples/core/auth-nextjs-pages-nextauth-4/README.md
similarity index 79%
rename from examples/core-auth-nextjs-pages-nextauth-4/README.md
rename to examples/core/auth-nextjs-pages-nextauth-4/README.md
index d33f0dc405c..04efdfad123 100644
--- a/examples/core-auth-nextjs-pages-nextauth-4/README.md
+++ b/examples/core/auth-nextjs-pages-nextauth-4/README.md
@@ -18,6 +18,18 @@ bun dev
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example auth-nextjs-pages-nextauth-4
+# or
+pnpm dlx create-toolpad-app@latest --example auth-nextjs-pages-nextauth-4
+```
+
+and follow the instructions in the terminal.
+
## Learn More
To learn more about Next.js, take a look at the following resources:
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/next-env.d.ts b/examples/core/auth-nextjs-pages-nextauth-4/next-env.d.ts
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/next-env.d.ts
rename to examples/core/auth-nextjs-pages-nextauth-4/next-env.d.ts
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/next.config.mjs b/examples/core/auth-nextjs-pages-nextauth-4/next.config.mjs
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/next.config.mjs
rename to examples/core/auth-nextjs-pages-nextauth-4/next.config.mjs
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/package.json b/examples/core/auth-nextjs-pages-nextauth-4/package.json
similarity index 67%
rename from examples/core-auth-nextjs-pages-nextauth-4/package.json
rename to examples/core/auth-nextjs-pages-nextauth-4/package.json
index 90d2b5ffad2..8e941327d4a 100644
--- a/examples/core-auth-nextjs-pages-nextauth-4/package.json
+++ b/examples/core/auth-nextjs-pages-nextauth-4/package.json
@@ -14,17 +14,17 @@
"@mui/material": "^6",
"@mui/material-nextjs": "^6",
"@toolpad/core": "latest",
- "next": "^14",
- "next-auth": "^4.24.7",
- "react": "^18",
- "react-dom": "^18"
+ "next": "^15",
+ "next-auth": "^4.24.10",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
},
"devDependencies": {
"typescript": "^5",
- "@types/node": "^20.16.11",
- "@types/react": "^18",
- "@types/react-dom": "^18",
+ "@types/node": "^20.17.12",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
"eslint": "^8",
- "eslint-config-next": "^14"
+ "eslint-config-next": "^15"
}
}
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/_app.tsx b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/_app.tsx
similarity index 91%
rename from examples/core-auth-nextjs-pages-nextauth-4/src/pages/_app.tsx
rename to examples/core/auth-nextjs-pages-nextauth-4/src/pages/_app.tsx
index b8e40df7033..6795db3e230 100644
--- a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/_app.tsx
+++ b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/_app.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { AppProvider } from '@toolpad/core/nextjs';
+import { NextAppProvider } from '@toolpad/core/nextjs';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import Head from 'next/head';
import { useRouter } from 'next/router';
@@ -13,7 +13,7 @@ import { SessionProvider, signIn, signOut, useSession } from 'next-auth/react';
import LinearProgress from '@mui/material/LinearProgress';
export type NextPageWithLayout = NextPage
& {
- getLayout?: (page: React.ReactElement) => React.ReactNode;
+ getLayout?: (page: React.ReactElement) => React.ReactNode;
requireAuth?: boolean;
};
@@ -46,7 +46,7 @@ const AUTHENTICATION = {
signOut,
};
-function getDefaultLayout(page: React.ReactElement) {
+function getDefaultLayout(page: React.ReactElement) {
return {page} ;
}
@@ -72,14 +72,14 @@ function AppLayout({ children }: { children: React.ReactNode }) {
-
{children}
-
+
);
}
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/_document.tsx b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/_document.tsx
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/src/pages/_document.tsx
rename to examples/core/auth-nextjs-pages-nextauth-4/src/pages/_document.tsx
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/api/auth/[...nextauth].ts b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/api/auth/[...nextauth].ts
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/src/pages/api/auth/[...nextauth].ts
rename to examples/core/auth-nextjs-pages-nextauth-4/src/pages/api/auth/[...nextauth].ts
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/auth/signin.tsx b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/auth/signin.tsx
similarity index 96%
rename from examples/core-auth-nextjs-pages-nextauth-4/src/pages/auth/signin.tsx
rename to examples/core/auth-nextjs-pages-nextauth-4/src/pages/auth/signin.tsx
index 342a860c691..be1d6b32d5a 100644
--- a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/auth/signin.tsx
+++ b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/auth/signin.tsx
@@ -8,7 +8,11 @@ import { useRouter } from 'next/router';
import { authOptions } from '../api/auth/[...nextauth]';
function ForgotPasswordLink() {
- return Forgot password?;
+ return (
+
+ Forgot password?
+
+ );
}
function SignUpLink() {
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/index.tsx b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/index.tsx
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/src/pages/index.tsx
rename to examples/core/auth-nextjs-pages-nextauth-4/src/pages/index.tsx
diff --git a/examples/core-auth-nextjs-pages-nextauth-4/src/pages/orders/index.tsx b/examples/core/auth-nextjs-pages-nextauth-4/src/pages/orders/index.tsx
similarity index 100%
rename from examples/core-auth-nextjs-pages-nextauth-4/src/pages/orders/index.tsx
rename to examples/core/auth-nextjs-pages-nextauth-4/src/pages/orders/index.tsx
diff --git a/examples/core-auth-nextjs-pages/tsconfig.json b/examples/core/auth-nextjs-pages-nextauth-4/tsconfig.json
similarity index 100%
rename from examples/core-auth-nextjs-pages/tsconfig.json
rename to examples/core/auth-nextjs-pages-nextauth-4/tsconfig.json
diff --git a/examples/core-auth-nextjs/.eslintrc.json b/examples/core/auth-nextjs-pages/.eslintrc.json
similarity index 100%
rename from examples/core-auth-nextjs/.eslintrc.json
rename to examples/core/auth-nextjs-pages/.eslintrc.json
diff --git a/examples/core-auth-nextjs-pages/README.md b/examples/core/auth-nextjs-pages/README.md
similarity index 86%
rename from examples/core-auth-nextjs-pages/README.md
rename to examples/core/auth-nextjs-pages/README.md
index e92d004f1c3..65b93a7ad4f 100644
--- a/examples/core-auth-nextjs-pages/README.md
+++ b/examples/core/auth-nextjs-pages/README.md
@@ -40,6 +40,18 @@ bun dev
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example auth-nextjs-pages
+# or
+pnpm dlx create-toolpad-app@latest --example auth-nextjs-pages
+```
+
+and follow the instructions in the terminal.
+
## Learn More
To learn more about Next.js, take a look at the following resources:
diff --git a/examples/core-auth-nextjs-pages/next-env.d.ts b/examples/core/auth-nextjs-pages/next-env.d.ts
similarity index 100%
rename from examples/core-auth-nextjs-pages/next-env.d.ts
rename to examples/core/auth-nextjs-pages/next-env.d.ts
diff --git a/examples/core-auth-nextjs-pages/next.config.mjs b/examples/core/auth-nextjs-pages/next.config.mjs
similarity index 76%
rename from examples/core-auth-nextjs-pages/next.config.mjs
rename to examples/core/auth-nextjs-pages/next.config.mjs
index d5456a15d4a..53c01cd0d0c 100644
--- a/examples/core-auth-nextjs-pages/next.config.mjs
+++ b/examples/core/auth-nextjs-pages/next.config.mjs
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
+ transpilePackages: ['next-auth'],
};
export default nextConfig;
diff --git a/examples/core-auth-nextjs-pages/package.json b/examples/core/auth-nextjs-pages/package.json
similarity index 66%
rename from examples/core-auth-nextjs-pages/package.json
rename to examples/core/auth-nextjs-pages/package.json
index 95ae37e6270..e6e77bf39b9 100644
--- a/examples/core-auth-nextjs-pages/package.json
+++ b/examples/core/auth-nextjs-pages/package.json
@@ -14,17 +14,17 @@
"@mui/material": "^6",
"@mui/material-nextjs": "^6",
"@toolpad/core": "latest",
- "next": "^14",
- "next-auth": "5.0.0-beta.20",
- "react": "^18",
- "react-dom": "^18"
+ "next": "^15",
+ "next-auth": "5.0.0-beta.25",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
},
"devDependencies": {
"typescript": "^5",
- "@types/node": "^20.16.11",
- "@types/react": "^18",
- "@types/react-dom": "^18",
+ "@types/node": "^20.17.12",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
"eslint": "^8",
- "eslint-config-next": "^14"
+ "eslint-config-next": "^15"
}
}
diff --git a/examples/core-auth-nextjs/src/app/api/auth/[...nextauth]/route.ts b/examples/core/auth-nextjs-pages/src/app/api/auth/[...nextauth]/route.ts
similarity index 100%
rename from examples/core-auth-nextjs/src/app/api/auth/[...nextauth]/route.ts
rename to examples/core/auth-nextjs-pages/src/app/api/auth/[...nextauth]/route.ts
diff --git a/examples/core-auth-nextjs-pages/src/auth.ts b/examples/core/auth-nextjs-pages/src/auth.ts
similarity index 79%
rename from examples/core-auth-nextjs-pages/src/auth.ts
rename to examples/core/auth-nextjs-pages/src/auth.ts
index a377904d3e0..5d3c3452b05 100644
--- a/examples/core-auth-nextjs-pages/src/auth.ts
+++ b/examples/core/auth-nextjs-pages/src/auth.ts
@@ -26,23 +26,11 @@ const providers: Provider[] = [
}),
];
-const missingVars: string[] = [];
-
if (!process.env.GITHUB_CLIENT_ID) {
- missingVars.push('GITHUB_CLIENT_ID');
+ console.warn('Missing environment variable "GITHUB_CLIENT_ID"');
}
if (!process.env.GITHUB_CLIENT_SECRET) {
- missingVars.push('GITHUB_CLIENT_SECRET');
-}
-
-if (missingVars.length > 0) {
- const message = `Authentication is configured but the following environment variables are missing: ${missingVars.join(', ')}`;
-
- if (process.env.NODE_ENV === 'production') {
- throw new Error(message);
- } else {
- console.warn(message);
- }
+ console.warn('Missing environment variable "GITHUB_CLIENT_SECRET"');
}
export const providerMap = providers.map((provider) => {
diff --git a/examples/core-auth-nextjs-pages/src/middleware.ts b/examples/core/auth-nextjs-pages/src/middleware.ts
similarity index 100%
rename from examples/core-auth-nextjs-pages/src/middleware.ts
rename to examples/core/auth-nextjs-pages/src/middleware.ts
diff --git a/examples/core-auth-nextjs-pages/src/pages/_app.tsx b/examples/core/auth-nextjs-pages/src/pages/_app.tsx
similarity index 91%
rename from examples/core-auth-nextjs-pages/src/pages/_app.tsx
rename to examples/core/auth-nextjs-pages/src/pages/_app.tsx
index e2642586973..56b7a8639a7 100644
--- a/examples/core-auth-nextjs-pages/src/pages/_app.tsx
+++ b/examples/core/auth-nextjs-pages/src/pages/_app.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { AppProvider } from '@toolpad/core/nextjs';
+import { NextAppProvider } from '@toolpad/core/nextjs';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { PageContainer } from '@toolpad/core/PageContainer';
import Head from 'next/head';
@@ -13,7 +13,7 @@ import { SessionProvider, signIn, signOut, useSession } from 'next-auth/react';
import LinearProgress from '@mui/material/LinearProgress';
export type NextPageWithLayout = NextPage
& {
- getLayout?: (page: React.ReactElement) => React.ReactNode;
+ getLayout?: (page: React.ReactElement) => React.ReactNode;
requireAuth?: boolean;
};
@@ -46,7 +46,7 @@ const AUTHENTICATION = {
signOut,
};
-function getDefaultLayout(page: React.ReactElement) {
+function getDefaultLayout(page: React.ReactElement) {
return (
{page}
@@ -71,14 +71,14 @@ function AppLayout({ children }: { children: React.ReactNode }) {
-
{children}
-
+
);
}
diff --git a/examples/core-auth-nextjs-pages/src/pages/_document.tsx b/examples/core/auth-nextjs-pages/src/pages/_document.tsx
similarity index 100%
rename from examples/core-auth-nextjs-pages/src/pages/_document.tsx
rename to examples/core/auth-nextjs-pages/src/pages/_document.tsx
diff --git a/examples/core-auth-nextjs-pages/src/pages/auth/signin.tsx b/examples/core/auth-nextjs-pages/src/pages/auth/signin.tsx
similarity index 100%
rename from examples/core-auth-nextjs-pages/src/pages/auth/signin.tsx
rename to examples/core/auth-nextjs-pages/src/pages/auth/signin.tsx
diff --git a/examples/core-auth-nextjs-pages/src/pages/index.tsx b/examples/core/auth-nextjs-pages/src/pages/index.tsx
similarity index 100%
rename from examples/core-auth-nextjs-pages/src/pages/index.tsx
rename to examples/core/auth-nextjs-pages/src/pages/index.tsx
diff --git a/examples/core-auth-nextjs-pages/src/pages/orders/index.tsx b/examples/core/auth-nextjs-pages/src/pages/orders/index.tsx
similarity index 100%
rename from examples/core-auth-nextjs-pages/src/pages/orders/index.tsx
rename to examples/core/auth-nextjs-pages/src/pages/orders/index.tsx
diff --git a/examples/core-auth-nextjs/tsconfig.json b/examples/core/auth-nextjs-pages/tsconfig.json
similarity index 100%
rename from examples/core-auth-nextjs/tsconfig.json
rename to examples/core/auth-nextjs-pages/tsconfig.json
diff --git a/examples/core-tutorial/.eslintrc.json b/examples/core/auth-nextjs-passkey/.eslintrc.json
similarity index 100%
rename from examples/core-tutorial/.eslintrc.json
rename to examples/core/auth-nextjs-passkey/.eslintrc.json
diff --git a/examples/core/auth-nextjs-passkey/.gitignore b/examples/core/auth-nextjs-passkey/.gitignore
new file mode 100644
index 00000000000..68c5d18f00d
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/examples/core/auth-nextjs-passkey/README.md b/examples/core/auth-nextjs-passkey/README.md
new file mode 100644
index 00000000000..65a8272086c
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/README.md
@@ -0,0 +1,62 @@
+# Toolpad Core - Next.js App Router with Passkey
+
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+1. You need to have a Postgres database running. You can use the following docker command to start a Postgres database:
+
+```bash
+docker run --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres
+```
+
+2. For the database created above, the connection string is `postgresql://postgres:postgres@localhost:5432/postgres`.
+
+3. Update the `DATABASE_URL` environment variable in the `.env` file with the connection string for the database you created above.
+
+4. Then, generate the Prisma Client:
+
+```bash
+npx prisma migrate dev --schema=./src/prisma/schema.prisma
+```
+
+5. Finally, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+6. Open [http://localhost:3000](http://localhost:3000) with your browser to see the app running.
+
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example auth-nextjs-passkey
+# or
+pnpm dlx create-toolpad-app@latest --example auth-nextjs-passkey
+```
+
+and follow the instructions in the terminal.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/examples/core/auth-nextjs-passkey/next-env.d.ts b/examples/core/auth-nextjs-passkey/next-env.d.ts
new file mode 100644
index 00000000000..4f11a03dc6c
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/examples/core/auth-nextjs-passkey/next.config.mjs b/examples/core/auth-nextjs-passkey/next.config.mjs
new file mode 100644
index 00000000000..4678774e6d6
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/examples/core/auth-nextjs-passkey/package.json b/examples/core/auth-nextjs-passkey/package.json
new file mode 100644
index 00000000000..8c708789826
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "core-auth-nextjs-passkey",
+ "version": "0.1.0",
+ "scripts": {
+ "dev": "next dev",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@auth/prisma-adapter": "^2",
+ "@emotion/react": "^11",
+ "@emotion/styled": "^11",
+ "@mui/icons-material": "^6",
+ "@mui/material": "^6",
+ "@mui/material-nextjs": "^6",
+ "@prisma/client": "^5",
+ "@simplewebauthn/browser": "^9",
+ "@simplewebauthn/server": "^9",
+ "@toolpad/core": "latest",
+ "next": "^15",
+ "next-auth": "5.0.0-beta.25",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22",
+ "@types/react": "^19.0.0",
+ "prisma": "^5",
+ "@types/react-dom": "^19.0.0",
+ "eslint-config-next": "^15"
+ }
+}
diff --git a/examples/core/auth-nextjs-passkey/src/app/(dashboard)/layout.tsx b/examples/core/auth-nextjs-passkey/src/app/(dashboard)/layout.tsx
new file mode 100644
index 00000000000..5dee163b753
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/(dashboard)/layout.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default async function DashboardPagesLayout(props: { children: React.ReactNode }) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/examples/core/auth-nextjs-passkey/src/app/(dashboard)/orders/page.tsx b/examples/core/auth-nextjs-passkey/src/app/(dashboard)/orders/page.tsx
new file mode 100644
index 00000000000..1f55fb65641
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/(dashboard)/orders/page.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+import Box from '@mui/material/Box';
+import { redirect } from 'next/navigation';
+import { headers } from 'next/headers';
+import { auth } from '../../../auth';
+
+export default async function OrdersPage() {
+ const session = await auth();
+ const currentUrl =
+ (await headers()).get('referer') || process.env.AUTH_URL || 'http://localhost:3000';
+
+ if (!session) {
+ // Get the current URL to redirect to signIn with `callbackUrl`
+ const redirectUrl = new URL('/auth/signin', currentUrl);
+ redirectUrl.searchParams.set('callbackUrl', currentUrl);
+
+ redirect(redirectUrl.toString());
+ }
+ return (
+
+ Welcome to the Toolpad orders!
+
+ );
+}
diff --git a/examples/core/auth-nextjs-passkey/src/app/(dashboard)/page.tsx b/examples/core/auth-nextjs-passkey/src/app/(dashboard)/page.tsx
new file mode 100644
index 00000000000..a1de3bab206
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/(dashboard)/page.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+import Box from '@mui/material/Box';
+import { redirect } from 'next/navigation';
+import { headers } from 'next/headers';
+import { auth } from '../../auth';
+
+export default async function HomePage() {
+ const session = await auth();
+ const currentUrl =
+ (await headers()).get('referer') || process.env.AUTH_URL || 'http://localhost:3000';
+
+ if (!session) {
+ // Get the current URL to redirect to signIn with `callbackUrl`
+ const redirectUrl = new URL('/auth/signin', currentUrl);
+ redirectUrl.searchParams.set('callbackUrl', currentUrl);
+
+ redirect(redirectUrl.toString());
+ }
+ return (
+
+ Welcome to Toolpad, {session?.user?.email || 'User'}!
+
+ );
+}
diff --git a/examples/core/auth-nextjs-passkey/src/app/api/auth/[...nextauth]/route.ts b/examples/core/auth-nextjs-passkey/src/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 00000000000..ca225652075
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,3 @@
+import { handlers } from '../../../../auth';
+
+export const { GET, POST } = handlers;
diff --git a/examples/core/auth-nextjs-passkey/src/app/auth/signin/actions.ts b/examples/core/auth-nextjs-passkey/src/app/auth/signin/actions.ts
new file mode 100644
index 00000000000..947c8c04907
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/auth/signin/actions.ts
@@ -0,0 +1,41 @@
+'use server';
+import { AuthError } from 'next-auth';
+import type { AuthProvider } from '@toolpad/core';
+
+import { signIn as signInAction } from '../../../auth';
+
+async function signIn(provider: AuthProvider, formData: FormData, callbackUrl?: string) {
+ try {
+ return await signInAction(provider.id, {
+ ...(formData && { email: formData.get('email'), password: formData.get('password') }),
+ redirectTo: callbackUrl ?? '/',
+ });
+ } catch (error) {
+ // The desired flow for successful sign in in all cases
+ // and unsuccessful sign in for OAuth providers will cause a `redirect`,
+ // and `redirect` is a throwing function, so we need to re-throw
+ // to allow the redirect to happen
+ // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642
+ // Detect a `NEXT_REDIRECT` error and re-throw it
+ if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
+ throw error;
+ }
+ // Handle Auth.js errors
+ if (error instanceof AuthError) {
+ return {
+ error:
+ error.type === 'CredentialsSignin'
+ ? 'Invalid credentials.'
+ : 'An error with Auth.js occurred.',
+ type: error.type,
+ };
+ }
+ // An error boundary must exist to handle unknown errors
+ return {
+ error: 'Something went wrong.',
+ type: 'UnknownError',
+ };
+ }
+}
+
+export default signIn;
diff --git a/examples/core/auth-nextjs-passkey/src/app/auth/signin/page.tsx b/examples/core/auth-nextjs-passkey/src/app/auth/signin/page.tsx
new file mode 100644
index 00000000000..6ae609504a5
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/auth/signin/page.tsx
@@ -0,0 +1,31 @@
+'use client';
+import * as React from 'react';
+import type { AuthProvider } from '@toolpad/core';
+import { SignInPage } from '@toolpad/core/SignInPage';
+import { signIn as webauthnSignIn } from 'next-auth/webauthn';
+import { providerMap } from '../../../auth';
+import serverSignIn from './actions';
+
+// Create a wrapper function for signIn
+const signIn = async (provider: AuthProvider, formData: FormData, callbackUrl?: string) => {
+ if (provider.id === 'passkey') {
+ try {
+ return await webauthnSignIn('passkey', {
+ email: formData.get('email'),
+ callbackUrl: callbackUrl || '/',
+ });
+ } catch (error) {
+ console.error(error);
+ return {
+ error: (error as Error)?.message || 'Something went wrong',
+ type: 'WebAuthnError',
+ };
+ }
+ }
+ // Use regular signIn for other providers
+ return serverSignIn(provider, formData, callbackUrl);
+};
+
+export default function SignIn() {
+ return ;
+}
diff --git a/examples/core/auth-nextjs-passkey/src/app/layout.tsx b/examples/core/auth-nextjs-passkey/src/app/layout.tsx
new file mode 100644
index 00000000000..b91589627b4
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/layout.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import { NextAppProvider } from '@toolpad/core/nextjs';
+import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import type { Navigation } from '@toolpad/core';
+import { SessionProvider, signIn, signOut } from 'next-auth/react';
+import { auth } from '../auth';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+const AUTHENTICATION = {
+ signIn,
+ signOut,
+};
+
+export default async function RootLayout(props: { children: React.ReactNode }) {
+ const session = await auth();
+
+ return (
+
+
+
+
+
+ {props.children}
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-passkey/src/app/public/layout.tsx b/examples/core/auth-nextjs-passkey/src/app/public/layout.tsx
new file mode 100644
index 00000000000..5dee163b753
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/public/layout.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default async function DashboardPagesLayout(props: { children: React.ReactNode }) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/examples/core/auth-nextjs-passkey/src/app/public/page.tsx b/examples/core/auth-nextjs-passkey/src/app/public/page.tsx
new file mode 100644
index 00000000000..19805793014
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/app/public/page.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default async function HomePage() {
+ return (
+
+
+ Public page
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-passkey/src/auth.ts b/examples/core/auth-nextjs-passkey/src/auth.ts
new file mode 100644
index 00000000000..6444dc86c13
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/auth.ts
@@ -0,0 +1,41 @@
+import NextAuth from 'next-auth';
+import Passkey from 'next-auth/providers/passkey';
+import { PrismaAdapter } from '@auth/prisma-adapter';
+import { PrismaClient } from '@prisma/client';
+import type { Provider } from 'next-auth/providers';
+
+const prisma = new PrismaClient();
+
+const providers: Provider[] = [Passkey];
+
+export const providerMap = providers.map((provider) => {
+ if (typeof provider === 'function') {
+ const providerData = provider();
+ return { id: providerData.id, name: providerData.name };
+ }
+ return { id: provider.id, name: provider.name };
+});
+
+export const { handlers, auth, signIn, signOut } = NextAuth({
+ providers,
+ adapter: PrismaAdapter(prisma),
+ experimental: {
+ enableWebAuthn: true,
+ },
+ secret: process.env.AUTH_SECRET,
+ pages: {
+ signIn: '/auth/signin',
+ },
+ callbacks: {
+ authorized({ auth: session, request: { nextUrl } }) {
+ const isLoggedIn = !!session?.user;
+ const isPublicPage = nextUrl.pathname.startsWith('/public');
+
+ if (isPublicPage || isLoggedIn) {
+ return true;
+ }
+
+ return false; // Redirect unauthenticated users to login page
+ },
+ },
+});
diff --git a/examples/core/auth-nextjs-passkey/src/prisma.ts b/examples/core/auth-nextjs-passkey/src/prisma.ts
new file mode 100644
index 00000000000..ee2f97d763c
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/prisma.ts
@@ -0,0 +1,9 @@
+import { PrismaClient } from '@prisma/client';
+
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
+
+export const prisma = globalForPrisma.prisma || new PrismaClient();
+
+if (process.env.NODE_ENV !== 'production') {
+ globalForPrisma.prisma = prisma;
+}
diff --git a/examples/core/auth-nextjs-passkey/src/prisma/schema.prisma b/examples/core/auth-nextjs-passkey/src/prisma/schema.prisma
new file mode 100644
index 00000000000..558675b06c5
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/src/prisma/schema.prisma
@@ -0,0 +1,78 @@
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+// schema.prisma
+generator client {
+ provider = "prisma-client-js"
+
+}
+
+model User {
+ id String @id @default(cuid())
+ name String?
+ email String @unique
+ emailVerified DateTime?
+ image String?
+ accounts Account[]
+ sessions Session[]
+ Authenticator Authenticator[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+model Account {
+ userId String
+ type String
+ provider String
+ providerAccountId String
+ refresh_token String?
+ access_token String?
+ expires_at Int?
+ token_type String?
+ scope String?
+ id_token String?
+ session_state String?
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@id([provider, providerAccountId])
+}
+
+model Session {
+ sessionToken String @unique
+ userId String
+ expires DateTime
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+model VerificationToken {
+ identifier String
+ token String
+ expires DateTime
+
+ @@id([identifier, token])
+}
+
+model Authenticator {
+ credentialID String @unique
+ userId String
+ providerAccountId String
+ credentialPublicKey String
+ counter Int
+ credentialDeviceType String
+ credentialBackedUp Boolean
+ transports String?
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@id([userId, credentialID])
+}
\ No newline at end of file
diff --git a/examples/core/auth-nextjs-passkey/tsconfig.json b/examples/core/auth-nextjs-passkey/tsconfig.json
new file mode 100644
index 00000000000..bb5584ed1a4
--- /dev/null
+++ b/examples/core/auth-nextjs-passkey/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/examples/core/auth-nextjs-themed/.env b/examples/core/auth-nextjs-themed/.env
new file mode 100644
index 00000000000..856f2860e78
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/.env
@@ -0,0 +1,3 @@
+
+AUTH_SECRET="cNU2IkgN6v9dVQ1EqbCe4Npqh0IbM6VEsMBpg69wiyU=" # Added by `npx auth`. Read more: https://cli.authjs.dev
+AUTH_URL="https://r3l69w-3000.csb.app"
\ No newline at end of file
diff --git a/examples/core/auth-nextjs-themed/.gitattributes b/examples/core/auth-nextjs-themed/.gitattributes
new file mode 100644
index 00000000000..dfe0770424b
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/examples/core/auth-nextjs-themed/.gitignore b/examples/core/auth-nextjs-themed/.gitignore
new file mode 100644
index 00000000000..b38185899f2
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/.gitignore
@@ -0,0 +1,36 @@
+# Ignore files generated by the IDE
+.idea/
+
+# Ignore compiled binaries
+*.exe
+*.dll
+*.so
+*.next
+
+# Ignore build output directories
+/bin/
+/build/
+/dist/
+
+# Ignore package manager directories
+/node_modules/
+/vendor/
+
+# Ignore log files
+*.log
+
+# Ignore temporary files
+*.tmp
+*.swp
+*.bak
+
+# Ignore sensitive or personal files
+config.ini
+secrets.txt
+
+# Ignore operating system files
+.DS_Store
+Thumbs.db
+
+# Environment variables
+*.env*
\ No newline at end of file
diff --git a/examples/core/auth-nextjs-themed/README.md b/examples/core/auth-nextjs-themed/README.md
new file mode 100644
index 00000000000..9c9b9a314ee
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/README.md
@@ -0,0 +1,37 @@
+# Toolpad Core - Create Toolpad App
+
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-toolpad-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Setup
+
+Run `npx auth secret` to generate a secret and replace the value in the .env.local file with it.
+
+Add the CLIENT_ID and CLIENT_SECRET from your OAuth provider to the .env.local file.
+
+## Getting Started
+
+First, run the development server: `npm run dev`
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+## Run on CodeSandbox
+
+Run this example on CodeSandbox [here](https://codesandbox.io/p/sandbox/github/mui/toolpad/tree/master/examples/core/auth-nextjs-themed).
+
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example auth-nextjs-themed
+# or
+pnpm dlx create-toolpad-app@latest --example auth-nextjs-themed
+```
+
+and follow the instructions in the terminal.
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/examples/core/auth-nextjs-themed/app/(dashboard)/DashboardContent.tsx b/examples/core/auth-nextjs-themed/app/(dashboard)/DashboardContent.tsx
new file mode 100644
index 00000000000..cb951603a50
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/(dashboard)/DashboardContent.tsx
@@ -0,0 +1,107 @@
+'use client';
+import * as React from 'react';
+import { alpha } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+import Grid from '@mui/material/Grid2';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import StatCard, { StatCardProps } from '../components/StatCard';
+import HighlightedCard from '../components/HiglightedCard';
+import SessionsChart from '../components/SessionsChart';
+import PageViewsBarChart from '../components/PageViewsBarChart';
+import CustomTreeView from '../components/CustomTreeView';
+import ChartUserByCountry from '../components/ChartUserByCountry';
+
+const data: StatCardProps[] = [
+ {
+ title: 'Users',
+ value: '14k',
+ interval: 'Last 30 days',
+ trend: 'up',
+ data: [
+ 200, 24, 220, 260, 240, 380, 100, 240, 280, 240, 300, 340, 320, 360, 340, 380, 360, 400, 380,
+ 420, 400, 640, 340, 460, 440, 480, 460, 600, 880, 920,
+ ],
+ },
+ {
+ title: 'Conversions',
+ value: '325',
+ interval: 'Last 30 days',
+ trend: 'down',
+ data: [
+ 1640, 1250, 970, 1130, 1050, 900, 720, 1080, 900, 450, 920, 820, 840, 600, 820, 780, 800, 760,
+ 380, 740, 660, 620, 840, 500, 520, 480, 400, 360, 300, 220,
+ ],
+ },
+ {
+ title: 'Event count',
+ value: '200k',
+ interval: 'Last 30 days',
+ trend: 'neutral',
+ data: [
+ 500, 400, 510, 530, 520, 600, 530, 520, 510, 730, 520, 510, 530, 620, 510, 530, 520, 410, 530,
+ 520, 610, 530, 520, 610, 530, 420, 510, 430, 520, 510,
+ ],
+ },
+];
+
+export default function DashboardContent() {
+ return (
+
+ ({
+ flexGrow: 1,
+ backgroundColor: theme.vars
+ ? `rgba(${theme.vars.palette.background.defaultChannel} / 1)`
+ : alpha(theme.palette.background.default, 1),
+ overflow: 'auto',
+ })}
+ >
+
+
+ {/* cards */}
+
+ Overview
+
+ theme.spacing(2) }}>
+ {data.map((card, index) => (
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/(dashboard)/SidebarFooterAccount.tsx b/examples/core/auth-nextjs-themed/app/(dashboard)/SidebarFooterAccount.tsx
new file mode 100644
index 00000000000..4372318c3cb
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/(dashboard)/SidebarFooterAccount.tsx
@@ -0,0 +1,114 @@
+'use client';
+import * as React from 'react';
+import Stack from '@mui/material/Stack';
+import MenuList from '@mui/material/MenuList';
+import Button from '@mui/material/Button';
+import AddIcon from '@mui/icons-material/Add';
+import Divider from '@mui/material/Divider';
+import {
+ Account,
+ AccountPreview,
+ AccountPreviewProps,
+ AccountPopoverFooter,
+ SignOutButton,
+} from '@toolpad/core/Account';
+import { SidebarFooterProps } from '@toolpad/core/DashboardLayout';
+
+function AccountSidebarPreview(props: AccountPreviewProps & { mini: boolean }) {
+ const { handleClick, open, mini } = props;
+ return (
+
+
+
+
+ );
+}
+
+function SidebarFooterAccountPopover({ mini }: { mini: boolean }) {
+ return (
+
+ {mini ? : null}
+
+ }
+ disableElevation
+ >
+ Add account
+
+
+
+
+
+
+
+ );
+}
+
+const createPreviewComponent = (mini: boolean) => {
+ function PreviewComponent(props: AccountPreviewProps) {
+ return ;
+ }
+ return PreviewComponent;
+};
+
+const createPopoverComponent = (mini: boolean) => {
+ function PopoverComponent() {
+ return ;
+ }
+ return PopoverComponent;
+};
+
+export default function SidebarFooterAccount({ mini }: SidebarFooterProps) {
+ const PreviewComponent = React.useMemo(() => createPreviewComponent(mini), [mini]);
+ const PopoverComponent = React.useMemo(() => createPopoverComponent(mini), [mini]);
+ return (
+
+ `drop-shadow(0px 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.32)'})`,
+ mt: 1,
+ '&::before': {
+ content: '""',
+ display: 'block',
+ position: 'absolute',
+ bottom: 10,
+ left: 0,
+ width: 10,
+ height: 10,
+ bgcolor: 'background.paper',
+ transform: 'translate(-50%, -50%) rotate(45deg)',
+ zIndex: 0,
+ },
+ },
+ },
+ },
+ },
+ }}
+ />
+ );
+}
+
+export function ToolbarAccountOverride() {
+ return null;
+}
diff --git a/examples/core/auth-nextjs-themed/app/(dashboard)/layout.tsx b/examples/core/auth-nextjs-themed/app/(dashboard)/layout.tsx
new file mode 100644
index 00000000000..c12af668eb4
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/(dashboard)/layout.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import Copyright from '../components/Copyright';
+import SidebarFooterAccount, { ToolbarAccountOverride } from './SidebarFooterAccount';
+
+export default function Layout(props: { children: React.ReactNode }) {
+ return (
+
+
+ {props.children}
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/(dashboard)/orders/page.tsx b/examples/core/auth-nextjs-themed/app/(dashboard)/orders/page.tsx
new file mode 100644
index 00000000000..b0b9b0d84dd
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/(dashboard)/orders/page.tsx
@@ -0,0 +1,7 @@
+'use client';
+import * as React from 'react';
+import CustomDataGrid from '../../components/CustomDataGrid';
+
+export default function OrdersPage() {
+ return ;
+}
diff --git a/examples/core/auth-nextjs-themed/app/(dashboard)/page.tsx b/examples/core/auth-nextjs-themed/app/(dashboard)/page.tsx
new file mode 100644
index 00000000000..86ff536cf36
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/(dashboard)/page.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import DashboardContent from './DashboardContent';
+
+export default function Dashboard() {
+ return ;
+}
diff --git a/examples/core/auth-nextjs-themed/app/api/auth/[...nextauth]/route.ts b/examples/core/auth-nextjs-themed/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 00000000000..ca225652075
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,3 @@
+import { handlers } from '../../../../auth';
+
+export const { GET, POST } = handlers;
diff --git a/examples/core/auth-nextjs-themed/app/auth/signin/actions.ts b/examples/core/auth-nextjs-themed/app/auth/signin/actions.ts
new file mode 100644
index 00000000000..5565a91cfa9
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/auth/signin/actions.ts
@@ -0,0 +1,40 @@
+'use server';
+import { AuthError } from 'next-auth';
+import type { AuthProvider } from '@toolpad/core';
+import { signIn as signInAction } from '../../../auth';
+
+async function signIn(provider: AuthProvider, formData: FormData, callbackUrl?: string) {
+ try {
+ return await signInAction(provider.id, {
+ ...(formData && { email: formData.get('email'), password: formData.get('password') }),
+ redirectTo: callbackUrl ?? '/',
+ });
+ } catch (error) {
+ // The desired flow for successful sign in in all cases
+ // and unsuccessful sign in for OAuth providers will cause a `redirect`,
+ // and `redirect` is a throwing function, so we need to re-throw
+ // to allow the redirect to happen
+ // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642
+ // Detect a `NEXT_REDIRECT` error and re-throw it
+ if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
+ throw error;
+ }
+ // Handle Auth.js errors
+ if (error instanceof AuthError) {
+ return {
+ error:
+ error.type === 'CredentialsSignin'
+ ? 'Invalid credentials.'
+ : 'An error with Auth.js occurred.',
+ type: error.type,
+ };
+ }
+ // An error boundary must exist to handle unknown errors
+ return {
+ error: 'Something went wrong.',
+ type: 'UnknownError',
+ };
+ }
+}
+
+export default signIn;
diff --git a/examples/core/auth-nextjs-themed/app/auth/signin/page.tsx b/examples/core/auth-nextjs-themed/app/auth/signin/page.tsx
new file mode 100644
index 00000000000..64936cdf273
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/auth/signin/page.tsx
@@ -0,0 +1,48 @@
+'use client';
+import * as React from 'react';
+import Link from '@mui/material/Link';
+import Alert from '@mui/material/Alert';
+import { SignInPage } from '@toolpad/core/SignInPage';
+import { providerMap } from '../../../auth';
+import signIn from './actions';
+
+function ForgotPasswordLink() {
+ return (
+
+
+ Forgot password?
+
+
+ );
+}
+
+function SignUpLink() {
+ return (
+
+ Don't have an account? Sign up
+
+ );
+}
+
+function DemoInfo() {
+ return (
+
+ You can use toolpad-demo@mui.com with the password @demo1 to
+ test
+
+ );
+}
+
+export default function SignIn() {
+ return (
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/ChartUserByCountry.tsx b/examples/core/auth-nextjs-themed/app/components/ChartUserByCountry.tsx
new file mode 100644
index 00000000000..d313d944d09
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/ChartUserByCountry.tsx
@@ -0,0 +1,185 @@
+'use client';
+import * as React from 'react';
+import { PieChart } from '@mui/x-charts/PieChart';
+import { useDrawingArea } from '@mui/x-charts/hooks';
+import { styled } from '@mui/material/styles';
+import Typography from '@mui/material/Typography';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress';
+
+import { UsaFlag, BrazilFlag, GlobeFlag } from '../mocks/CustomIcons';
+
+const data = [
+ { label: 'USA', value: 35000 },
+ { label: 'Brazil', value: 10000 },
+ { label: 'Other', value: 5000 },
+];
+
+const countries = [
+ {
+ name: 'USA',
+ value: 70,
+ flag: ,
+ color: 'hsl(220, 25%, 45%)',
+ },
+ {
+ name: 'Brazil',
+ value: 20,
+ flag: ,
+ color: 'hsl(220, 25%, 30%)',
+ },
+ {
+ name: 'Other',
+ value: 10,
+ flag: ,
+ color: 'hsl(220, 25%, 20%)',
+ },
+];
+
+interface StyledTextProps {
+ variant: 'primary' | 'secondary';
+}
+
+const StyledText = styled('text', {
+ shouldForwardProp: (prop) => prop !== 'variant',
+})(({ theme }) => ({
+ textAnchor: 'middle',
+ dominantBaseline: 'central',
+ fill: (theme.vars || theme).palette.text.secondary,
+ variants: [
+ {
+ props: {
+ variant: 'primary',
+ },
+ style: {
+ fontSize: theme.typography.h5.fontSize,
+ },
+ },
+ {
+ props: ({ variant }) => variant !== 'primary',
+ style: {
+ fontSize: theme.typography.body2.fontSize,
+ },
+ },
+ {
+ props: {
+ variant: 'primary',
+ },
+ style: {
+ fontWeight: theme.typography.h5.fontWeight,
+ },
+ },
+ {
+ props: ({ variant }) => variant !== 'primary',
+ style: {
+ fontWeight: theme.typography.body2.fontWeight,
+ },
+ },
+ ],
+}));
+
+interface PieCenterLabelProps {
+ primaryText: string;
+ secondaryText: string;
+}
+
+function PieCenterLabel({ primaryText, secondaryText }: PieCenterLabelProps) {
+ const { width, height, left, top } = useDrawingArea();
+ const primaryY = top + height / 2 - 10;
+ const secondaryY = primaryY + 24;
+
+ return (
+
+
+ {primaryText}
+
+
+ {secondaryText}
+
+
+ );
+}
+
+const colors = [
+ 'hsl(220, 20%, 65%)',
+ 'hsl(220, 20%, 42%)',
+ 'hsl(220, 20%, 35%)',
+ 'hsl(220, 20%, 25%)',
+];
+
+export default function ChartUserByCountry() {
+ return (
+
+
+
+ Users by country
+
+
+
+
+
+
+ {countries.map((country, index) => (
+
+ {country.flag}
+
+
+
+ {country.name}
+
+
+ {country.value}%
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/Copyright.tsx b/examples/core/auth-nextjs-themed/app/components/Copyright.tsx
new file mode 100644
index 00000000000..18e72887597
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/Copyright.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import Link from '@mui/material/Link';
+import Typography from '@mui/material/Typography';
+
+export default function Copyright(props: any) {
+ return (
+
+ {'Copyright © '}
+
+ Your Co
+ {' '}
+ {new Date().getFullYear()}
+ {'.'}
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/CustomDataGrid.tsx b/examples/core/auth-nextjs-themed/app/components/CustomDataGrid.tsx
new file mode 100644
index 00000000000..27a6050d060
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/CustomDataGrid.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import { DataGrid } from '@mui/x-data-grid';
+import { columns, rows } from '../mocks/gridOrdersData';
+
+export default function CustomizedDataGrid() {
+ return (
+
+ (params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd')}
+ initialState={{
+ pagination: { paginationModel: { pageSize: 20 } },
+ }}
+ sx={(theme) => ({
+ borderColor:
+ theme.palette.mode === 'dark' ? theme.palette.grey[700] : theme.palette.grey[200],
+ '& .MuiDataGrid-cell': {
+ borderColor:
+ theme.palette.mode === 'dark' ? theme.palette.grey[700] : theme.palette.grey[200],
+ },
+ })}
+ pageSizeOptions={[10, 20, 50]}
+ disableColumnResize
+ density="compact"
+ slotProps={{
+ filterPanel: {
+ filterFormProps: {
+ logicOperatorInputProps: {
+ variant: 'outlined',
+ size: 'small',
+ },
+ columnInputProps: {
+ variant: 'outlined',
+ size: 'small',
+ sx: { mt: 'auto' },
+ },
+ operatorInputProps: {
+ variant: 'outlined',
+ size: 'small',
+ sx: { mt: 'auto' },
+ },
+ valueInputProps: {
+ InputComponentProps: {
+ variant: 'outlined',
+ size: 'small',
+ },
+ },
+ },
+ },
+ }}
+ />
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/CustomDatePicker.tsx b/examples/core/auth-nextjs-themed/app/components/CustomDatePicker.tsx
new file mode 100644
index 00000000000..59307f43a46
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/CustomDatePicker.tsx
@@ -0,0 +1,71 @@
+import * as React from 'react';
+import dayjs, { Dayjs } from 'dayjs';
+import Button from '@mui/material/Button';
+import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded';
+import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
+import { UseDateFieldProps } from '@mui/x-date-pickers/DateField';
+import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
+import { DatePicker } from '@mui/x-date-pickers/DatePicker';
+import {
+ BaseSingleInputFieldProps,
+ DateValidationError,
+ FieldSection,
+} from '@mui/x-date-pickers/models';
+
+interface ButtonFieldProps
+ extends UseDateFieldProps,
+ BaseSingleInputFieldProps {
+ setOpen?: React.Dispatch>;
+}
+
+function ButtonField(props: ButtonFieldProps) {
+ const {
+ setOpen,
+ label,
+ id,
+ disabled,
+ InputProps: { ref } = {},
+ inputProps: { 'aria-label': ariaLabel } = {},
+ } = props;
+
+ return (
+ setOpen?.((prev) => !prev)}
+ startIcon={ }
+ sx={{ minWidth: 'fit-content' }}
+ >
+ {label ? `${label}` : 'Pick a date'}
+
+ );
+}
+
+export default function CustomDatePicker() {
+ const [value, setValue] = React.useState(dayjs('2023-04-17'));
+ const [open, setOpen] = React.useState(false);
+
+ return (
+
+ setValue(newValue)}
+ slots={{ field: ButtonField }}
+ slotProps={{
+ field: { setOpen } as any,
+ nextIconButton: { size: 'small' },
+ previousIconButton: { size: 'small' },
+ }}
+ open={open}
+ onClose={() => setOpen(false)}
+ onOpen={() => setOpen(true)}
+ views={['day', 'month', 'year']}
+ />
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/CustomTreeView.tsx b/examples/core/auth-nextjs-themed/app/components/CustomTreeView.tsx
new file mode 100644
index 00000000000..d0f18d52d1b
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/CustomTreeView.tsx
@@ -0,0 +1,203 @@
+'use client';
+import * as React from 'react';
+import clsx from 'clsx';
+import { animated, useSpring } from '@react-spring/web';
+import { TransitionProps } from '@mui/material/transitions';
+import Box from '@mui/material/Box';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Collapse from '@mui/material/Collapse';
+import Typography from '@mui/material/Typography';
+import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
+import {
+ unstable_useTreeItem2 as useTreeItem2,
+ UseTreeItem2Parameters,
+} from '@mui/x-tree-view/useTreeItem2';
+import {
+ TreeItem2Content,
+ TreeItem2IconContainer,
+ TreeItem2Label,
+ TreeItem2Root,
+} from '@mui/x-tree-view/TreeItem2';
+import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon';
+import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider';
+import { TreeViewBaseItem } from '@mui/x-tree-view/models';
+import { useTheme } from '@mui/material/styles';
+
+type Color = 'blue' | 'green';
+
+type ExtendedTreeItemProps = {
+ color?: Color;
+ id: string;
+ label: string;
+};
+
+const ITEMS: TreeViewBaseItem[] = [
+ {
+ id: '1',
+ label: 'Website',
+ children: [
+ { id: '1.1', label: 'Home', color: 'green' },
+ { id: '1.2', label: 'Pricing', color: 'green' },
+ { id: '1.3', label: 'About us', color: 'green' },
+ {
+ id: '1.4',
+ label: 'Blog',
+ children: [
+ { id: '1.1.1', label: 'Announcements', color: 'blue' },
+ { id: '1.1.2', label: 'April lookahead', color: 'blue' },
+ { id: '1.1.3', label: "What's new", color: 'blue' },
+ { id: '1.1.4', label: 'Meet the team', color: 'blue' },
+ ],
+ },
+ ],
+ },
+ {
+ id: '2',
+ label: 'Store',
+ children: [
+ { id: '2.1', label: 'All products', color: 'green' },
+ {
+ id: '2.2',
+ label: 'Categories',
+ children: [
+ { id: '2.2.1', label: 'Gadgets', color: 'blue' },
+ { id: '2.2.2', label: 'Phones', color: 'blue' },
+ { id: '2.2.3', label: 'Wearables', color: 'blue' },
+ ],
+ },
+ { id: '2.3', label: 'Bestsellers', color: 'green' },
+ { id: '2.4', label: 'Sales', color: 'green' },
+ ],
+ },
+ { id: '4', label: 'Contact', color: 'blue' },
+ { id: '5', label: 'Help', color: 'blue' },
+];
+
+function DotIcon({ color }: { color: string }) {
+ return (
+
+
+
+
+
+ );
+}
+
+const AnimatedCollapse = animated(Collapse);
+
+function TransitionComponent(props: TransitionProps) {
+ const style = useSpring({
+ to: {
+ opacity: props.in ? 1 : 0,
+ transform: `translate3d(0,${props.in ? 0 : 20}px,0)`,
+ },
+ });
+
+ return ;
+}
+
+interface CustomLabelProps {
+ children: React.ReactNode;
+ color?: Color;
+ expandable?: boolean;
+}
+
+function CustomLabel({ color, expandable, children, ...other }: CustomLabelProps) {
+ const theme = useTheme();
+ const colors = {
+ blue: (theme.vars || theme).palette.primary.main,
+ green: (theme.vars || theme).palette.success.main,
+ };
+
+ const iconColor = color ? colors[color] : null;
+ return (
+
+ {iconColor && }
+
+ {children}
+
+
+ );
+}
+
+interface CustomTreeItemProps
+ extends Omit,
+ Omit, 'onFocus'> {}
+
+const CustomTreeItem = React.forwardRef(function CustomTreeItem(
+ props: CustomTreeItemProps,
+ ref: React.Ref,
+) {
+ const { id, itemId, label, disabled, children, ...other } = props;
+
+ const {
+ getRootProps,
+ getContentProps,
+ getIconContainerProps,
+ getLabelProps,
+ getGroupTransitionProps,
+ status,
+ publicAPI,
+ } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref });
+
+ const item = publicAPI.getItem(itemId);
+ const color = item?.color;
+ return (
+
+
+
+ {status.expandable && (
+
+
+
+ )}
+
+
+
+ {children && (
+
+ )}
+
+
+ );
+});
+
+export default function CustomizedTreeView() {
+ return (
+
+
+
+ Product tree
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/HiglightedCard.tsx b/examples/core/auth-nextjs-themed/app/components/HiglightedCard.tsx
new file mode 100644
index 00000000000..f6f8d68b280
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/HiglightedCard.tsx
@@ -0,0 +1,37 @@
+import * as React from 'react';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Button from '@mui/material/Button';
+import Typography from '@mui/material/Typography';
+import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';
+import InsightsRoundedIcon from '@mui/icons-material/InsightsRounded';
+import useMediaQuery from '@mui/material/useMediaQuery';
+import { useTheme } from '@mui/material/styles';
+
+export default function HighlightedCard() {
+ const theme = useTheme();
+ const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
+
+ return (
+
+
+
+
+ Explore your data
+
+
+ Uncover performance and visitor insights with our data wizardry.
+
+ }
+ fullWidth={isSmallScreen}
+ >
+ Get insights
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/PageViewsBarChart.tsx b/examples/core/auth-nextjs-themed/app/components/PageViewsBarChart.tsx
new file mode 100644
index 00000000000..e7a90bde2a4
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/PageViewsBarChart.tsx
@@ -0,0 +1,85 @@
+import * as React from 'react';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Chip from '@mui/material/Chip';
+import Typography from '@mui/material/Typography';
+import Stack from '@mui/material/Stack';
+import { BarChart } from '@mui/x-charts/BarChart';
+import { useTheme } from '@mui/material/styles';
+
+export default function PageViewsBarChart() {
+ const theme = useTheme();
+ const colorPalette = [
+ (theme.vars || theme).palette.primary.dark,
+ (theme.vars || theme).palette.primary.main,
+ (theme.vars || theme).palette.primary.light,
+ ];
+ return (
+
+
+
+ Page views and downloads
+
+
+
+
+ 1.3M
+
+
+
+
+ Page views and downloads for the last 6 months
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/SessionsChart.tsx b/examples/core/auth-nextjs-themed/app/components/SessionsChart.tsx
new file mode 100644
index 00000000000..f43d0af4b4a
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/SessionsChart.tsx
@@ -0,0 +1,150 @@
+import * as React from 'react';
+import { useTheme } from '@mui/material/styles';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Chip from '@mui/material/Chip';
+import Typography from '@mui/material/Typography';
+import Stack from '@mui/material/Stack';
+import { LineChart } from '@mui/x-charts/LineChart';
+
+function AreaGradient({ color, id }: { color: string; id: string }) {
+ return (
+
+
+
+
+
+
+ );
+}
+
+function getDaysInMonth(month: number, year: number) {
+ const date = new Date(year, month, 0);
+ const monthName = date.toLocaleDateString('en-US', {
+ month: 'short',
+ });
+ const daysInMonth = date.getDate();
+ const days = [];
+ let i = 1;
+ while (days.length < daysInMonth) {
+ days.push(`${monthName} ${i}`);
+ i += 1;
+ }
+ return days;
+}
+
+export default function SessionsChart() {
+ const theme = useTheme();
+ const data = getDaysInMonth(4, 2024);
+
+ const colorPalette = [
+ theme.palette.primary.light,
+ theme.palette.primary.main,
+ theme.palette.primary.dark,
+ ];
+
+ return (
+
+
+
+ Sessions
+
+
+
+
+ 13,277
+
+
+
+
+ Sessions per day for the last 30 days
+
+
+ (i + 1) % 5 === 0,
+ },
+ ]}
+ series={[
+ {
+ id: 'direct',
+ label: 'Direct',
+ showMark: false,
+ curve: 'linear',
+ stack: 'total',
+ area: true,
+ stackOrder: 'ascending',
+ data: [
+ 300, 900, 600, 1200, 1500, 1800, 2400, 2100, 2700, 3000, 1800, 3300, 3600, 3900,
+ 4200, 4500, 3900, 4800, 5100, 5400, 4800, 5700, 6000, 6300, 6600, 6900, 7200, 7500,
+ 7800, 8100,
+ ],
+ },
+ {
+ id: 'referral',
+ label: 'Referral',
+ showMark: false,
+ curve: 'linear',
+ stack: 'total',
+ area: true,
+ stackOrder: 'ascending',
+ data: [
+ 500, 900, 700, 1400, 1100, 1700, 2300, 2000, 2600, 2900, 2300, 3200, 3500, 3800,
+ 4100, 4400, 2900, 4700, 5000, 5300, 5600, 5900, 6200, 6500, 5600, 6800, 7100, 7400,
+ 7700, 8000,
+ ],
+ },
+ {
+ id: 'organic',
+ label: 'Organic',
+ showMark: false,
+ curve: 'linear',
+ stack: 'total',
+ stackOrder: 'ascending',
+ data: [
+ 1000, 1500, 1200, 1700, 1300, 2000, 2400, 2200, 2600, 2800, 2500, 3000, 3400, 3700,
+ 3200, 3900, 4100, 3500, 4300, 4500, 4000, 4700, 5000, 5200, 4800, 5400, 5600, 5900,
+ 6100, 6300,
+ ],
+ area: true,
+ },
+ ]}
+ height={250}
+ margin={{ left: 50, right: 20, top: 20, bottom: 20 }}
+ grid={{ horizontal: true }}
+ sx={{
+ '& .MuiAreaElement-series-organic': {
+ fill: "url('#organic')",
+ },
+ '& .MuiAreaElement-series-referral': {
+ fill: "url('#referral')",
+ },
+ '& .MuiAreaElement-series-direct': {
+ fill: "url('#direct')",
+ },
+ }}
+ slotProps={{
+ legend: {
+ hidden: true,
+ },
+ }}
+ >
+
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/components/StatCard.tsx b/examples/core/auth-nextjs-themed/app/components/StatCard.tsx
new file mode 100644
index 00000000000..0438bc8f1fd
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/components/StatCard.tsx
@@ -0,0 +1,108 @@
+import * as React from 'react';
+import { useTheme } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Chip from '@mui/material/Chip';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
+import { areaElementClasses } from '@mui/x-charts/LineChart';
+
+export type StatCardProps = {
+ title: string;
+ value: string;
+ interval: string;
+ trend: 'up' | 'down' | 'neutral';
+ data: number[];
+};
+
+function getDaysInMonth(month: number, year: number) {
+ const date = new Date(year, month, 0);
+ const monthName = date.toLocaleDateString('en-US', {
+ month: 'short',
+ });
+ const daysInMonth = date.getDate();
+ const days = [];
+ let i = 1;
+ while (days.length < daysInMonth) {
+ days.push(`${monthName} ${i}`);
+ i += 1;
+ }
+ return days;
+}
+
+function AreaGradient({ color, id }: { color: string; id: string }) {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default function StatCard({ title, value, interval, trend, data }: StatCardProps) {
+ const theme = useTheme();
+ const daysInWeek = getDaysInMonth(4, 2024);
+
+ const trendColors = {
+ up: theme.palette.mode === 'light' ? theme.palette.success.main : theme.palette.success.dark,
+ down: theme.palette.mode === 'light' ? theme.palette.error.main : theme.palette.error.dark,
+ neutral: theme.palette.mode === 'light' ? theme.palette.grey[400] : theme.palette.grey[700],
+ };
+
+ const labelColors = {
+ up: 'success' as const,
+ down: 'error' as const,
+ neutral: 'default' as const,
+ };
+
+ const color = labelColors[trend];
+ const chartColor = trendColors[trend];
+ const trendValues = { up: '+25%', down: '-25%', neutral: '+5%' };
+
+ return (
+
+
+
+ {title}
+
+
+
+
+
+ {value}
+
+
+
+
+ {interval}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/layout.tsx b/examples/core/auth-nextjs-themed/app/layout.tsx
new file mode 100644
index 00000000000..3fceadb73cb
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/layout.tsx
@@ -0,0 +1,52 @@
+import * as React from 'react';
+import { NextAppProvider } from '@toolpad/core/nextjs';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
+import type { Navigation } from '@toolpad/core/AppProvider';
+import { SessionProvider, signIn, signOut } from 'next-auth/react';
+import theme from '../theme';
+import { auth } from '../auth';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const AUTHENTICATION = {
+ signIn,
+ signOut,
+};
+
+export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
+ const session = await auth();
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/mocks/CustomIcons.tsx b/examples/core/auth-nextjs-themed/app/mocks/CustomIcons.tsx
new file mode 100644
index 00000000000..af53b6aeba4
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/mocks/CustomIcons.tsx
@@ -0,0 +1,189 @@
+import * as React from 'react';
+import SvgIcon from '@mui/material/SvgIcon';
+
+export function UsaFlag() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+export function BrazilFlag() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function GlobeFlag() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-nextjs-themed/app/mocks/gridData.tsx b/examples/core/auth-nextjs-themed/app/mocks/gridData.tsx
new file mode 100644
index 00000000000..d04b3fdcd74
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/mocks/gridData.tsx
@@ -0,0 +1,619 @@
+import * as React from 'react';
+import Avatar from '@mui/material/Avatar';
+import Chip from '@mui/material/Chip';
+import { GridCellParams, GridRowsProp, GridColDef } from '@mui/x-data-grid';
+import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
+
+type SparkLineData = number[];
+
+function getDaysInMonth(month: number, year: number) {
+ const date = new Date(year, month, 0);
+ const monthName = date.toLocaleDateString('en-US', {
+ month: 'short',
+ });
+ const daysInMonth = date.getDate();
+ const days = [];
+ let i = 1;
+ while (days.length < daysInMonth) {
+ days.push(`${monthName} ${i}`);
+ i += 1;
+ }
+ return days;
+}
+
+function renderSparklineCell(params: GridCellParams) {
+ const data = getDaysInMonth(4, 2024);
+ const { value, colDef } = params;
+
+ if (!value || value.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
+function renderStatus(status: 'Online' | 'Offline') {
+ const colors: { [index: string]: 'success' | 'default' } = {
+ Online: 'success',
+ Offline: 'default',
+ };
+
+ return ;
+}
+
+export function renderAvatar(params: GridCellParams<{ name: string; color: string }, any, any>) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+ {params.value.name.toUpperCase().substring(0, 1)}
+
+ );
+}
+
+export const columns: GridColDef[] = [
+ { field: 'pageTitle', headerName: 'Page Title', flex: 1.5, minWidth: 200 },
+ {
+ field: 'status',
+ headerName: 'Status',
+ flex: 0.5,
+ minWidth: 80,
+ renderCell: (params) => renderStatus(params.value as any),
+ },
+ {
+ field: 'users',
+ headerName: 'Users',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 80,
+ },
+ {
+ field: 'eventCount',
+ headerName: 'Event Count',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 100,
+ },
+ {
+ field: 'viewsPerUser',
+ headerName: 'Views per User',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 120,
+ },
+ {
+ field: 'averageTime',
+ headerName: 'Average Time',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 100,
+ },
+ {
+ field: 'conversions',
+ headerName: 'Daily Conversions',
+ flex: 1,
+ minWidth: 150,
+ renderCell: renderSparklineCell,
+ },
+];
+
+export const rows: GridRowsProp = [
+ {
+ id: 1,
+ pageTitle: 'Homepage Overview',
+ status: 'Online',
+ eventCount: 8345,
+ users: 212423,
+ viewsPerUser: 18.5,
+ averageTime: '2m 15s',
+ conversions: [
+ 469172, 488506, 592287, 617401, 640374, 632751, 668638, 807246, 749198, 944863, 911787,
+ 844815, 992022, 1143838, 1446926, 1267886, 1362511, 1348746, 1560533, 1670690, 1695142,
+ 1916613, 1823306, 1683646, 2025965, 2529989, 3263473, 3296541, 3041524, 2599497,
+ ],
+ },
+ {
+ id: 2,
+ pageTitle: 'Product Details - Gadgets',
+ status: 'Online',
+ eventCount: 5653,
+ users: 172240,
+ viewsPerUser: 9.7,
+ averageTime: '2m 30s',
+ conversions: [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 557488, 1341471,
+ 2044561, 2206438,
+ ],
+ },
+ {
+ id: 3,
+ pageTitle: 'Checkout Process - Step 1',
+ status: 'Offline',
+ eventCount: 3455,
+ users: 58240,
+ viewsPerUser: 15.2,
+ averageTime: '2m 10s',
+ conversions: [
+ 166896, 190041, 248686, 226746, 261744, 271890, 332176, 381123, 396435, 495620, 520278,
+ 460839, 704158, 559134, 681089, 712384, 765381, 771374, 851314, 907947, 903675, 1049642,
+ 1003160, 881573, 1072283, 1139115, 1382701, 1395655, 1355040, 1381571,
+ ],
+ },
+ {
+ id: 4,
+ pageTitle: 'User Profile Dashboard',
+ status: 'Online',
+ eventCount: 112543,
+ users: 96240,
+ viewsPerUser: 4.5,
+ averageTime: '2m 40s',
+ conversions: [
+ 264651, 311845, 436558, 439385, 520413, 533380, 562363, 533793, 558029, 791126, 649082,
+ 566792, 723451, 737827, 890859, 935554, 1044397, 1022973, 1129827, 1145309, 1195630, 1358925,
+ 1373160, 1172679, 1340106, 1396974, 1623641, 1687545, 1581634, 1550291,
+ ],
+ },
+ {
+ id: 5,
+ pageTitle: 'Article Listing - Tech News',
+ status: 'Offline',
+ eventCount: 3653,
+ users: 142240,
+ viewsPerUser: 3.1,
+ averageTime: '2m 55s',
+ conversions: [
+ 251871, 262216, 402383, 396459, 378793, 406720, 447538, 451451, 457111, 589821, 640744,
+ 504879, 626099, 662007, 754576, 768231, 833019, 851537, 972306, 1014831, 1027570, 1189068,
+ 1119099, 987244, 1197954, 1310721, 1480816, 1577547, 1854053, 1791831,
+ ],
+ },
+ {
+ id: 6,
+ pageTitle: 'FAQs - Customer Support',
+ status: 'Online',
+ eventCount: 106543,
+ users: 15240,
+ viewsPerUser: 7.2,
+ averageTime: '2m 20s',
+ conversions: [
+ 13671, 16918, 27272, 34315, 42212, 56369, 64241, 77857, 70680, 91093, 108306, 94734, 132289,
+ 133860, 147706, 158504, 192578, 207173, 220052, 233496, 250091, 285557, 268555, 259482,
+ 274019, 321648, 359801, 399502, 447249, 497403,
+ ],
+ },
+ {
+ id: 7,
+ pageTitle: 'Product Comparison - Laptops',
+ status: 'Offline',
+ eventCount: 7853,
+ users: 32240,
+ viewsPerUser: 6.5,
+ averageTime: '2m 50s',
+ conversions: [
+ 93682, 107901, 144919, 151769, 170804, 183736, 201752, 219792, 227887, 295382, 309600, 278050,
+ 331964, 356826, 404896, 428090, 470245, 485582, 539056, 582112, 594289, 671915, 649510,
+ 574911, 713843, 754965, 853020, 916793, 960158, 984265,
+ ],
+ },
+ {
+ id: 8,
+ pageTitle: 'Shopping Cart - Electronics',
+ status: 'Online',
+ eventCount: 8563,
+ users: 48240,
+ viewsPerUser: 4.3,
+ averageTime: '3m 10s',
+ conversions: [
+ 52394, 63357, 82800, 105466, 128729, 144472, 172148, 197919, 212302, 278153, 290499, 249824,
+ 317499, 333024, 388925, 410576, 462099, 488477, 533956, 572307, 591019, 681506, 653332,
+ 581234, 719038, 783496, 911609, 973328, 1056071, 1112940,
+ ],
+ },
+ {
+ id: 9,
+ pageTitle: 'Payment Confirmation - Bank Transfer',
+ status: 'Offline',
+ eventCount: 4563,
+ users: 18240,
+ viewsPerUser: 2.7,
+ averageTime: '3m 25s',
+ conversions: [
+ 15372, 16901, 25489, 30148, 40857, 51136, 64627, 75804, 89633, 100407, 114908, 129957, 143568,
+ 158509, 174822, 192488, 211512, 234702, 258812, 284328, 310431, 338186, 366582, 396749,
+ 428788, 462880, 499125, 537723, 578884, 622825,
+ ],
+ },
+ {
+ id: 10,
+ pageTitle: 'Product Reviews - Smartphones',
+ status: 'Online',
+ eventCount: 9863,
+ users: 28240,
+ viewsPerUser: 5.1,
+ averageTime: '3m 05s',
+ conversions: [
+ 70211, 89234, 115676, 136021, 158744, 174682, 192890, 218073, 240926, 308190, 317552, 279834,
+ 334072, 354955, 422153, 443911, 501486, 538091, 593724, 642882, 686539, 788615, 754813,
+ 687955, 883645, 978347, 1142551, 1233074, 1278155, 1356724,
+ ],
+ },
+ {
+ id: 11,
+ pageTitle: 'Subscription Management - Services',
+ status: 'Offline',
+ eventCount: 6563,
+ users: 24240,
+ viewsPerUser: 4.8,
+ averageTime: '3m 15s',
+ conversions: [
+ 49662, 58971, 78547, 93486, 108722, 124901, 146422, 167883, 189295, 230090, 249837, 217828,
+ 266494, 287537, 339586, 363299, 412855, 440900, 490111, 536729, 580591, 671635, 655812,
+ 576431, 741632, 819296, 971762, 1052605, 1099234, 1173591,
+ ],
+ },
+ {
+ id: 12,
+ pageTitle: 'Order Tracking - Shipments',
+ status: 'Online',
+ eventCount: 12353,
+ users: 38240,
+ viewsPerUser: 3.5,
+ averageTime: '3m 20s',
+ conversions: [
+ 29589, 37965, 55800, 64672, 77995, 91126, 108203, 128900, 148232, 177159, 193489, 164471,
+ 210765, 229977, 273802, 299381, 341092, 371567, 413812, 457693, 495920, 564785, 541022,
+ 491680, 618096, 704926, 833365, 904313, 974622, 1036567,
+ ],
+ },
+ {
+ id: 13,
+ pageTitle: 'Customer Feedback - Surveys',
+ status: 'Offline',
+ eventCount: 5863,
+ users: 13240,
+ viewsPerUser: 2.3,
+ averageTime: '3m 30s',
+ conversions: [
+ 8472, 9637, 14892, 19276, 23489, 28510, 33845, 39602, 45867, 52605, 59189, 65731, 76021,
+ 85579, 96876, 108515, 119572, 131826, 145328, 160192, 176528, 196662, 217929, 239731, 262920,
+ 289258, 315691, 342199, 370752, 402319,
+ ],
+ },
+ {
+ id: 14,
+ pageTitle: 'Account Settings - Preferences',
+ status: 'Online',
+ eventCount: 7853,
+ users: 18240,
+ viewsPerUser: 3.2,
+ averageTime: '3m 15s',
+ conversions: [
+ 15792, 16948, 22728, 25491, 28412, 31268, 34241, 37857, 42068, 46893, 51098, 55734, 60780,
+ 66421, 72680, 79584, 87233, 95711, 105285, 115814, 127509, 140260, 154086, 169495, 186445,
+ 205109, 225580, 247983, 272484, 299280,
+ ],
+ },
+ {
+ id: 15,
+ pageTitle: 'Login Page - Authentication',
+ status: 'Offline',
+ eventCount: 9563,
+ users: 24240,
+ viewsPerUser: 2.5,
+ averageTime: '3m 35s',
+ conversions: [
+ 25638, 28355, 42089, 53021, 66074, 80620, 97989, 118202, 142103, 166890, 193869, 225467,
+ 264089, 307721, 358059, 417835, 488732, 573924, 674878, 794657, 938542, 1111291, 1313329,
+ 1543835, 1812156, 2123349, 2484926, 2907023, 3399566, 3973545,
+ ],
+ },
+ {
+ id: 16,
+ pageTitle: 'Promotions - Seasonal Sales',
+ status: 'Online',
+ eventCount: 13423,
+ users: 54230,
+ viewsPerUser: 7.8,
+ averageTime: '2m 45s',
+ conversions: [
+ 241732, 256384, 289465, 321423, 345672, 378294, 398472, 420364, 436278, 460192, 495374,
+ 510283, 532489, 559672, 587312, 610982, 629385, 654732, 678925, 704362, 725182, 749384,
+ 772361, 798234, 819472, 846291, 872183, 894673, 919283, 945672,
+ ],
+ },
+ {
+ id: 17,
+ pageTitle: 'Tutorials - How to Guides',
+ status: 'Offline',
+ eventCount: 4234,
+ users: 19342,
+ viewsPerUser: 5.2,
+ averageTime: '3m 05s',
+ conversions: [
+ 12345, 14567, 16789, 18901, 21023, 23145, 25267, 27389, 29501, 31623, 33745, 35867, 37989,
+ 40101, 42223, 44345, 46467, 48589, 50701, 52823, 54945, 57067, 59189, 61301, 63423, 65545,
+ 67667, 69789, 71901, 74023,
+ ],
+ },
+ {
+ id: 18,
+ pageTitle: 'Blog Posts - Tech Insights',
+ status: 'Online',
+ eventCount: 8567,
+ users: 34234,
+ viewsPerUser: 6.3,
+ averageTime: '2m 50s',
+ conversions: [
+ 23456, 25678, 27890, 30102, 32324, 34546, 36768, 38980, 41202, 43424, 45646, 47868, 50080,
+ 52302, 54524, 56746, 58968, 61180, 63402, 65624, 67846, 70068, 72290, 74502, 76724, 78946,
+ 81168, 83380, 85602, 87824,
+ ],
+ },
+ {
+ id: 19,
+ pageTitle: 'Events - Webinars',
+ status: 'Offline',
+ eventCount: 3456,
+ users: 19234,
+ viewsPerUser: 4.5,
+ averageTime: '3m 20s',
+ conversions: [
+ 123456, 145678, 167890, 190012, 212324, 234546, 256768, 278980, 301202, 323424, 345646,
+ 367868, 390080, 412302, 434524, 456746, 478968, 501180, 523402, 545624, 567846, 590068,
+ 612290, 634502, 656724, 678946, 701168, 723380, 745602, 767824,
+ ],
+ },
+ {
+ id: 20,
+ pageTitle: 'Support - Contact Us',
+ status: 'Online',
+ eventCount: 6734,
+ users: 27645,
+ viewsPerUser: 3.9,
+ averageTime: '2m 55s',
+ conversions: [
+ 234567, 256789, 278901, 301023, 323245, 345467, 367689, 389801, 412023, 434245, 456467,
+ 478689, 500801, 523023, 545245, 567467, 589689, 611801, 634023, 656245, 678467, 700689,
+ 722801, 745023, 767245, 789467, 811689, 833801, 856023, 878245,
+ ],
+ },
+ {
+ id: 21,
+ pageTitle: 'Case Studies - Success Stories',
+ status: 'Offline',
+ eventCount: 4567,
+ users: 19345,
+ viewsPerUser: 6.1,
+ averageTime: '3m 10s',
+ conversions: [
+ 34567, 36789, 38901, 41023, 43145, 45267, 47389, 49501, 51623, 53745, 55867, 57989, 60101,
+ 62223, 64345, 66467, 68589, 70701, 72823, 74945, 77067, 79189, 81301, 83423, 85545, 87667,
+ 89789, 91901, 94023, 96145,
+ ],
+ },
+ {
+ id: 22,
+ pageTitle: 'News - Industry Updates',
+ status: 'Online',
+ eventCount: 7856,
+ users: 34567,
+ viewsPerUser: 5.7,
+ averageTime: '3m 05s',
+ conversions: [
+ 45678, 47890, 50102, 52324, 54546, 56768, 58980, 61202, 63424, 65646, 67868, 70080, 72302,
+ 74524, 76746, 78968, 81180, 83402, 85624, 87846, 90068, 92290, 94502, 96724, 98946, 101168,
+ 103380, 105602, 107824, 110046,
+ ],
+ },
+ {
+ id: 23,
+ pageTitle: 'Forum - User Discussions',
+ status: 'Offline',
+ eventCount: 5678,
+ users: 23456,
+ viewsPerUser: 4.2,
+ averageTime: '2m 40s',
+ conversions: [
+ 56789, 58901, 61023, 63145, 65267, 67389, 69501, 71623, 73745, 75867, 77989, 80101, 82223,
+ 84345, 86467, 88589, 90701, 92823, 94945, 97067, 99189, 101301, 103423, 105545, 107667,
+ 109789, 111901, 114023, 116145, 118267,
+ ],
+ },
+ {
+ id: 24,
+ pageTitle: 'Documentation - API Reference',
+ status: 'Online',
+ eventCount: 6789,
+ users: 27689,
+ viewsPerUser: 5.0,
+ averageTime: '3m 00s',
+ conversions: [
+ 67890, 70102, 72324, 74546, 76768, 78980, 81202, 83424, 85646, 87868, 90080, 92302, 94524,
+ 96746, 98968, 101180, 103402, 105624, 107846, 110068, 112290, 114502, 116724, 118946, 121168,
+ 123380, 125602, 127824, 130046, 132268,
+ ],
+ },
+ {
+ id: 25,
+ pageTitle: 'Services - Consulting',
+ status: 'Offline',
+ eventCount: 4563,
+ users: 19240,
+ viewsPerUser: 6.4,
+ averageTime: '3m 25s',
+ conversions: [
+ 345678, 367890, 390012, 412324, 434546, 456768, 478980, 501202, 523424, 545646, 567868,
+ 590080, 612302, 634524, 656746, 678968, 701180, 723402, 745624, 767846, 790068, 812290,
+ 834502, 856724, 878946, 901168, 923380, 945602, 967824, 990046,
+ ],
+ },
+ {
+ id: 26,
+ pageTitle: 'Feedback - User Reviews',
+ status: 'Online',
+ eventCount: 8564,
+ users: 34240,
+ viewsPerUser: 6.2,
+ averageTime: '3m 15s',
+ conversions: [
+ 123478, 145690, 167912, 190134, 212356, 234578, 256790, 279012, 301234, 323456, 345678,
+ 367890, 390012, 412234, 434456, 456678, 478890, 501012, 523234, 545456, 567678, 589890,
+ 612012, 634234, 656456, 678678, 700890, 723012, 745234, 767456,
+ ],
+ },
+ {
+ id: 27,
+ pageTitle: 'Profiles - Team Members',
+ status: 'Offline',
+ eventCount: 5634,
+ users: 23423,
+ viewsPerUser: 5.5,
+ averageTime: '2m 45s',
+ conversions: [
+ 345123, 367345, 389567, 411789, 434012, 456234, 478456, 500678, 522901, 545123, 567345,
+ 589567, 611789, 634012, 656234, 678456, 700678, 722901, 745123, 767345, 789567, 811789,
+ 834012, 856234, 878456, 900678, 922901, 945123, 967345, 989567,
+ ],
+ },
+ {
+ id: 28,
+ pageTitle: 'Notifications - Alerts',
+ status: 'Online',
+ eventCount: 6745,
+ users: 27654,
+ viewsPerUser: 4.9,
+ averageTime: '3m 10s',
+ conversions: [
+ 456123, 478345, 500567, 522789, 545012, 567234, 589456, 611678, 633901, 656123, 678345,
+ 700567, 722789, 745012, 767234, 789456, 811678, 833901, 856123, 878345, 900567, 922789,
+ 945012, 967234, 989456, 1011678, 1033901, 1056123, 1078345, 1100567,
+ ],
+ },
+ {
+ id: 29,
+ pageTitle: 'Dashboard - Metrics',
+ status: 'Offline',
+ eventCount: 5678,
+ users: 23456,
+ viewsPerUser: 6.3,
+ averageTime: '2m 50s',
+ conversions: [
+ 567890, 590112, 612334, 634556, 656778, 678990, 701212, 723434, 745656, 767878, 790100,
+ 812322, 834544, 856766, 878988, 901210, 923432, 945654, 967876, 990098, 1012320, 1034542,
+ 1056764, 1078986, 1101208, 1123430, 1145652, 1167874, 1190096, 1212318,
+ ],
+ },
+ {
+ id: 30,
+ pageTitle: 'Reports - Monthly Analysis',
+ status: 'Online',
+ eventCount: 7890,
+ users: 34567,
+ viewsPerUser: 5.9,
+ averageTime: '3m 20s',
+ conversions: [
+ 678901, 701123, 723345, 745567, 767789, 790011, 812233, 834455, 856677, 878899, 901121,
+ 923343, 945565, 967787, 990009, 1012231, 1034453, 1056675, 1078897, 1101119, 1123341, 1145563,
+ 1167785, 1190007, 1212229, 1234451, 1256673, 1278895, 1301117, 1323339,
+ ],
+ },
+ {
+ id: 31,
+ pageTitle: 'Training - Employee Onboarding',
+ status: 'Offline',
+ eventCount: 3456,
+ users: 19234,
+ viewsPerUser: 6.1,
+ averageTime: '3m 10s',
+ conversions: [
+ 789012, 811234, 833456, 855678, 877890, 900112, 922334, 944556, 966778, 989000, 1011222,
+ 1033444, 1055666, 1077888, 1100110, 1122332, 1144554, 1166776, 1188998, 1211220, 1233442,
+ 1255664, 1277886, 1300108, 1322330, 1344552, 1366774, 1388996, 1411218, 1433440,
+ ],
+ },
+ {
+ id: 32,
+ pageTitle: 'Resources - Knowledge Base',
+ status: 'Online',
+ eventCount: 5678,
+ users: 23456,
+ viewsPerUser: 4.7,
+ averageTime: '3m 25s',
+ conversions: [
+ 890123, 912345, 934567, 956789, 979012, 1001234, 1023456, 1045678, 1067890, 1090123, 1112345,
+ 1134567, 1156789, 1179012, 1201234, 1223456, 1245678, 1267890, 1290123, 1312345, 1334567,
+ 1356789, 1379012, 1401234, 1423456, 1445678, 1467890, 1490123, 1512345, 1534567,
+ ],
+ },
+ {
+ id: 33,
+ pageTitle: 'Settings - Privacy Controls',
+ status: 'Offline',
+ eventCount: 6789,
+ users: 27689,
+ viewsPerUser: 5.8,
+ averageTime: '3m 05s',
+ conversions: [
+ 901234, 923456, 945678, 967890, 990112, 1012334, 1034556, 1056778, 1079000, 1101222, 1123444,
+ 1145666, 1167888, 1190110, 1212332, 1234554, 1256776, 1278998, 1301220, 1323442, 1345664,
+ 1367886, 1390108, 1412330, 1434552, 1456774, 1478996, 1501218, 1523440, 1545662,
+ ],
+ },
+ {
+ id: 34,
+ pageTitle: 'Integrations - Third-Party Services',
+ status: 'Online',
+ eventCount: 4567,
+ users: 19345,
+ viewsPerUser: 4.4,
+ averageTime: '2m 50s',
+ conversions: [
+ 123457, 145679, 167891, 190113, 212335, 234557, 256779, 279001, 301223, 323445, 345667,
+ 367889, 390011, 412233, 434455, 456677, 478899, 501121, 523343, 545565, 567787, 590009,
+ 612231, 634453, 656675, 678897, 701119, 723341, 745563, 767785,
+ ],
+ },
+ {
+ id: 35,
+ pageTitle: 'Account - Billing Information',
+ status: 'Offline',
+ eventCount: 7890,
+ users: 34567,
+ viewsPerUser: 5.4,
+ averageTime: '3m 00s',
+ conversions: [
+ 234568, 256790, 278912, 301134, 323356, 345578, 367790, 390012, 412234, 434456, 456678,
+ 478890, 501112, 523334, 545556, 567778, 590000, 612222, 634444, 656666, 678888, 701110,
+ 723332, 745554, 767776, 789998, 812220, 834442, 856664, 878886,
+ ],
+ },
+];
diff --git a/examples/core/auth-nextjs-themed/app/mocks/gridOrdersData.tsx b/examples/core/auth-nextjs-themed/app/mocks/gridOrdersData.tsx
new file mode 100644
index 00000000000..ab093005bf3
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/app/mocks/gridOrdersData.tsx
@@ -0,0 +1,287 @@
+import * as React from 'react';
+import Avatar from '@mui/material/Avatar';
+import Chip from '@mui/material/Chip';
+import { GridCellParams, GridRowsProp, GridColDef } from '@mui/x-data-grid';
+import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
+
+type SparkLineData = number[];
+
+function getDaysInMonth(month: number, year: number) {
+ const date = new Date(year, month, 0);
+ const monthName = date.toLocaleDateString('en-US', {
+ month: 'short',
+ });
+ const daysInMonth = date.getDate();
+ const days = [];
+ let i = 1;
+ while (days.length < daysInMonth) {
+ days.push(`${monthName} ${i}`);
+ i += 1;
+ }
+ return days;
+}
+
+function renderSparklineCell(params: GridCellParams) {
+ const data = getDaysInMonth(4, 2024);
+ const { value, colDef } = params;
+
+ if (!value || value.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
+function renderStatus(status: 'In Stock' | 'Out of Stock' | 'Low Stock') {
+ const colors: { [index: string]: 'success' | 'error' | 'default' } = {
+ 'In Stock': 'success',
+ 'Out of Stock': 'error',
+ 'Low Stock': 'default',
+ };
+
+ return ;
+}
+
+export function renderAvatar(params: GridCellParams<{ name: string; color: string }, any, any>) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+ {params.value.name.toUpperCase().substring(0, 1)}
+
+ );
+}
+
+export const columns: GridColDef[] = [
+ { field: 'productName', headerName: 'Product Name', flex: 1.5, minWidth: 200 },
+ {
+ field: 'status',
+ headerName: 'Status',
+ flex: 1,
+ minWidth: 80,
+ renderCell: (params) => renderStatus(params.value as any),
+ },
+ {
+ field: 'totalOrders',
+ headerName: 'Total Orders',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 80,
+ },
+ {
+ field: 'revenue',
+ headerName: 'Revenue',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 100,
+ },
+ {
+ field: 'avgOrderValue',
+ headerName: 'Avg Order Value',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 120,
+ },
+ {
+ field: 'processingTime',
+ headerName: 'Processing Time',
+ headerAlign: 'right',
+ align: 'right',
+ flex: 1,
+ minWidth: 100,
+ },
+ {
+ field: 'dailyOrders',
+ headerName: 'Daily Orders',
+ flex: 1,
+ minWidth: 150,
+ renderCell: renderSparklineCell,
+ },
+];
+
+export const rows: GridRowsProp = [
+ {
+ id: 1,
+ productName: 'Premium Wireless Headphones',
+ status: 'In Stock',
+ totalOrders: 8345,
+ revenue: '$212,423',
+ avgOrderValue: '$185.50',
+ processingTime: '2d 15h',
+ dailyOrders: [
+ 65, 72, 68, 82, 75, 78, 88, 95, 89, 85, 92, 85, 88, 91, 87, 85, 89, 92, 95, 88, 85, 90, 92,
+ 89, 86, 88, 91, 94, 90, 87,
+ ],
+ },
+ {
+ id: 2,
+ productName: 'Smart Fitness Watch',
+ status: 'Low Stock',
+ totalOrders: 12567,
+ revenue: '$458,945',
+ avgOrderValue: '$149.99',
+ processingTime: '1d 8h',
+ dailyOrders: [
+ 120, 115, 125, 118, 130, 128, 135, 142, 138, 145, 150, 148, 155, 160, 158, 165, 170, 168, 175,
+ 172, 178, 180, 176, 182, 185, 188, 192, 190, 195, 198,
+ ],
+ },
+ {
+ id: 3,
+ productName: 'Organic Coffee Beans',
+ status: 'In Stock',
+ totalOrders: 25890,
+ revenue: '$129,450',
+ avgOrderValue: '$24.99',
+ processingTime: '1d 2h',
+ dailyOrders: [
+ 250, 245, 260, 255, 265, 270, 268, 275, 280, 278, 285, 290, 288, 295, 300, 298, 305, 310, 308,
+ 315, 320, 318, 325, 330, 328, 335, 340, 338, 345, 350,
+ ],
+ },
+ {
+ id: 4,
+ productName: 'Gaming Laptop Pro',
+ status: 'Out of Stock',
+ totalOrders: 3456,
+ revenue: '$4,147,200',
+ avgOrderValue: '$1,199.99',
+ processingTime: '3d 12h',
+ dailyOrders: [
+ 35, 32, 38, 36, 40, 42, 39, 45, 43, 48, 46, 50, 48, 52, 50, 55, 53, 58, 56, 60, 58, 62, 60,
+ 65, 63, 68, 66, 70, 68, 72,
+ ],
+ },
+ {
+ id: 5,
+ productName: 'Yoga Mat Premium',
+ status: 'In Stock',
+ totalOrders: 15678,
+ revenue: '$548,730',
+ avgOrderValue: '$34.99',
+ processingTime: '1d 4h',
+ dailyOrders: [
+ 155, 150, 160, 158, 165, 170, 168, 175, 172, 178, 180, 176, 182, 185, 188, 192, 190, 195, 198,
+ 200, 198, 205, 208, 210, 212, 215, 218, 220, 222, 225,
+ ],
+ },
+ {
+ id: 6,
+ productName: 'Smartphone Case',
+ status: 'In Stock',
+ totalOrders: 42567,
+ revenue: '$425,670',
+ avgOrderValue: '$19.99',
+ processingTime: '1d 0h',
+ dailyOrders: [
+ 420, 415, 425, 430, 428, 435, 440, 438, 445, 450, 448, 455, 460, 458, 465, 470, 468, 475, 480,
+ 478, 485, 490, 488, 495, 500, 498, 505, 510, 508, 515,
+ ],
+ },
+ {
+ id: 7,
+ productName: 'Professional Camera Kit',
+ status: 'Low Stock',
+ totalOrders: 2345,
+ revenue: '$2,345,000',
+ avgOrderValue: '$999.99',
+ processingTime: '2d 8h',
+ dailyOrders: [
+ 22, 20, 25, 23, 28, 26, 30, 28, 32, 30, 35, 33, 38, 36, 40, 38, 42, 40, 45, 43, 48, 46, 50,
+ 48, 52, 50, 55, 53, 58, 56,
+ ],
+ },
+ {
+ id: 8,
+ productName: 'Wireless Charging Pad',
+ status: 'In Stock',
+ totalOrders: 18934,
+ revenue: '$567,890',
+ avgOrderValue: '$29.99',
+ processingTime: '1d 6h',
+ dailyOrders: [
+ 180, 175, 185, 182, 188, 192, 190, 195, 198, 200, 198, 205, 208, 210, 212, 215, 218, 220, 222,
+ 225, 228, 230, 232, 235, 238, 240, 242, 245, 248, 250,
+ ],
+ },
+ {
+ id: 9,
+ productName: 'Smart Home Hub',
+ status: 'In Stock',
+ totalOrders: 7890,
+ revenue: '$789,000',
+ avgOrderValue: '$99.99',
+ processingTime: '2d 0h',
+ dailyOrders: [
+ 75, 72, 78, 76, 80, 82, 79, 85, 83, 88, 86, 90, 88, 92, 90, 95, 93, 98, 96, 100, 98, 102, 100,
+ 105, 103, 108, 106, 110, 108, 112,
+ ],
+ },
+ {
+ id: 10,
+ productName: 'Ergonomic Office Chair',
+ status: 'Low Stock',
+ totalOrders: 4567,
+ revenue: '$1,370,100',
+ avgOrderValue: '$299.99',
+ processingTime: '3d 0h',
+ dailyOrders: [
+ 42, 40, 45, 43, 48, 46, 50, 48, 52, 50, 55, 53, 58, 56, 60, 58, 62, 60, 65, 63, 68, 66, 70,
+ 68, 72, 70, 75, 73, 78, 76,
+ ],
+ },
+ {
+ id: 11,
+ productName: 'Portable Power Bank',
+ status: 'In Stock',
+ totalOrders: 23456,
+ revenue: '$703,680',
+ avgOrderValue: '$29.99',
+ processingTime: '1d 4h',
+ dailyOrders: [
+ 230, 225, 235, 232, 238, 242, 240, 245, 248, 250, 248, 255, 258, 260, 262, 265, 268, 270, 272,
+ 275, 278, 280, 282, 285, 288, 290, 292, 295, 298, 300,
+ ],
+ },
+ {
+ id: 12,
+ productName: 'Mechanical Keyboard',
+ status: 'In Stock',
+ totalOrders: 9876,
+ revenue: '$987,600',
+ avgOrderValue: '$99.99',
+ processingTime: '1d 12h',
+ dailyOrders: [
+ 95, 92, 98, 96, 100, 102, 99, 105, 103, 108, 106, 110, 108, 112, 110, 115, 113, 118, 116, 120,
+ 118, 122, 120, 125, 123, 128, 126, 130, 128, 132,
+ ],
+ },
+];
diff --git a/examples/core/auth-nextjs-themed/auth.ts b/examples/core/auth-nextjs-themed/auth.ts
new file mode 100644
index 00000000000..def197e2280
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/auth.ts
@@ -0,0 +1,60 @@
+import NextAuth from 'next-auth';
+import GitHub from 'next-auth/providers/github';
+import Google from 'next-auth/providers/google';
+import Credentials from 'next-auth/providers/credentials';
+import type { Provider } from 'next-auth/providers';
+
+const providers: Provider[] = [
+ GitHub({
+ clientId: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
+ }),
+ Google({
+ clientId: process.env.GOOGLE_CLIENT_ID,
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
+ }),
+ Credentials({
+ credentials: {
+ email: { label: 'Email Address', type: 'email' },
+ password: { label: 'Password', type: 'password' },
+ },
+ authorize(c) {
+ if (c.password === '@demo1' && c.email === 'toolpad-demo@mui.com') {
+ return {
+ id: 'test',
+ name: 'Toolpad Demo',
+ email: String(c.email),
+ };
+ }
+ return null;
+ },
+ }),
+];
+
+export const providerMap = providers.map((provider) => {
+ if (typeof provider === 'function') {
+ const providerData = provider();
+ return { id: providerData.id, name: providerData.name };
+ }
+ return { id: provider.id, name: provider.name };
+});
+
+export const { handlers, auth, signIn, signOut } = NextAuth({
+ providers,
+ secret: process.env.AUTH_SECRET,
+ pages: {
+ signIn: '/auth/signin',
+ },
+ callbacks: {
+ authorized({ auth: session, request: { nextUrl } }) {
+ const isLoggedIn = !!session?.user;
+ const isPublicPage = nextUrl.pathname.startsWith('/public');
+
+ if (isPublicPage || isLoggedIn) {
+ return true;
+ }
+
+ return false; // Redirect unauthenticated users to login page
+ },
+ },
+});
diff --git a/examples/core-auth-nextjs/src/middleware.ts b/examples/core/auth-nextjs-themed/middleware.ts
similarity index 100%
rename from examples/core-auth-nextjs/src/middleware.ts
rename to examples/core/auth-nextjs-themed/middleware.ts
diff --git a/examples/core/auth-nextjs-themed/next-env.d.ts b/examples/core/auth-nextjs-themed/next-env.d.ts
new file mode 100644
index 00000000000..40c3d68096c
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
diff --git a/examples/core-tutorial/next.config.mjs b/examples/core/auth-nextjs-themed/next.config.mjs
similarity index 100%
rename from examples/core-tutorial/next.config.mjs
rename to examples/core/auth-nextjs-themed/next.config.mjs
diff --git a/examples/core/auth-nextjs-themed/package.json b/examples/core/auth-nextjs-themed/package.json
new file mode 100644
index 00000000000..9a817648aec
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "auth-nextjs-themed",
+ "version": "0.1.0",
+ "scripts": {
+ "dev": "next dev",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@emotion/cache": "^11",
+ "@emotion/react": "^11",
+ "@emotion/styled": "^11",
+ "@mui/icons-material": "^6",
+ "@mui/material": "^6",
+ "@mui/material-nextjs": "^6",
+ "@mui/x-charts": "^7",
+ "@mui/x-tree-view": "^7",
+ "@mui/x-data-grid": "^7",
+ "@mui/x-date-pickers": "^7",
+ "dayjs": "^1",
+ "clsx": "^2",
+ "@react-spring/web": "^9",
+ "@toolpad/core": "latest",
+ "next": "^15",
+ "next-auth": "^5.0.0-beta.25",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.17.12",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "eslint": "^8",
+ "eslint-config-next": "^15",
+ "typescript": "^5"
+ }
+}
diff --git a/examples/core/auth-nextjs-themed/theme.ts b/examples/core/auth-nextjs-themed/theme.ts
new file mode 100644
index 00000000000..4536bd4389a
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme.ts
@@ -0,0 +1,13 @@
+'use client';
+import { createTheme } from '@mui/material/styles';
+import getMPTheme from './theme/getMPTheme';
+
+const lightTheme = createTheme(getMPTheme('light'));
+const darkTheme = createTheme(getMPTheme('dark'));
+
+const theme = {
+ light: lightTheme,
+ dark: darkTheme,
+};
+
+export default theme;
diff --git a/examples/core/auth-nextjs-themed/theme/customizations/dataDisplay.tsx b/examples/core/auth-nextjs-themed/theme/customizations/dataDisplay.tsx
new file mode 100644
index 00000000000..55580c72774
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme/customizations/dataDisplay.tsx
@@ -0,0 +1,222 @@
+import { Theme, alpha, Components } from '@mui/material/styles';
+import { svgIconClasses } from '@mui/material/SvgIcon';
+import { typographyClasses } from '@mui/material/Typography';
+import { buttonBaseClasses } from '@mui/material/ButtonBase';
+import { chipClasses } from '@mui/material/Chip';
+import { iconButtonClasses } from '@mui/material/IconButton';
+import { gray, red, green } from '../themePrimitives';
+
+export const dataDisplayCustomizations: Components = {
+ MuiList: {
+ styleOverrides: {
+ root: {
+ padding: '8px',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 0,
+ },
+ },
+ },
+ MuiListItem: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ [`& .${svgIconClasses.root}`]: {
+ width: '1rem',
+ height: '1rem',
+ color: theme.palette.text.secondary,
+ },
+ [`& .${typographyClasses.root}`]: {
+ fontWeight: 500,
+ },
+ [`& .${buttonBaseClasses.root}`]: {
+ display: 'flex',
+ gap: 8,
+ padding: '2px 8px',
+ borderRadius: theme.shape.borderRadius,
+ opacity: 0.7,
+ '&.Mui-selected': {
+ opacity: 1,
+ backgroundColor: alpha(theme.palette.action.selected, 0.3),
+ [`& .${svgIconClasses.root}`]: {
+ color: theme.palette.text.primary,
+ },
+ '&:focus-visible': {
+ backgroundColor: alpha(theme.palette.action.selected, 0.3),
+ },
+ '&:hover': {
+ backgroundColor: alpha(theme.palette.action.selected, 0.5),
+ },
+ },
+ '&:focus-visible': {
+ backgroundColor: 'transparent',
+ },
+ },
+ }),
+ },
+ },
+ MuiListItemText: {
+ styleOverrides: {
+ primary: ({ theme }) => ({
+ fontSize: theme.typography.body2.fontSize,
+ fontWeight: 500,
+ lineHeight: theme.typography.body2.lineHeight,
+ }),
+ secondary: ({ theme }) => ({
+ fontSize: theme.typography.caption.fontSize,
+ lineHeight: theme.typography.caption.lineHeight,
+ }),
+ },
+ },
+ MuiListItemIcon: {
+ styleOverrides: {
+ root: {
+ minWidth: 0,
+ justifyContent: 'center',
+ },
+ },
+ },
+ MuiChip: {
+ defaultProps: {
+ size: 'small',
+ },
+ styleOverrides: {
+ root: ({ theme }) => ({
+ border: '1px solid',
+ borderRadius: '999px',
+ [`& .${chipClasses.label}`]: {
+ fontWeight: 600,
+ },
+ variants: [
+ {
+ props: {
+ color: 'default',
+ },
+ style: {
+ borderColor: gray[200],
+ backgroundColor: gray[100],
+ [`& .${chipClasses.label}`]: {
+ color: gray[500],
+ },
+ [`& .${chipClasses.icon}`]: {
+ color: gray[500],
+ },
+ ...theme.applyStyles('dark', {
+ borderColor: gray[700],
+ backgroundColor: gray[800],
+ [`& .${chipClasses.label}`]: {
+ color: gray[300],
+ },
+ [`& .${chipClasses.icon}`]: {
+ color: gray[300],
+ },
+ }),
+ },
+ },
+ {
+ props: {
+ color: 'success',
+ },
+ style: {
+ borderColor: green[200],
+ backgroundColor: green[50],
+ [`& .${chipClasses.label}`]: {
+ color: green[500],
+ },
+ [`& .${chipClasses.icon}`]: {
+ color: green[500],
+ },
+ ...theme.applyStyles('dark', {
+ borderColor: green[800],
+ backgroundColor: green[900],
+ [`& .${chipClasses.label}`]: {
+ color: green[300],
+ },
+ [`& .${chipClasses.icon}`]: {
+ color: green[300],
+ },
+ }),
+ },
+ },
+ {
+ props: {
+ color: 'error',
+ },
+ style: {
+ borderColor: red[100],
+ backgroundColor: red[50],
+ [`& .${chipClasses.label}`]: {
+ color: red[500],
+ },
+ [`& .${chipClasses.icon}`]: {
+ color: red[500],
+ },
+ ...theme.applyStyles('dark', {
+ borderColor: red[800],
+ backgroundColor: red[900],
+ [`& .${chipClasses.label}`]: {
+ color: red[200],
+ },
+ [`& .${chipClasses.icon}`]: {
+ color: red[300],
+ },
+ }),
+ },
+ },
+ {
+ props: { size: 'small' },
+ style: {
+ maxHeight: 20,
+ [`& .${chipClasses.label}`]: {
+ fontSize: theme.typography.caption.fontSize,
+ },
+ [`& .${svgIconClasses.root}`]: {
+ fontSize: theme.typography.caption.fontSize,
+ },
+ },
+ },
+ {
+ props: { size: 'medium' },
+ style: {
+ [`& .${chipClasses.label}`]: {
+ fontSize: theme.typography.caption.fontSize,
+ },
+ },
+ },
+ ],
+ }),
+ },
+ },
+ MuiTablePagination: {
+ styleOverrides: {
+ actions: {
+ display: 'flex',
+ gap: 8,
+ marginRight: 6,
+ [`& .${iconButtonClasses.root}`]: {
+ minWidth: 0,
+ width: 36,
+ height: 36,
+ },
+ },
+ },
+ },
+ MuiIcon: {
+ defaultProps: {
+ fontSize: 'small',
+ },
+ styleOverrides: {
+ root: {
+ variants: [
+ {
+ props: {
+ fontSize: 'small',
+ },
+ style: {
+ fontSize: '1rem',
+ },
+ },
+ ],
+ },
+ },
+ },
+};
diff --git a/examples/core/auth-nextjs-themed/theme/customizations/feedback.tsx b/examples/core/auth-nextjs-themed/theme/customizations/feedback.tsx
new file mode 100644
index 00000000000..19b5942825b
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme/customizations/feedback.tsx
@@ -0,0 +1,40 @@
+import { Theme, alpha, Components } from '@mui/material/styles';
+import { gray } from '../themePrimitives';
+
+export const feedbackCustomizations: Components = {
+ MuiAlert: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderRadius: 10,
+ color: theme.palette.text.primary,
+ border: `1px solid ${alpha(gray[300], 0.9)}`,
+ ...theme.applyStyles('dark', {
+ border: `1px solid ${alpha(gray[700], 0.9)}`,
+ }),
+ }),
+ },
+ },
+ MuiDialog: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ '& .MuiDialog-paper': {
+ borderRadius: '10px',
+ border: '1px solid',
+ borderColor: theme.palette.divider,
+ },
+ }),
+ },
+ },
+ MuiLinearProgress: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ height: 8,
+ borderRadius: 8,
+ backgroundColor: gray[200],
+ ...theme.applyStyles('dark', {
+ backgroundColor: gray[800],
+ }),
+ }),
+ },
+ },
+};
diff --git a/examples/core/auth-nextjs-themed/theme/customizations/index.ts b/examples/core/auth-nextjs-themed/theme/customizations/index.ts
new file mode 100644
index 00000000000..6c7c04eeb5b
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme/customizations/index.ts
@@ -0,0 +1,4 @@
+export { inputsCustomizations } from './inputs';
+export { dataDisplayCustomizations } from './dataDisplay';
+export { feedbackCustomizations } from './feedback';
+export { navigationCustomizations } from './navigation';
diff --git a/examples/core/auth-nextjs-themed/theme/customizations/inputs.tsx b/examples/core/auth-nextjs-themed/theme/customizations/inputs.tsx
new file mode 100644
index 00000000000..6a5f28d1f98
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme/customizations/inputs.tsx
@@ -0,0 +1,466 @@
+import * as React from 'react';
+import { alpha, Theme, Components } from '@mui/material/styles';
+import { outlinedInputClasses } from '@mui/material/OutlinedInput';
+import { svgIconClasses } from '@mui/material/SvgIcon';
+import { toggleButtonGroupClasses } from '@mui/material/ToggleButtonGroup';
+import { toggleButtonClasses } from '@mui/material/ToggleButton';
+import CheckBoxOutlineBlankRoundedIcon from '@mui/icons-material/CheckBoxOutlineBlankRounded';
+import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
+import RemoveRoundedIcon from '@mui/icons-material/RemoveRounded';
+import { gray, brand } from '../themePrimitives';
+
+export const inputsCustomizations: Components = {
+ MuiButtonBase: {
+ defaultProps: {
+ disableTouchRipple: true,
+ disableRipple: true,
+ },
+ styleOverrides: {
+ root: ({ theme }) => ({
+ boxSizing: 'border-box',
+ transition: 'all 100ms ease-in',
+ '&:focus-visible': {
+ outline: `3px solid ${alpha(theme.palette.primary.main, 0.5)}`,
+ outlineOffset: '2px',
+ },
+ }),
+ },
+ },
+ MuiButton: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ boxShadow: 'none',
+ borderRadius: theme.shape.borderRadius,
+ textTransform: 'none',
+ variants: [
+ {
+ props: {
+ size: 'small',
+ },
+ style: {
+ height: '2.25rem',
+ padding: '8px 12px',
+ },
+ },
+ {
+ props: {
+ size: 'medium',
+ },
+ style: {
+ height: '2.5rem', // 40px
+ },
+ },
+ {
+ props: {
+ color: 'primary',
+ variant: 'contained',
+ },
+ style: {
+ color: 'white',
+ backgroundColor: gray[900],
+ backgroundImage: `linear-gradient(to bottom, ${gray[700]}, ${gray[800]})`,
+ boxShadow: `inset 0 1px 0 ${gray[600]}, inset 0 -1px 0 1px hsl(220, 0%, 0%)`,
+ border: `1px solid ${gray[700]}`,
+ '&:hover': {
+ backgroundImage: 'none',
+ backgroundColor: gray[700],
+ boxShadow: 'none',
+ },
+ '&:active': {
+ backgroundColor: gray[800],
+ },
+ ...theme.applyStyles('dark', {
+ color: 'black',
+ backgroundColor: gray[50],
+ backgroundImage: `linear-gradient(to bottom, ${gray[100]}, ${gray[50]})`,
+ boxShadow: 'inset 0 -1px 0 hsl(220, 30%, 80%)',
+ border: `1px solid ${gray[50]}`,
+ '&:hover': {
+ backgroundImage: 'none',
+ backgroundColor: gray[300],
+ boxShadow: 'none',
+ },
+ '&:active': {
+ backgroundColor: gray[400],
+ },
+ }),
+ },
+ },
+ {
+ props: {
+ color: 'secondary',
+ variant: 'contained',
+ },
+ style: {
+ color: 'white',
+ backgroundColor: brand[300],
+ backgroundImage: `linear-gradient(to bottom, ${alpha(brand[400], 0.8)}, ${brand[500]})`,
+ boxShadow: `inset 0 2px 0 ${alpha(brand[200], 0.2)}, inset 0 -2px 0 ${alpha(brand[700], 0.4)}`,
+ border: `1px solid ${brand[500]}`,
+ '&:hover': {
+ backgroundColor: brand[700],
+ boxShadow: 'none',
+ },
+ '&:active': {
+ backgroundColor: brand[700],
+ backgroundImage: 'none',
+ },
+ },
+ },
+ {
+ props: {
+ variant: 'outlined',
+ },
+ style: {
+ color: theme.palette.text.primary,
+ border: '1px solid',
+ borderColor: gray[200],
+ backgroundColor: alpha(gray[50], 0.3),
+ '&:hover': {
+ backgroundColor: gray[100],
+ borderColor: gray[300],
+ },
+ '&:active': {
+ backgroundColor: gray[200],
+ },
+ ...theme.applyStyles('dark', {
+ backgroundColor: gray[800],
+ borderColor: gray[700],
+
+ '&:hover': {
+ backgroundColor: gray[900],
+ borderColor: gray[600],
+ },
+ '&:active': {
+ backgroundColor: gray[900],
+ },
+ }),
+ },
+ },
+ {
+ props: {
+ color: 'secondary',
+ variant: 'outlined',
+ },
+ style: {
+ color: brand[700],
+ border: '1px solid',
+ borderColor: brand[200],
+ backgroundColor: brand[50],
+ '&:hover': {
+ backgroundColor: brand[100],
+ borderColor: brand[400],
+ },
+ '&:active': {
+ backgroundColor: alpha(brand[200], 0.7),
+ },
+ ...theme.applyStyles('dark', {
+ color: brand[50],
+ border: '1px solid',
+ borderColor: brand[900],
+ backgroundColor: alpha(brand[900], 0.3),
+ '&:hover': {
+ borderColor: brand[700],
+ backgroundColor: alpha(brand[900], 0.6),
+ },
+ '&:active': {
+ backgroundColor: alpha(brand[900], 0.5),
+ },
+ }),
+ },
+ },
+ {
+ props: {
+ variant: 'text',
+ },
+ style: {
+ color: gray[600],
+ '&:hover': {
+ backgroundColor: gray[100],
+ },
+ '&:active': {
+ backgroundColor: gray[200],
+ },
+ ...theme.applyStyles('dark', {
+ color: gray[50],
+ '&:hover': {
+ backgroundColor: gray[700],
+ },
+ '&:active': {
+ backgroundColor: alpha(gray[700], 0.7),
+ },
+ }),
+ },
+ },
+ {
+ props: {
+ color: 'secondary',
+ variant: 'text',
+ },
+ style: {
+ color: brand[700],
+ '&:hover': {
+ backgroundColor: alpha(brand[100], 0.5),
+ },
+ '&:active': {
+ backgroundColor: alpha(brand[200], 0.7),
+ },
+ ...theme.applyStyles('dark', {
+ color: brand[100],
+ '&:hover': {
+ backgroundColor: alpha(brand[900], 0.5),
+ },
+ '&:active': {
+ backgroundColor: alpha(brand[900], 0.3),
+ },
+ }),
+ },
+ },
+ ],
+ }),
+ },
+ },
+ // @ts-ignore TODO: MuiLoadingButton is not present in the default `theme`
+ MuiLoadingButton: {
+ styleOverrides: {
+ // @ts-ignore
+ root: ({ theme }) => ({
+ '& .MuiLoadingButton-loadingIndicator': {
+ color: gray[400],
+ ...theme.applyStyles('dark', {
+ color: gray[600],
+ }),
+ },
+ }),
+ },
+ },
+ MuiIconButton: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ boxShadow: 'none',
+ borderRadius: theme.shape.borderRadius,
+ textTransform: 'none',
+ fontWeight: theme.typography.fontWeightMedium,
+ letterSpacing: 0,
+ color: theme.palette.text.primary,
+ border: '1px solid ',
+ borderColor: gray[200],
+ backgroundColor: alpha(gray[50], 0.3),
+ '&:hover': {
+ backgroundColor: gray[100],
+ borderColor: gray[300],
+ },
+ '&:active': {
+ backgroundColor: gray[200],
+ },
+ ...theme.applyStyles('dark', {
+ backgroundColor: gray[800],
+ borderColor: gray[700],
+ '&:hover': {
+ backgroundColor: gray[900],
+ borderColor: gray[600],
+ },
+ '&:active': {
+ backgroundColor: gray[900],
+ },
+ }),
+ variants: [
+ {
+ props: {
+ size: 'small',
+ },
+ style: {
+ width: '2.25rem',
+ height: '2.25rem',
+ padding: '0.25rem',
+ [`& .${svgIconClasses.root}`]: { fontSize: '1rem' },
+ },
+ },
+ {
+ props: {
+ size: 'medium',
+ },
+ style: {
+ width: '2.5rem',
+ height: '2.5rem',
+ },
+ },
+ ],
+ }),
+ },
+ },
+ MuiToggleButtonGroup: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderRadius: '10px',
+ boxShadow: `0 4px 16px ${alpha(gray[400], 0.2)}`,
+ [`& .${toggleButtonGroupClasses.selected}`]: {
+ color: brand[500],
+ },
+ ...theme.applyStyles('dark', {
+ [`& .${toggleButtonGroupClasses.selected}`]: {
+ color: '#fff',
+ },
+ boxShadow: `0 4px 16px ${alpha(brand[700], 0.5)}`,
+ }),
+ }),
+ },
+ },
+ MuiToggleButton: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ padding: '12px 16px',
+ textTransform: 'none',
+ borderRadius: '10px',
+ fontWeight: 500,
+ ...theme.applyStyles('dark', {
+ color: gray[400],
+ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.5)',
+ [`&.${toggleButtonClasses.selected}`]: {
+ color: brand[300],
+ },
+ }),
+ }),
+ },
+ },
+ MuiCheckbox: {
+ defaultProps: {
+ disableRipple: true,
+ icon: ,
+ checkedIcon: ,
+ indeterminateIcon: ,
+ },
+ styleOverrides: {
+ root: ({ theme }) => ({
+ margin: 10,
+ height: 16,
+ width: 16,
+ borderRadius: 5,
+ border: '1px solid ',
+ borderColor: alpha(gray[300], 0.8),
+ boxShadow: '0 0 0 1.5px hsla(210, 0%, 0%, 0.04) inset',
+ backgroundColor: alpha(gray[100], 0.4),
+ transition: 'border-color, background-color, 120ms ease-in',
+ '&:hover': {
+ borderColor: brand[300],
+ },
+ '&.Mui-focusVisible': {
+ outline: `3px solid ${alpha(brand[500], 0.5)}`,
+ outlineOffset: '2px',
+ borderColor: brand[400],
+ },
+ '&.Mui-checked': {
+ color: 'white',
+ backgroundColor: brand[500],
+ borderColor: brand[500],
+ boxShadow: `none`,
+ '&:hover': {
+ backgroundColor: brand[600],
+ },
+ },
+ ...theme.applyStyles('dark', {
+ borderColor: alpha(gray[700], 0.8),
+ boxShadow: '0 0 0 1.5px hsl(210, 0%, 0%) inset',
+ backgroundColor: alpha(gray[900], 0.8),
+ '&:hover': {
+ borderColor: brand[300],
+ },
+ '&.Mui-focusVisible': {
+ borderColor: brand[400],
+ outline: `3px solid ${alpha(brand[500], 0.5)}`,
+ outlineOffset: '2px',
+ },
+ }),
+ }),
+ },
+ },
+ MuiInputBase: {
+ styleOverrides: {
+ root: {
+ border: 'none',
+ },
+ input: {
+ '&::placeholder': {
+ opacity: 0.7,
+ color: gray[500],
+ },
+ },
+ },
+ },
+ MuiInputLabel: {
+ styleOverrides: {
+ root: {
+ transform: 'translate(4px, -11px) scale(0.75)',
+ [`&.${outlinedInputClasses.focused}`]: {
+ transform: 'translate(4px, -12px) scale(0.75)',
+ },
+ },
+ },
+ },
+ MuiOutlinedInput: {
+ styleOverrides: {
+ input: {
+ padding: 0,
+ },
+ root: ({ theme }) => ({
+ padding: '8px 12px',
+ color: theme.palette.text.primary,
+ borderRadius: theme.shape.borderRadius,
+ border: `1px solid ${theme.palette.divider}`,
+ backgroundColor: theme.palette.background.default,
+ transition: 'border 120ms ease-in',
+ '&:hover': {
+ borderColor: gray[400],
+ },
+ [`&.${outlinedInputClasses.focused}`]: {
+ outline: `2px solid ${alpha(brand[500], 0.5)}`,
+ borderColor: brand[400],
+ },
+ ...theme.applyStyles('dark', {
+ '&:hover': {
+ borderColor: gray[500],
+ },
+ }),
+ variants: [
+ {
+ props: {
+ size: 'small',
+ },
+ style: {
+ height: '2.25rem',
+ },
+ },
+ {
+ props: {
+ size: 'medium',
+ },
+ style: {
+ height: '2.5rem',
+ },
+ },
+ ],
+ }),
+ notchedOutline: {
+ border: 'none',
+ },
+ },
+ },
+ MuiInputAdornment: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ color: theme.palette.grey[500],
+ ...theme.applyStyles('dark', {
+ color: theme.palette.grey[400],
+ }),
+ }),
+ },
+ },
+ MuiFormLabel: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ typography: theme.typography.caption,
+ marginBottom: 8,
+ }),
+ },
+ },
+};
diff --git a/examples/core/auth-nextjs-themed/theme/customizations/navigation.tsx b/examples/core/auth-nextjs-themed/theme/customizations/navigation.tsx
new file mode 100644
index 00000000000..ab3f20e80ee
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme/customizations/navigation.tsx
@@ -0,0 +1,278 @@
+import * as React from 'react';
+import { Theme, alpha, Components } from '@mui/material/styles';
+import { SvgIconProps } from '@mui/material/SvgIcon';
+import { buttonBaseClasses } from '@mui/material/ButtonBase';
+import { dividerClasses } from '@mui/material/Divider';
+import { menuItemClasses } from '@mui/material/MenuItem';
+import { selectClasses } from '@mui/material/Select';
+import { tabClasses } from '@mui/material/Tab';
+import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
+import { gray, brand } from '../themePrimitives';
+
+export const navigationCustomizations: Components = {
+ MuiMenuItem: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderRadius: theme.shape.borderRadius,
+ padding: '6px 8px',
+ [`&.${menuItemClasses.focusVisible}`]: {
+ backgroundColor: 'transparent',
+ },
+ [`&.${menuItemClasses.selected}`]: {
+ [`&.${menuItemClasses.focusVisible}`]: {
+ backgroundColor: alpha(theme.palette.action.selected, 0.3),
+ },
+ },
+ }),
+ },
+ },
+ MuiMenu: {
+ styleOverrides: {
+ list: {
+ gap: '0px',
+ [`&.${dividerClasses.root}`]: {
+ margin: '0 -8px',
+ },
+ },
+ paper: ({ theme }) => ({
+ marginTop: '4px',
+ borderRadius: theme.shape.borderRadius,
+ border: `1px solid ${theme.palette.divider}`,
+ backgroundImage: 'none',
+ background: 'hsl(0, 0%, 100%)',
+ boxShadow:
+ 'hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px',
+ [`& .${buttonBaseClasses.root}`]: {
+ '&.Mui-selected': {
+ backgroundColor: alpha(theme.palette.action.selected, 0.3),
+ },
+ },
+ ...theme.applyStyles('dark', {
+ background: gray[900],
+ boxShadow:
+ 'hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px',
+ }),
+ }),
+ },
+ },
+ MuiSelect: {
+ defaultProps: {
+ IconComponent: React.forwardRef((props, ref) => (
+
+ )),
+ },
+ styleOverrides: {
+ root: ({ theme }) => ({
+ borderRadius: theme.shape.borderRadius,
+ border: '1px solid',
+ borderColor: gray[200],
+ backgroundColor: theme.palette.background.paper,
+ boxShadow: `inset 0 1px 0 1px hsla(220, 0%, 100%, 0.6), inset 0 -1px 0 1px hsla(220, 35%, 90%, 0.5)`,
+ '&:hover': {
+ borderColor: gray[300],
+ backgroundColor: theme.palette.background.paper,
+ boxShadow: 'none',
+ },
+ [`&.${selectClasses.focused}`]: {
+ outlineOffset: 0,
+ borderColor: gray[400],
+ },
+ '&:before, &:after': {
+ display: 'none',
+ },
+
+ ...theme.applyStyles('dark', {
+ borderRadius: theme.shape.borderRadius,
+ borderColor: gray[700],
+ backgroundColor: theme.palette.background.paper,
+ boxShadow: `inset 0 1px 0 1px ${alpha(gray[700], 0.15)}, inset 0 -1px 0 1px hsla(220, 0%, 0%, 0.7)`,
+ '&:hover': {
+ borderColor: alpha(gray[700], 0.7),
+ backgroundColor: theme.palette.background.paper,
+ boxShadow: 'none',
+ },
+ [`&.${selectClasses.focused}`]: {
+ outlineOffset: 0,
+ borderColor: gray[900],
+ },
+ '&:before, &:after': {
+ display: 'none',
+ },
+ }),
+ }),
+ select: ({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ ...theme.applyStyles('dark', {
+ display: 'flex',
+ alignItems: 'center',
+ '&:focus-visible': {
+ backgroundColor: gray[900],
+ },
+ }),
+ }),
+ },
+ },
+ MuiLink: {
+ defaultProps: {
+ underline: 'none',
+ },
+ styleOverrides: {
+ root: ({ theme }) => ({
+ color: theme.palette.text.primary,
+ fontWeight: 500,
+ position: 'relative',
+ textDecoration: 'none',
+ width: 'fit-content',
+ '&::before': {
+ content: '""',
+ position: 'absolute',
+ width: '100%',
+ height: '1px',
+ bottom: 0,
+ left: 0,
+ backgroundColor: theme.palette.text.secondary,
+ opacity: 0.3,
+ transition: 'width 0.3s ease, opacity 0.3s ease',
+ },
+ '&:hover::before': {
+ width: 0,
+ },
+ '&:focus-visible': {
+ outline: `3px solid ${alpha(brand[500], 0.5)}`,
+ outlineOffset: '4px',
+ borderRadius: '2px',
+ },
+ }),
+ },
+ },
+ MuiDrawer: {
+ styleOverrides: {
+ paper: ({ theme }) => ({
+ backgroundColor: theme.palette.background.default,
+ }),
+ },
+ },
+ MuiPaginationItem: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ '&.Mui-selected': {
+ color: 'white',
+ backgroundColor: theme.palette.grey[900],
+ },
+ ...theme.applyStyles('dark', {
+ '&.Mui-selected': {
+ color: 'black',
+ backgroundColor: theme.palette.grey[50],
+ },
+ }),
+ }),
+ },
+ },
+ MuiTabs: {
+ styleOverrides: {
+ root: { minHeight: 'fit-content' },
+ indicator: ({ theme }) => ({
+ backgroundColor: theme.palette.grey[800],
+ ...theme.applyStyles('dark', {
+ backgroundColor: theme.palette.grey[200],
+ }),
+ }),
+ },
+ },
+ MuiTab: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ padding: '6px 8px',
+ marginBottom: '8px',
+ textTransform: 'none',
+ minWidth: 'fit-content',
+ minHeight: 'fit-content',
+ color: theme.palette.text.secondary,
+ borderRadius: theme.shape.borderRadius,
+ border: '1px solid',
+ borderColor: 'transparent',
+ ':hover': {
+ color: theme.palette.text.primary,
+ backgroundColor: gray[100],
+ borderColor: gray[200],
+ },
+ [`&.${tabClasses.selected}`]: {
+ color: gray[900],
+ },
+ ...theme.applyStyles('dark', {
+ ':hover': {
+ color: theme.palette.text.primary,
+ backgroundColor: gray[800],
+ borderColor: gray[700],
+ },
+ [`&.${tabClasses.selected}`]: {
+ color: '#fff',
+ },
+ }),
+ }),
+ },
+ },
+ MuiStepConnector: {
+ styleOverrides: {
+ line: ({ theme }) => ({
+ borderTop: '1px solid',
+ borderColor: theme.palette.divider,
+ flex: 1,
+ borderRadius: '99px',
+ }),
+ },
+ },
+ MuiStepIcon: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ color: 'transparent',
+ border: `1px solid ${gray[400]}`,
+ width: 12,
+ height: 12,
+ borderRadius: '50%',
+ '& text': {
+ display: 'none',
+ },
+ '&.Mui-active': {
+ border: 'none',
+ color: theme.palette.primary.main,
+ },
+ '&.Mui-completed': {
+ border: 'none',
+ color: theme.palette.success.main,
+ },
+ ...theme.applyStyles('dark', {
+ border: `1px solid ${gray[700]}`,
+ '&.Mui-active': {
+ border: 'none',
+ color: theme.palette.primary.light,
+ },
+ '&.Mui-completed': {
+ border: 'none',
+ color: theme.palette.success.light,
+ },
+ }),
+ variants: [
+ {
+ props: { completed: true },
+ style: {
+ width: 12,
+ height: 12,
+ },
+ },
+ ],
+ }),
+ },
+ },
+ MuiStepLabel: {
+ styleOverrides: {
+ label: ({ theme }) => ({
+ '&.Mui-completed': {
+ opacity: 0.6,
+ ...theme.applyStyles('dark', { opacity: 0.5 }),
+ },
+ }),
+ },
+ },
+};
diff --git a/examples/core/auth-nextjs-themed/theme/getMPTheme.tsx b/examples/core/auth-nextjs-themed/theme/getMPTheme.tsx
new file mode 100644
index 00000000000..1c3970aa501
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme/getMPTheme.tsx
@@ -0,0 +1,21 @@
+import type {} from '@mui/material/themeCssVarsAugmentation';
+import { ThemeOptions, PaletteMode } from '@mui/material/styles';
+import { getDesignTokens } from './themePrimitives';
+import {
+ inputsCustomizations,
+ dataDisplayCustomizations,
+ feedbackCustomizations,
+ navigationCustomizations,
+} from './customizations';
+
+export default function getMPTheme(mode: PaletteMode): ThemeOptions {
+ return {
+ ...getDesignTokens(mode),
+ components: {
+ ...inputsCustomizations,
+ ...dataDisplayCustomizations,
+ ...feedbackCustomizations,
+ ...navigationCustomizations,
+ },
+ };
+}
diff --git a/examples/core/auth-nextjs-themed/theme/themePrimitives.ts b/examples/core/auth-nextjs-themed/theme/themePrimitives.ts
new file mode 100644
index 00000000000..bccf187d5f5
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/theme/themePrimitives.ts
@@ -0,0 +1,248 @@
+import { createTheme, alpha, Shadows } from '@mui/material/styles';
+import { PaletteMode } from '@mui/material';
+import { Inter } from 'next/font/google';
+
+const inter = Inter({ subsets: ['latin'], display: 'swap' });
+
+declare module '@mui/material/Paper' {
+ interface PaperPropsVariantOverrides {
+ highlighted: true;
+ }
+}
+declare module '@mui/material/styles/createPalette' {
+ interface ColorRange {
+ 50: string;
+ 100: string;
+ 200: string;
+ 300: string;
+ 400: string;
+ 500: string;
+ 600: string;
+ 700: string;
+ 800: string;
+ 900: string;
+ }
+
+ interface PaletteColor extends ColorRange {}
+}
+
+const defaultTheme = createTheme();
+
+const customShadows: Shadows = [...defaultTheme.shadows];
+
+export const brand = {
+ 50: 'hsl(210, 100%, 95%)',
+ 100: 'hsl(210, 100%, 92%)',
+ 200: 'hsl(210, 100%, 80%)',
+ 300: 'hsl(210, 100%, 65%)',
+ 400: 'hsl(210, 98%, 48%)',
+ 500: 'hsl(210, 98%, 42%)',
+ 600: 'hsl(210, 98%, 55%)',
+ 700: 'hsl(210, 100%, 35%)',
+ 800: 'hsl(210, 100%, 16%)',
+ 900: 'hsl(210, 100%, 21%)',
+};
+
+export const gray = {
+ 50: 'hsl(220, 35%, 97%)',
+ 100: 'hsl(220, 30%, 94%)',
+ 200: 'hsl(220, 20%, 88%)',
+ 300: 'hsl(220, 20%, 80%)',
+ 400: 'hsl(220, 20%, 65%)',
+ 500: 'hsl(220, 20%, 42%)',
+ 600: 'hsl(220, 20%, 35%)',
+ 700: 'hsl(220, 20%, 25%)',
+ 800: 'hsl(220, 30%, 6%)',
+ 900: 'hsl(220, 35%, 3%)',
+};
+
+export const green = {
+ 50: 'hsl(120, 80%, 98%)',
+ 100: 'hsl(120, 75%, 94%)',
+ 200: 'hsl(120, 75%, 87%)',
+ 300: 'hsl(120, 61%, 77%)',
+ 400: 'hsl(120, 44%, 53%)',
+ 500: 'hsl(120, 59%, 30%)',
+ 600: 'hsl(120, 70%, 25%)',
+ 700: 'hsl(120, 75%, 16%)',
+ 800: 'hsl(120, 84%, 10%)',
+ 900: 'hsl(120, 87%, 6%)',
+};
+
+export const orange = {
+ 50: 'hsl(45, 100%, 97%)',
+ 100: 'hsl(45, 92%, 90%)',
+ 200: 'hsl(45, 94%, 80%)',
+ 300: 'hsl(45, 90%, 65%)',
+ 400: 'hsl(45, 90%, 40%)',
+ 500: 'hsl(45, 90%, 35%)',
+ 600: 'hsl(45, 91%, 25%)',
+ 700: 'hsl(45, 94%, 20%)',
+ 800: 'hsl(45, 95%, 16%)',
+ 900: 'hsl(45, 93%, 12%)',
+};
+
+export const red = {
+ 50: 'hsl(0, 100%, 97%)',
+ 100: 'hsl(0, 92%, 90%)',
+ 200: 'hsl(0, 94%, 80%)',
+ 300: 'hsl(0, 90%, 65%)',
+ 400: 'hsl(0, 90%, 40%)',
+ 500: 'hsl(0, 90%, 30%)',
+ 600: 'hsl(0, 91%, 25%)',
+ 700: 'hsl(0, 94%, 18%)',
+ 800: 'hsl(0, 95%, 12%)',
+ 900: 'hsl(0, 93%, 6%)',
+};
+
+export const getDesignTokens = (mode: PaletteMode) => {
+ customShadows[1] =
+ mode === 'dark'
+ ? 'hsla(220, 30%, 5%, 0.7) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.8) 0px 8px 16px -5px'
+ : 'hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px';
+
+ customShadows[4] =
+ '0px 5px 15px rgba(9, 11, 17, 0.05), 0px 15px 35px -5px rgba(19, 23, 32, 0.05)';
+
+ return {
+ palette: {
+ mode,
+ primary: {
+ light: brand[200],
+ main: brand[400],
+ dark: brand[700],
+ contrastText: brand[50],
+ ...(mode === 'dark' && {
+ contrastText: brand[50],
+ light: brand[300],
+ main: brand[400],
+ dark: brand[700],
+ }),
+ },
+ info: {
+ light: brand[100],
+ main: brand[300],
+ dark: brand[600],
+ contrastText: gray[50],
+ ...(mode === 'dark' && {
+ contrastText: brand[300],
+ light: brand[500],
+ main: brand[700],
+ dark: brand[900],
+ }),
+ },
+ warning: {
+ light: orange[300],
+ main: orange[400],
+ dark: orange[800],
+ ...(mode === 'dark' && {
+ light: orange[400],
+ main: orange[500],
+ dark: orange[700],
+ }),
+ },
+ error: {
+ light: red[300],
+ main: red[400],
+ dark: red[800],
+ ...(mode === 'dark' && {
+ light: red[400],
+ main: red[500],
+ dark: red[700],
+ }),
+ },
+ success: {
+ light: green[300],
+ main: green[400],
+ dark: green[800],
+ ...(mode === 'dark' && {
+ light: green[400],
+ main: green[500],
+ dark: green[700],
+ }),
+ },
+ grey: {
+ ...gray,
+ },
+ divider: mode === 'dark' ? alpha(gray[700], 0.6) : alpha(gray[300], 0.4),
+ background: {
+ default: 'hsl(0, 0%, 99%)',
+ paper: 'hsl(220, 35%, 97%)',
+ ...(mode === 'dark' && {
+ default: gray[900],
+ paper: 'hsl(220, 30%, 7%)',
+ }),
+ },
+ text: {
+ primary: gray[800],
+ secondary: gray[600],
+ warning: orange[400],
+ ...(mode === 'dark' && {
+ primary: 'hsl(0, 0%, 100%)',
+ secondary: gray[400],
+ }),
+ },
+ action: {
+ hover: alpha(gray[200], 0.2),
+ selected: `${alpha(gray[200], 0.3)}`,
+ ...(mode === 'dark' && {
+ hover: alpha(gray[600], 0.2),
+ selected: alpha(gray[600], 0.3),
+ }),
+ },
+ },
+ typography: {
+ fontFamily: inter.style.fontFamily,
+ h1: {
+ fontSize: defaultTheme.typography.pxToRem(48),
+ fontWeight: 600,
+ lineHeight: 1.2,
+ letterSpacing: -0.5,
+ },
+ h2: {
+ fontSize: defaultTheme.typography.pxToRem(36),
+ fontWeight: 600,
+ lineHeight: 1.2,
+ },
+ h3: {
+ fontSize: defaultTheme.typography.pxToRem(30),
+ lineHeight: 1.2,
+ },
+ h4: {
+ fontSize: defaultTheme.typography.pxToRem(24),
+ fontWeight: 600,
+ lineHeight: 1.5,
+ },
+ h5: {
+ fontSize: defaultTheme.typography.pxToRem(20),
+ fontWeight: 600,
+ },
+ h6: {
+ fontSize: defaultTheme.typography.pxToRem(18),
+ fontWeight: 600,
+ },
+ subtitle1: {
+ fontSize: defaultTheme.typography.pxToRem(18),
+ },
+ subtitle2: {
+ fontSize: defaultTheme.typography.pxToRem(14),
+ fontWeight: 500,
+ },
+ body1: {
+ fontSize: defaultTheme.typography.pxToRem(14),
+ },
+ body2: {
+ fontSize: defaultTheme.typography.pxToRem(14),
+ fontWeight: 400,
+ },
+ caption: {
+ fontSize: defaultTheme.typography.pxToRem(12),
+ fontWeight: 400,
+ },
+ },
+ shape: {
+ borderRadius: 8,
+ },
+ shadows: customShadows,
+ };
+};
diff --git a/examples/core/auth-nextjs-themed/tsconfig.json b/examples/core/auth-nextjs-themed/tsconfig.json
new file mode 100644
index 00000000000..5a1e9c80490
--- /dev/null
+++ b/examples/core/auth-nextjs-themed/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/examples/core/auth-nextjs/.eslintrc.json b/examples/core/auth-nextjs/.eslintrc.json
new file mode 100644
index 00000000000..bffb357a712
--- /dev/null
+++ b/examples/core/auth-nextjs/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/examples/core-auth-nextjs/.gitignore b/examples/core/auth-nextjs/.gitignore
similarity index 100%
rename from examples/core-auth-nextjs/.gitignore
rename to examples/core/auth-nextjs/.gitignore
diff --git a/examples/core-auth-nextjs/README.md b/examples/core/auth-nextjs/README.md
similarity index 87%
rename from examples/core-auth-nextjs/README.md
rename to examples/core/auth-nextjs/README.md
index be121f349a6..71b291d1002 100644
--- a/examples/core-auth-nextjs/README.md
+++ b/examples/core/auth-nextjs/README.md
@@ -34,12 +34,24 @@ GITHUB_CLIENT_SECRET=
```bash
npx auth secret
+# or
+pnpm dlx create-toolpad-app@latest --example auth-nextjs
```
### GitHub configuration
To get the required credentials from GitHub, we need to create an application in their developer settings. Read this [detailed guide on Auth.js](https://authjs.dev/guides/configuring-github) on how to obtain those.
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example auth-nextjs
+```
+
+and follow the instructions in the terminal.
+
## Learn More
To learn more about Next.js, take a look at the following resources:
diff --git a/examples/core-auth-nextjs/next-env.d.ts b/examples/core/auth-nextjs/next-env.d.ts
similarity index 100%
rename from examples/core-auth-nextjs/next-env.d.ts
rename to examples/core/auth-nextjs/next-env.d.ts
diff --git a/examples/core/auth-nextjs/next.config.mjs b/examples/core/auth-nextjs/next.config.mjs
new file mode 100644
index 00000000000..4678774e6d6
--- /dev/null
+++ b/examples/core/auth-nextjs/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/examples/core-auth-nextjs/package.json b/examples/core/auth-nextjs/package.json
similarity index 63%
rename from examples/core-auth-nextjs/package.json
rename to examples/core/auth-nextjs/package.json
index d1cc451e6df..4fe9adb516f 100644
--- a/examples/core-auth-nextjs/package.json
+++ b/examples/core/auth-nextjs/package.json
@@ -12,17 +12,17 @@
"@mui/material": "^6",
"@mui/material-nextjs": "^6",
"@toolpad/core": "latest",
- "next": "^14",
- "next-auth": "5.0.0-beta.20",
- "react": "^18",
- "react-dom": "^18"
+ "next": "^15",
+ "next-auth": "5.0.0-beta.25",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
},
"devDependencies": {
"typescript": "^5",
- "@types/node": "^20.16.11",
- "@types/react": "^18",
- "@types/react-dom": "^18",
+ "@types/node": "^20.17.12",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
"eslint": "^8",
- "eslint-config-next": "^14"
+ "eslint-config-next": "^15"
}
}
diff --git a/examples/core/auth-nextjs/src/app/(dashboard)/layout.tsx b/examples/core/auth-nextjs/src/app/(dashboard)/layout.tsx
new file mode 100644
index 00000000000..5dee163b753
--- /dev/null
+++ b/examples/core/auth-nextjs/src/app/(dashboard)/layout.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+
+export default async function DashboardPagesLayout(props: { children: React.ReactNode }) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/examples/core-auth-nextjs/src/app/(dashboard)/orders/page.tsx b/examples/core/auth-nextjs/src/app/(dashboard)/orders/page.tsx
similarity index 100%
rename from examples/core-auth-nextjs/src/app/(dashboard)/orders/page.tsx
rename to examples/core/auth-nextjs/src/app/(dashboard)/orders/page.tsx
diff --git a/examples/core-auth-nextjs/src/app/(dashboard)/page.tsx b/examples/core/auth-nextjs/src/app/(dashboard)/page.tsx
similarity index 100%
rename from examples/core-auth-nextjs/src/app/(dashboard)/page.tsx
rename to examples/core/auth-nextjs/src/app/(dashboard)/page.tsx
diff --git a/examples/core/auth-nextjs/src/app/api/auth/[...nextauth]/route.ts b/examples/core/auth-nextjs/src/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 00000000000..ca225652075
--- /dev/null
+++ b/examples/core/auth-nextjs/src/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,3 @@
+import { handlers } from '../../../../auth';
+
+export const { GET, POST } = handlers;
diff --git a/examples/core-auth-nextjs/src/app/auth/signin/page.tsx b/examples/core/auth-nextjs/src/app/auth/signin/page.tsx
similarity index 100%
rename from examples/core-auth-nextjs/src/app/auth/signin/page.tsx
rename to examples/core/auth-nextjs/src/app/auth/signin/page.tsx
diff --git a/examples/core-auth-nextjs/src/app/layout.tsx b/examples/core/auth-nextjs/src/app/layout.tsx
similarity index 87%
rename from examples/core-auth-nextjs/src/app/layout.tsx
rename to examples/core/auth-nextjs/src/app/layout.tsx
index 610583ab933..ed6df4dbcc3 100644
--- a/examples/core-auth-nextjs/src/app/layout.tsx
+++ b/examples/core/auth-nextjs/src/app/layout.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import { AppProvider } from '@toolpad/core/nextjs';
-import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
+import { NextAppProvider } from '@toolpad/core/nextjs';
+import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import type { Navigation } from '@toolpad/core/AppProvider';
@@ -40,14 +40,14 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
-
{props.children}
-
+
diff --git a/examples/core-auth-nextjs/src/app/public/layout.tsx b/examples/core/auth-nextjs/src/app/public/layout.tsx
similarity index 100%
rename from examples/core-auth-nextjs/src/app/public/layout.tsx
rename to examples/core/auth-nextjs/src/app/public/layout.tsx
diff --git a/examples/core/auth-nextjs/src/app/public/page.tsx b/examples/core/auth-nextjs/src/app/public/page.tsx
new file mode 100644
index 00000000000..9eb1ae52478
--- /dev/null
+++ b/examples/core/auth-nextjs/src/app/public/page.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default async function HomePage() {
+ return Public page ;
+}
diff --git a/examples/core-auth-nextjs/src/auth.ts b/examples/core/auth-nextjs/src/auth.ts
similarity index 74%
rename from examples/core-auth-nextjs/src/auth.ts
rename to examples/core/auth-nextjs/src/auth.ts
index 74dd44ac883..c9874f0a52e 100644
--- a/examples/core-auth-nextjs/src/auth.ts
+++ b/examples/core/auth-nextjs/src/auth.ts
@@ -26,26 +26,11 @@ const providers: Provider[] = [
}),
];
-const missingVars: string[] = [];
-
if (!process.env.GITHUB_CLIENT_ID) {
- missingVars.push('GITHUB_CLIENT_ID');
+ console.warn('Missing environment variable "GITHUB_CLIENT_ID"');
}
if (!process.env.GITHUB_CLIENT_SECRET) {
- missingVars.push('GITHUB_CLIENT_SECRET');
-}
-
-if (missingVars.length > 0) {
- const baseMessage =
- 'Authentication is configured but the following environment variables are missing:';
-
- if (process.env.NODE_ENV === 'production') {
- throw new Error(`error - ${baseMessage} ${missingVars.join(', ')}`);
- } else {
- console.warn(
- `\u001b[33mwarn\u001b[0m - ${baseMessage} \u001b[31m${missingVars.join(', ')}\u001b[0m`,
- );
- }
+ console.warn('Missing environment variable "GITHUB_CLIENT_SECRET"');
}
export const providerMap = providers.map((provider) => {
diff --git a/examples/core/auth-nextjs/src/middleware.ts b/examples/core/auth-nextjs/src/middleware.ts
new file mode 100644
index 00000000000..02c48f4db60
--- /dev/null
+++ b/examples/core/auth-nextjs/src/middleware.ts
@@ -0,0 +1,6 @@
+export { auth as middleware } from './auth';
+
+export const config = {
+ // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+};
diff --git a/examples/core/auth-nextjs/tsconfig.json b/examples/core/auth-nextjs/tsconfig.json
new file mode 100644
index 00000000000..bb5584ed1a4
--- /dev/null
+++ b/examples/core/auth-nextjs/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/examples/core-vite/.gitignore b/examples/core/auth-vite/.gitignore
similarity index 100%
rename from examples/core-vite/.gitignore
rename to examples/core/auth-vite/.gitignore
diff --git a/examples/core/auth-vite/README.md b/examples/core/auth-vite/README.md
new file mode 100644
index 00000000000..c02f7e78a33
--- /dev/null
+++ b/examples/core/auth-vite/README.md
@@ -0,0 +1,33 @@
+# Toolpad Core - Vite with React Router and mock authentication
+
+This example provides a minimal setup to get Toolpad Core working in Vite with HMR, as well as routing with React Router and a mock authentication setup.
+
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example auth-vite
+# or
+pnpm dlx create-toolpad-app@latest --example auth-vite
+```
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
+
+## The source
+
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/core/auth-vite/)
diff --git a/examples/core/auth-vite/index.html b/examples/core/auth-vite/index.html
new file mode 100644
index 00000000000..fc6eb70c9c8
--- /dev/null
+++ b/examples/core/auth-vite/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+ Toolpad Core Vite with Auth
+
+
+
+
+
+
diff --git a/examples/core/auth-vite/package.json b/examples/core/auth-vite/package.json
new file mode 100644
index 00000000000..312aef1fbfb
--- /dev/null
+++ b/examples/core/auth-vite/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "core-vite-auth",
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@emotion/react": "^11",
+ "@emotion/styled": "^11",
+ "@mui/icons-material": "^6",
+ "@mui/material": "^6",
+ "@toolpad/core": "latest",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-router": "^7"
+ },
+ "devDependencies": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^4.3.2",
+ "typescript": "^5",
+ "vite": "^5.4.8"
+ }
+}
diff --git a/examples/core-vite/public/vite.svg b/examples/core/auth-vite/public/vite.svg
similarity index 100%
rename from examples/core-vite/public/vite.svg
rename to examples/core/auth-vite/public/vite.svg
diff --git a/examples/core/auth-vite/src/App.tsx b/examples/core/auth-vite/src/App.tsx
new file mode 100644
index 00000000000..1a54541e4ff
--- /dev/null
+++ b/examples/core/auth-vite/src/App.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { ReactRouterAppProvider } from '@toolpad/core/react-router';
+import { Outlet, useNavigate } from 'react-router';
+import type { Navigation, Session } from '@toolpad/core/AppProvider';
+import { SessionContext } from './SessionContext';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+export default function App() {
+ const [session, setSession] = React.useState(null);
+ const navigate = useNavigate();
+
+ const signIn = React.useCallback(() => {
+ navigate('/sign-in');
+ }, [navigate]);
+
+ const signOut = React.useCallback(() => {
+ setSession(null);
+ navigate('/sign-in');
+ }, [navigate]);
+
+ const sessionContextValue = React.useMemo(() => ({ session, setSession }), [session, setSession]);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-vite/src/SessionContext.ts b/examples/core/auth-vite/src/SessionContext.ts
new file mode 100644
index 00000000000..6dff1dcae9a
--- /dev/null
+++ b/examples/core/auth-vite/src/SessionContext.ts
@@ -0,0 +1,16 @@
+import * as React from 'react';
+import type { Session } from '@toolpad/core';
+
+export interface SessionContextValue {
+ session: Session | null;
+ setSession: (session: Session | null) => void;
+}
+
+export const SessionContext = React.createContext({
+ session: {},
+ setSession: () => {},
+});
+
+export function useSession() {
+ return React.useContext(SessionContext);
+}
diff --git a/examples/core-vite/src/assets/.gitkeep b/examples/core/auth-vite/src/assets/.gitkeep
similarity index 100%
rename from examples/core-vite/src/assets/.gitkeep
rename to examples/core/auth-vite/src/assets/.gitkeep
diff --git a/examples/core/auth-vite/src/layouts/dashboard.tsx b/examples/core/auth-vite/src/layouts/dashboard.tsx
new file mode 100644
index 00000000000..03a99900aa0
--- /dev/null
+++ b/examples/core/auth-vite/src/layouts/dashboard.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import { Outlet, Navigate, useLocation } from 'react-router';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import { useSession } from '../SessionContext';
+
+export default function Layout() {
+ const { session } = useSession();
+ const location = useLocation();
+
+ if (!session) {
+ // Add the `callbackUrl` search parameter
+ const redirectTo = `/sign-in?callbackUrl=${encodeURIComponent(location.pathname)}`;
+
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/examples/core/auth-vite/src/main.tsx b/examples/core/auth-vite/src/main.tsx
new file mode 100644
index 00000000000..d5929bec598
--- /dev/null
+++ b/examples/core/auth-vite/src/main.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { createBrowserRouter, RouterProvider } from 'react-router';
+import App from './App';
+import Layout from './layouts/dashboard';
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+import SignInPage from './pages/signIn';
+
+const router = createBrowserRouter([
+ {
+ Component: App,
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '/',
+ Component: DashboardPage,
+ },
+ {
+ path: '/orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ {
+ path: '/sign-in',
+ Component: SignInPage,
+ },
+ ],
+ },
+]);
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/examples/core-vite/src/pages/index.tsx b/examples/core/auth-vite/src/pages/index.tsx
similarity index 100%
rename from examples/core-vite/src/pages/index.tsx
rename to examples/core/auth-vite/src/pages/index.tsx
diff --git a/examples/core-vite/src/pages/orders.tsx b/examples/core/auth-vite/src/pages/orders.tsx
similarity index 100%
rename from examples/core-vite/src/pages/orders.tsx
rename to examples/core/auth-vite/src/pages/orders.tsx
diff --git a/examples/core/auth-vite/src/pages/signIn.tsx b/examples/core/auth-vite/src/pages/signIn.tsx
new file mode 100644
index 00000000000..17008f42546
--- /dev/null
+++ b/examples/core/auth-vite/src/pages/signIn.tsx
@@ -0,0 +1,47 @@
+'use client';
+import * as React from 'react';
+import { SignInPage } from '@toolpad/core/SignInPage';
+import type { Session } from '@toolpad/core/AppProvider';
+import { useNavigate } from 'react-router';
+import { useSession } from '../SessionContext';
+
+const fakeAsyncGetSession = async (formData: any): Promise => {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ if (formData.get('password') === 'password') {
+ resolve({
+ user: {
+ name: 'Bharat Kashyap',
+ email: formData.get('email') || '',
+ image: 'https://avatars.githubusercontent.com/u/19550456',
+ },
+ });
+ }
+ reject(new Error('Incorrect credentials.'));
+ }, 1000);
+ });
+};
+
+export default function SignIn() {
+ const { setSession } = useSession();
+ const navigate = useNavigate();
+ return (
+ {
+ // Demo session
+ try {
+ const session = await fakeAsyncGetSession(formData);
+ if (session) {
+ setSession(session);
+ navigate(callbackUrl || '/', { replace: true });
+ return {};
+ }
+ } catch (error) {
+ return { error: error instanceof Error ? error.message : 'An error occurred' };
+ }
+ return {};
+ }}
+ />
+ );
+}
diff --git a/examples/core-vite/src/vite-env.d.ts b/examples/core/auth-vite/src/vite-env.d.ts
similarity index 100%
rename from examples/core-vite/src/vite-env.d.ts
rename to examples/core/auth-vite/src/vite-env.d.ts
diff --git a/examples/core/auth-vite/tsconfig.json b/examples/core/auth-vite/tsconfig.json
new file mode 100644
index 00000000000..251a83f8a97
--- /dev/null
+++ b/examples/core/auth-vite/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/examples/core-vite/vite.config.ts b/examples/core/auth-vite/vite.config.ts
similarity index 100%
rename from examples/core-vite/vite.config.ts
rename to examples/core/auth-vite/vite.config.ts
diff --git a/examples/core/firebase-vite/.gitignore b/examples/core/firebase-vite/.gitignore
new file mode 100644
index 00000000000..a547bf36d8d
--- /dev/null
+++ b/examples/core/firebase-vite/.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/examples/core/firebase-vite/README.md b/examples/core/firebase-vite/README.md
new file mode 100644
index 00000000000..676555594b9
--- /dev/null
+++ b/examples/core/firebase-vite/README.md
@@ -0,0 +1,46 @@
+# Toolpad Core - Vite with React Router and Firebase Auth
+
+This example provides a minimal setup to get Toolpad Core working in Vite with HMR, as well as routing with React Router and authentication with Firebase.
+
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example firebase-vite
+# or
+pnpm dlx create-toolpad-app@latest --example firebase-vite
+```
+
+## Setting up
+
+The project requires a `.env` with the following variables:
+
+```bash
+VITE_FIREBASE_API_KEY=
+VITE_FIREBASE_AUTH_DOMAIN=
+VITE_FIREBASE_PROJECT_ID=
+VITE_FIREBASE_STORAGE_BUCKET=
+VITE_FIREBASE_MESSAGE_SENDER_ID=
+VITE_FIREBASE_APP_ID=
+```
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
+
+## The source
+
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/core/vite/)
diff --git a/examples/core-vite/index.html b/examples/core/firebase-vite/index.html
similarity index 100%
rename from examples/core-vite/index.html
rename to examples/core/firebase-vite/index.html
diff --git a/examples/core/firebase-vite/package.json b/examples/core/firebase-vite/package.json
new file mode 100644
index 00000000000..5c86cf00fb9
--- /dev/null
+++ b/examples/core/firebase-vite/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "auth-vite-themed",
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "firebase": "^11",
+ "@emotion/react": "^11",
+ "@emotion/styled": "^11",
+ "@mui/icons-material": "^6",
+ "@mui/material": "^6",
+ "@toolpad/core": "latest",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-router": "^7"
+ },
+ "devDependencies": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "^4.3.2",
+ "typescript": "^5",
+ "vite": "^5.4.8"
+ }
+}
diff --git a/examples/core/firebase-vite/public/vite.svg b/examples/core/firebase-vite/public/vite.svg
new file mode 100644
index 00000000000..e7b8dfb1b2a
--- /dev/null
+++ b/examples/core/firebase-vite/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/core/firebase-vite/src/App.tsx b/examples/core/firebase-vite/src/App.tsx
new file mode 100644
index 00000000000..c3a9b09aec3
--- /dev/null
+++ b/examples/core/firebase-vite/src/App.tsx
@@ -0,0 +1,81 @@
+import * as React from 'react';
+import DashboardIcon from '@mui/icons-material/Dashboard';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { Outlet } from 'react-router';
+import type { User } from 'firebase/auth';
+import { ReactRouterAppProvider } from '@toolpad/core/react-router';
+import type { Navigation, Authentication } from '@toolpad/core/AppProvider';
+import { firebaseSignOut, signInWithGoogle, onAuthStateChanged } from './firebase/auth';
+import SessionContext, { type Session } from './SessionContext';
+
+const NAVIGATION: Navigation = [
+ {
+ kind: 'header',
+ title: 'Main items',
+ },
+ {
+ title: 'Dashboard',
+ icon: ,
+ },
+ {
+ segment: 'orders',
+ title: 'Orders',
+ icon: ,
+ },
+];
+
+const BRANDING = {
+ title: 'My Toolpad Core App',
+};
+
+const AUTHENTICATION: Authentication = {
+ signIn: signInWithGoogle,
+ signOut: firebaseSignOut,
+};
+
+export default function App() {
+ const [session, setSession] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+
+ const sessionContextValue = React.useMemo(
+ () => ({
+ session,
+ setSession,
+ loading,
+ }),
+ [session, loading],
+ );
+
+ React.useEffect(() => {
+ // Returns an `unsubscribe` function to be called during teardown
+ const unsubscribe = onAuthStateChanged((user: User | null) => {
+ if (user) {
+ setSession({
+ user: {
+ name: user.displayName || '',
+ email: user.email || '',
+ image: user.photoURL || '',
+ },
+ });
+ } else {
+ setSession(null);
+ }
+ setLoading(false);
+ });
+
+ return () => unsubscribe();
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/examples/core/firebase-vite/src/SessionContext.tsx b/examples/core/firebase-vite/src/SessionContext.tsx
new file mode 100644
index 00000000000..bf7f187abfa
--- /dev/null
+++ b/examples/core/firebase-vite/src/SessionContext.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+
+export interface Session {
+ user: {
+ name?: string;
+ email?: string;
+ image?: string;
+ };
+}
+
+interface SessionContextType {
+ session: Session | null;
+ setSession: (session: Session) => void;
+ loading: boolean;
+}
+
+const SessionContext = React.createContext({
+ session: null,
+ setSession: () => {},
+ loading: true,
+});
+
+export default SessionContext;
+
+export const useSession = () => React.useContext(SessionContext);
diff --git a/examples/core-tutorial/app/api/auth/[...nextAuth]/route.ts b/examples/core/firebase-vite/src/assets/.gitkeep
similarity index 100%
rename from examples/core-tutorial/app/api/auth/[...nextAuth]/route.ts
rename to examples/core/firebase-vite/src/assets/.gitkeep
diff --git a/examples/core/firebase-vite/src/firebase/auth.ts b/examples/core/firebase-vite/src/firebase/auth.ts
new file mode 100644
index 00000000000..014956544b3
--- /dev/null
+++ b/examples/core/firebase-vite/src/firebase/auth.ts
@@ -0,0 +1,92 @@
+import {
+ GoogleAuthProvider,
+ GithubAuthProvider,
+ signInWithPopup,
+ setPersistence,
+ browserSessionPersistence,
+ signInWithEmailAndPassword,
+ signOut,
+} from 'firebase/auth';
+import { firebaseAuth } from './firebaseConfig';
+
+const googleProvider = new GoogleAuthProvider();
+const githubProvider = new GithubAuthProvider();
+
+// Sign in with Google functionality
+export const signInWithGoogle = async () => {
+ try {
+ return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
+ const result = await signInWithPopup(firebaseAuth, googleProvider);
+ return {
+ success: true,
+ user: result.user,
+ error: null,
+ };
+ });
+ } catch (error: any) {
+ return {
+ success: false,
+ user: null,
+ error: error.message,
+ };
+ }
+};
+
+// Sign in with GitHub functionality
+export const signInWithGithub = async () => {
+ try {
+ return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
+ const result = await signInWithPopup(firebaseAuth, githubProvider);
+ return {
+ success: true,
+ user: result.user,
+ error: null,
+ };
+ });
+ } catch (error: any) {
+ return {
+ success: false,
+ user: null,
+ error: error.message,
+ };
+ }
+};
+
+// Sign in with email and password
+
+export async function signInWithCredentials(email: string, password: string) {
+ try {
+ return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
+ const userCredential = await signInWithEmailAndPassword(firebaseAuth, email, password);
+ return {
+ success: true,
+ user: userCredential.user,
+ error: null,
+ };
+ });
+ } catch (error: any) {
+ return {
+ success: false,
+ user: null,
+ error: error.message || 'Failed to sign in with email/password',
+ };
+ }
+}
+
+// Sign out functionality
+export const firebaseSignOut = async () => {
+ try {
+ await signOut(firebaseAuth);
+ return { success: true };
+ } catch (error: any) {
+ return {
+ success: false,
+ error: error.message,
+ };
+ }
+};
+
+// Auth state observer
+export const onAuthStateChanged = (callback: (user: any) => void) => {
+ return firebaseAuth.onAuthStateChanged(callback);
+};
diff --git a/examples/core/firebase-vite/src/firebase/firebaseConfig.ts b/examples/core/firebase-vite/src/firebase/firebaseConfig.ts
new file mode 100644
index 00000000000..bc753697b56
--- /dev/null
+++ b/examples/core/firebase-vite/src/firebase/firebaseConfig.ts
@@ -0,0 +1,14 @@
+import { initializeApp } from 'firebase/app';
+import { getAuth } from 'firebase/auth';
+
+const app = initializeApp({
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
+ storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
+ messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGE_SENDER_ID,
+ appId: import.meta.env.VITE_FIREBASE_APP_ID,
+});
+
+export const firebaseAuth = getAuth(app);
+export default app;
diff --git a/examples/core/firebase-vite/src/layouts/dashboard.tsx b/examples/core/firebase-vite/src/layouts/dashboard.tsx
new file mode 100644
index 00000000000..07e4183641b
--- /dev/null
+++ b/examples/core/firebase-vite/src/layouts/dashboard.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react';
+import LinearProgress from '@mui/material/LinearProgress';
+import { Outlet, Navigate, useLocation } from 'react-router';
+import { DashboardLayout } from '@toolpad/core/DashboardLayout';
+import { PageContainer } from '@toolpad/core/PageContainer';
+import { Account } from '@toolpad/core/Account';
+
+import { useSession } from '../SessionContext';
+
+function CustomAccount() {
+ return (
+
+ );
+}
+
+export default function Layout() {
+ const { session, loading } = useSession();
+ const location = useLocation();
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (!session) {
+ // Add the `callbackUrl` search parameter
+ const redirectTo = `/sign-in?callbackUrl=${encodeURIComponent(location.pathname)}`;
+
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/examples/core/firebase-vite/src/main.tsx b/examples/core/firebase-vite/src/main.tsx
new file mode 100644
index 00000000000..a806e8e3b68
--- /dev/null
+++ b/examples/core/firebase-vite/src/main.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { createBrowserRouter, RouterProvider } from 'react-router';
+import App from './App';
+import Layout from './layouts/dashboard';
+import DashboardPage from './pages';
+import OrdersPage from './pages/orders';
+import SignInPage from './pages/signin';
+
+const router = createBrowserRouter([
+ {
+ Component: App,
+ children: [
+ {
+ path: '/',
+ Component: Layout,
+ children: [
+ {
+ path: '',
+ Component: DashboardPage,
+ },
+ {
+ path: 'orders',
+ Component: OrdersPage,
+ },
+ ],
+ },
+ {
+ path: '/sign-in',
+ Component: SignInPage,
+ },
+ ],
+ },
+]);
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/examples/core/firebase-vite/src/pages/index.tsx b/examples/core/firebase-vite/src/pages/index.tsx
new file mode 100644
index 00000000000..e4581fc26bf
--- /dev/null
+++ b/examples/core/firebase-vite/src/pages/index.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function DashboardPage() {
+ return Welcome to Toolpad! ;
+}
diff --git a/examples/core-tutorial/app/(dashboard)/orders/page.tsx b/examples/core/firebase-vite/src/pages/orders.tsx
similarity index 100%
rename from examples/core-tutorial/app/(dashboard)/orders/page.tsx
rename to examples/core/firebase-vite/src/pages/orders.tsx
diff --git a/examples/core/firebase-vite/src/pages/signin.tsx b/examples/core/firebase-vite/src/pages/signin.tsx
new file mode 100644
index 00000000000..32e8bdc8e34
--- /dev/null
+++ b/examples/core/firebase-vite/src/pages/signin.tsx
@@ -0,0 +1,87 @@
+'use client';
+import * as React from 'react';
+import Alert from '@mui/material/Alert';
+import LinearProgress from '@mui/material/LinearProgress';
+import { SignInPage } from '@toolpad/core/SignInPage';
+import { Navigate, useNavigate } from 'react-router';
+import { useSession, type Session } from '../SessionContext';
+import { signInWithGoogle, signInWithGithub, signInWithCredentials } from '../firebase/auth';
+
+function DemoInfo() {
+ return (
+
+ You can use toolpad-demo@mui.com with the password @demo1 to
+ test
+
+ );
+}
+
+export default function SignIn() {
+ const { session, setSession, loading } = useSession();
+ const navigate = useNavigate();
+
+ if (loading) {
+ return ;
+ }
+
+ if (session) {
+ return ;
+ }
+
+ return (
+ {
+ let result;
+ try {
+ if (provider.id === 'google') {
+ result = await signInWithGoogle();
+ }
+ if (provider.id === 'github') {
+ result = await signInWithGithub();
+ }
+ if (provider.id === 'credentials') {
+ const email = formData?.get('email') as string;
+ const password = formData?.get('password') as string;
+
+ if (!email || !password) {
+ return { error: 'Email and password are required' };
+ }
+
+ result = await signInWithCredentials(email, password);
+ }
+
+ if (result?.success && result?.user) {
+ // Convert Firebase user to Session format
+ const userSession: Session = {
+ user: {
+ name: result.user.displayName || '',
+ email: result.user.email || '',
+ image: result.user.photoURL || '',
+ },
+ };
+ setSession(userSession);
+ navigate(callbackUrl || '/', { replace: true });
+ return {};
+ }
+ return { error: result?.error || 'Failed to sign in' };
+ } catch (error) {
+ return { error: error instanceof Error ? error.message : 'An error occurred' };
+ }
+ }}
+ slots={{ subtitle: DemoInfo }}
+ slotProps={{
+ emailField: {
+ defaultValue: 'toolpad-demo@mui.com',
+ },
+ passwordField: {
+ defaultValue: '@demo1',
+ },
+ }}
+ />
+ );
+}
diff --git a/examples/core/firebase-vite/src/vite-env.d.ts b/examples/core/firebase-vite/src/vite-env.d.ts
new file mode 100644
index 00000000000..11f02fe2a00
--- /dev/null
+++ b/examples/core/firebase-vite/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/core/firebase-vite/tsconfig.json b/examples/core/firebase-vite/tsconfig.json
new file mode 100644
index 00000000000..251a83f8a97
--- /dev/null
+++ b/examples/core/firebase-vite/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/examples/core/firebase-vite/vite.config.ts b/examples/core/firebase-vite/vite.config.ts
new file mode 100644
index 00000000000..627a3196243
--- /dev/null
+++ b/examples/core/firebase-vite/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/examples/core/tutorial/.eslintrc.json b/examples/core/tutorial/.eslintrc.json
new file mode 100644
index 00000000000..bffb357a712
--- /dev/null
+++ b/examples/core/tutorial/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/examples/core-tutorial/.gitignore b/examples/core/tutorial/.gitignore
similarity index 100%
rename from examples/core-tutorial/.gitignore
rename to examples/core/tutorial/.gitignore
diff --git a/examples/core/tutorial/README.md b/examples/core/tutorial/README.md
new file mode 100644
index 00000000000..47c0fedd221
--- /dev/null
+++ b/examples/core/tutorial/README.md
@@ -0,0 +1,35 @@
+# Toolpad Core - Tutorial
+
+This example provides a minimal setup to get Toolpad Core working with the Next.js App Router.
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
+
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example tutorial
+# or
+pnpm dlx create-toolpad-app@latest --example tutorial
+```
+
+and follow the instructions in the terminal.
+
+## The source
+
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/core/tutorial/)
diff --git a/examples/core-tutorial/app/(dashboard)/layout.tsx b/examples/core/tutorial/app/(dashboard)/layout.tsx
similarity index 100%
rename from examples/core-tutorial/app/(dashboard)/layout.tsx
rename to examples/core/tutorial/app/(dashboard)/layout.tsx
diff --git a/examples/core/tutorial/app/(dashboard)/orders/page.tsx b/examples/core/tutorial/app/(dashboard)/orders/page.tsx
new file mode 100644
index 00000000000..de4948afd88
--- /dev/null
+++ b/examples/core/tutorial/app/(dashboard)/orders/page.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function OrdersPage() {
+ return Welcome to the Toolpad orders! ;
+}
diff --git a/examples/core-tutorial/app/(dashboard)/page.tsx b/examples/core/tutorial/app/(dashboard)/page.tsx
similarity index 100%
rename from examples/core-tutorial/app/(dashboard)/page.tsx
rename to examples/core/tutorial/app/(dashboard)/page.tsx
diff --git a/examples/core-tutorial/app/layout.tsx b/examples/core/tutorial/app/layout.tsx
similarity index 65%
rename from examples/core-tutorial/app/layout.tsx
rename to examples/core/tutorial/app/layout.tsx
index eff87342084..490c41047c0 100644
--- a/examples/core-tutorial/app/layout.tsx
+++ b/examples/core/tutorial/app/layout.tsx
@@ -1,8 +1,10 @@
-import { AppProvider } from '@toolpad/core/nextjs';
+import * as React from 'react';
+import { NextAppProvider } from '@toolpad/core/nextjs';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
-import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
+import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import type { Navigation } from '@toolpad/core/AppProvider';
+import LinearProgress from '@mui/material/LinearProgress';
import theme from '../theme';
const NAVIGATION: Navigation = [
@@ -27,9 +29,11 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
-
- {children}
-
+ }>
+
+ {children}
+
+
diff --git a/examples/core/tutorial/next-env.d.ts b/examples/core/tutorial/next-env.d.ts
new file mode 100644
index 00000000000..40c3d68096c
--- /dev/null
+++ b/examples/core/tutorial/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
diff --git a/examples/core/tutorial/next.config.mjs b/examples/core/tutorial/next.config.mjs
new file mode 100644
index 00000000000..f26ac370c56
--- /dev/null
+++ b/examples/core/tutorial/next.config.mjs
@@ -0,0 +1,3 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+export default nextConfig;
diff --git a/examples/core-tutorial/package.json b/examples/core/tutorial/package.json
similarity index 69%
rename from examples/core-tutorial/package.json
rename to examples/core/tutorial/package.json
index c45cfd1b39b..1a3df080f7b 100644
--- a/examples/core-tutorial/package.json
+++ b/examples/core/tutorial/package.json
@@ -8,9 +8,9 @@
"lint": "next lint"
},
"dependencies": {
- "react": "^18",
- "react-dom": "^18",
- "next": "^14",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "next": "^15",
"@toolpad/core": "latest",
"@mui/material": "^6",
"@mui/material-nextjs": "^6",
@@ -20,10 +20,10 @@
},
"devDependencies": {
"typescript": "^5",
- "@types/node": "^20.16.11",
- "@types/react": "^18",
- "@types/react-dom": "^18",
+ "@types/node": "^20.17.12",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
"eslint": "^8",
- "eslint-config-next": "^14"
+ "eslint-config-next": "^15"
}
}
diff --git a/examples/core-tutorial/theme.ts b/examples/core/tutorial/theme.ts
similarity index 100%
rename from examples/core-tutorial/theme.ts
rename to examples/core/tutorial/theme.ts
diff --git a/examples/core-tutorial/tsconfig.json b/examples/core/tutorial/tsconfig.json
similarity index 100%
rename from examples/core-tutorial/tsconfig.json
rename to examples/core/tutorial/tsconfig.json
diff --git a/examples/core/vite/.gitignore b/examples/core/vite/.gitignore
new file mode 100644
index 00000000000..a547bf36d8d
--- /dev/null
+++ b/examples/core/vite/.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/examples/core-vite/README.md b/examples/core/vite/README.md
similarity index 56%
rename from examples/core-vite/README.md
rename to examples/core/vite/README.md
index 0978492ca52..79d574e9cfc 100644
--- a/examples/core-vite/README.md
+++ b/examples/core/vite/README.md
@@ -1,7 +1,19 @@
-# Toolpad Core - Vite & React Router
+# Toolpad Core - Vite with React Router
This example provides a minimal setup to get Toolpad Core working in Vite with HMR, as well as routing with React Router.
+## Clone using `create-toolpad-app`
+
+To copy this example and customize it for your needs, run
+
+```bash
+npx create-toolpad-app@latest --example vite
+# or
+pnpm dlx create-toolpad-app@latest --example vite
+```
+
+and follow the instructions in the terminal.
+
## Getting Started
First, run the development server:
@@ -20,4 +32,4 @@ Open [http://localhost:5173](http://localhost:5173) with your browser to see the
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/core-vite)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/core/vite/)
diff --git a/examples/core/vite/index.html b/examples/core/vite/index.html
new file mode 100644
index 00000000000..1f790b03947
--- /dev/null
+++ b/examples/core/vite/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+ Toolpad Core Vite
+
+
+
+
+
+
diff --git a/examples/core-vite/package.json b/examples/core/vite/package.json
similarity index 76%
rename from examples/core-vite/package.json
rename to examples/core/vite/package.json
index afe44cc4cf7..6d55773f903 100644
--- a/examples/core-vite/package.json
+++ b/examples/core/vite/package.json
@@ -13,13 +13,13 @@
"@mui/icons-material": "^6",
"@mui/material": "^6",
"@toolpad/core": "latest",
- "react": "^18",
- "react-dom": "^18",
- "react-router-dom": "^6"
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-router": "^7"
},
"devDependencies": {
- "@types/react": "^18",
- "@types/react-dom": "^18",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.2",
"typescript": "^5",
"vite": "^5.4.8"
diff --git a/examples/core/vite/public/vite.svg b/examples/core/vite/public/vite.svg
new file mode 100644
index 00000000000..e7b8dfb1b2a
--- /dev/null
+++ b/examples/core/vite/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/core-vite/src/App.tsx b/examples/core/vite/src/App.tsx
similarity index 73%
rename from examples/core-vite/src/App.tsx
rename to examples/core/vite/src/App.tsx
index b83180f8c80..ea7cb04796c 100644
--- a/examples/core-vite/src/App.tsx
+++ b/examples/core/vite/src/App.tsx
@@ -1,8 +1,8 @@
import * as React from 'react';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
-import { Outlet } from 'react-router-dom';
-import { AppProvider } from '@toolpad/core/react-router-dom';
+import { Outlet } from 'react-router';
+import { ReactRouterAppProvider } from '@toolpad/core/react-router';
import type { Navigation } from '@toolpad/core/AppProvider';
const NAVIGATION: Navigation = [
@@ -27,8 +27,8 @@ const BRANDING = {
export default function App() {
return (
-
+
-
+
);
}
diff --git a/examples/core-tutorial/app/auth/[...path]/page.tsx b/examples/core/vite/src/assets/.gitkeep
similarity index 100%
rename from examples/core-tutorial/app/auth/[...path]/page.tsx
rename to examples/core/vite/src/assets/.gitkeep
diff --git a/examples/core-vite/src/layouts/dashboard.tsx b/examples/core/vite/src/layouts/dashboard.tsx
similarity index 88%
rename from examples/core-vite/src/layouts/dashboard.tsx
rename to examples/core/vite/src/layouts/dashboard.tsx
index c540feb6e0f..84b8584f7c6 100644
--- a/examples/core-vite/src/layouts/dashboard.tsx
+++ b/examples/core/vite/src/layouts/dashboard.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { Outlet } from 'react-router-dom';
+import { Outlet } from 'react-router';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { PageContainer } from '@toolpad/core/PageContainer';
diff --git a/examples/core-vite/src/main.tsx b/examples/core/vite/src/main.tsx
similarity index 92%
rename from examples/core-vite/src/main.tsx
rename to examples/core/vite/src/main.tsx
index cda00abd9f2..cee7678c361 100644
--- a/examples/core-vite/src/main.tsx
+++ b/examples/core/vite/src/main.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import { createBrowserRouter, RouterProvider } from 'react-router-dom';
+import { createBrowserRouter, RouterProvider } from 'react-router';
import App from './App';
import Layout from './layouts/dashboard';
import DashboardPage from './pages';
@@ -15,11 +15,11 @@ const router = createBrowserRouter([
Component: Layout,
children: [
{
- path: '/',
+ path: '',
Component: DashboardPage,
},
{
- path: '/orders',
+ path: 'orders',
Component: OrdersPage,
},
],
diff --git a/examples/core/vite/src/pages/index.tsx b/examples/core/vite/src/pages/index.tsx
new file mode 100644
index 00000000000..e4581fc26bf
--- /dev/null
+++ b/examples/core/vite/src/pages/index.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function DashboardPage() {
+ return Welcome to Toolpad! ;
+}
diff --git a/examples/core/vite/src/pages/orders.tsx b/examples/core/vite/src/pages/orders.tsx
new file mode 100644
index 00000000000..de4948afd88
--- /dev/null
+++ b/examples/core/vite/src/pages/orders.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+export default function OrdersPage() {
+ return Welcome to the Toolpad orders! ;
+}
diff --git a/examples/core/vite/src/vite-env.d.ts b/examples/core/vite/src/vite-env.d.ts
new file mode 100644
index 00000000000..11f02fe2a00
--- /dev/null
+++ b/examples/core/vite/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/core-vite/tsconfig.json b/examples/core/vite/tsconfig.json
similarity index 100%
rename from examples/core-vite/tsconfig.json
rename to examples/core/vite/tsconfig.json
diff --git a/examples/core-vite/tsconfig.node.json b/examples/core/vite/tsconfig.node.json
similarity index 100%
rename from examples/core-vite/tsconfig.node.json
rename to examples/core/vite/tsconfig.node.json
diff --git a/examples/core/vite/vite.config.ts b/examples/core/vite/vite.config.ts
new file mode 100644
index 00000000000..627a3196243
--- /dev/null
+++ b/examples/core/vite/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/examples/auth-github/.env.example b/examples/studio/auth-github/.env.example
similarity index 100%
rename from examples/auth-github/.env.example
rename to examples/studio/auth-github/.env.example
diff --git a/examples/auth-github/README.md b/examples/studio/auth-github/README.md
similarity index 97%
rename from examples/auth-github/README.md
rename to examples/studio/auth-github/README.md
index c641e94539c..8208699515a 100644
--- a/examples/auth-github/README.md
+++ b/examples/studio/auth-github/README.md
@@ -32,4 +32,4 @@ pnpm create toolpad-app --example auth-github
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/auth-github)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/studio/auth-github)
diff --git a/examples/auth-github/package.json b/examples/studio/auth-github/package.json
similarity index 100%
rename from examples/auth-github/package.json
rename to examples/studio/auth-github/package.json
diff --git a/examples/auth-github/toolpad/.gitignore b/examples/studio/auth-github/toolpad/.gitignore
similarity index 100%
rename from examples/auth-github/toolpad/.gitignore
rename to examples/studio/auth-github/toolpad/.gitignore
diff --git a/examples/auth-github/toolpad/application.yml b/examples/studio/auth-github/toolpad/application.yml
similarity index 100%
rename from examples/auth-github/toolpad/application.yml
rename to examples/studio/auth-github/toolpad/application.yml
diff --git a/examples/auth-github/toolpad/pages/protectedpage/page.yml b/examples/studio/auth-github/toolpad/pages/protectedpage/page.yml
similarity index 100%
rename from examples/auth-github/toolpad/pages/protectedpage/page.yml
rename to examples/studio/auth-github/toolpad/pages/protectedpage/page.yml
diff --git a/examples/auth-google/.env.example b/examples/studio/auth-google/.env.example
similarity index 100%
rename from examples/auth-google/.env.example
rename to examples/studio/auth-google/.env.example
diff --git a/examples/auth-google/README.md b/examples/studio/auth-google/README.md
similarity index 96%
rename from examples/auth-google/README.md
rename to examples/studio/auth-google/README.md
index dbaef095095..cddd6d160a7 100644
--- a/examples/auth-google/README.md
+++ b/examples/studio/auth-google/README.md
@@ -32,4 +32,4 @@ pnpm create toolpad-app --example auth-google
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/auth-google)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/auth-google)
diff --git a/examples/auth-google/package.json b/examples/studio/auth-google/package.json
similarity index 100%
rename from examples/auth-google/package.json
rename to examples/studio/auth-google/package.json
diff --git a/examples/auth-google/toolpad/.gitignore b/examples/studio/auth-google/toolpad/.gitignore
similarity index 100%
rename from examples/auth-google/toolpad/.gitignore
rename to examples/studio/auth-google/toolpad/.gitignore
diff --git a/examples/auth-google/toolpad/application.yml b/examples/studio/auth-google/toolpad/application.yml
similarity index 100%
rename from examples/auth-google/toolpad/application.yml
rename to examples/studio/auth-google/toolpad/application.yml
diff --git a/examples/auth-google/toolpad/pages/protectedpage/page.yml b/examples/studio/auth-google/toolpad/pages/protectedpage/page.yml
similarity index 100%
rename from examples/auth-google/toolpad/pages/protectedpage/page.yml
rename to examples/studio/auth-google/toolpad/pages/protectedpage/page.yml
diff --git a/examples/basic-crud-app/README.md b/examples/studio/basic-crud-app/README.md
similarity index 93%
rename from examples/basic-crud-app/README.md
rename to examples/studio/basic-crud-app/README.md
index 7e3d18691a6..350c16ffebd 100644
--- a/examples/basic-crud-app/README.md
+++ b/examples/studio/basic-crud-app/README.md
@@ -28,7 +28,7 @@ pnpm create toolpad-app --example basic-crud-app
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/basic-crud-app)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/basic-crud-app)
## What's inside
@@ -41,4 +41,4 @@ This app demonstrates the following capabilities of Toolpad:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/basic-crud-app)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/basic-crud-app)
diff --git a/examples/basic-crud-app/package.json b/examples/studio/basic-crud-app/package.json
similarity index 100%
rename from examples/basic-crud-app/package.json
rename to examples/studio/basic-crud-app/package.json
diff --git a/examples/basic-crud-app/toolpad/.gitignore b/examples/studio/basic-crud-app/toolpad/.gitignore
similarity index 100%
rename from examples/basic-crud-app/toolpad/.gitignore
rename to examples/studio/basic-crud-app/toolpad/.gitignore
diff --git a/examples/basic-crud-app/toolpad/application.yml b/examples/studio/basic-crud-app/toolpad/application.yml
similarity index 100%
rename from examples/basic-crud-app/toolpad/application.yml
rename to examples/studio/basic-crud-app/toolpad/application.yml
diff --git a/examples/basic-crud-app/toolpad/pages/AdminApp/page.yml b/examples/studio/basic-crud-app/toolpad/pages/AdminApp/page.yml
similarity index 100%
rename from examples/basic-crud-app/toolpad/pages/AdminApp/page.yml
rename to examples/studio/basic-crud-app/toolpad/pages/AdminApp/page.yml
diff --git a/examples/basic-crud-app/toolpad/pages/AdminApp1/page.yml b/examples/studio/basic-crud-app/toolpad/pages/AdminApp1/page.yml
similarity index 100%
rename from examples/basic-crud-app/toolpad/pages/AdminApp1/page.yml
rename to examples/studio/basic-crud-app/toolpad/pages/AdminApp1/page.yml
diff --git a/examples/basic-crud-app/toolpad/resources/dataProvider.ts b/examples/studio/basic-crud-app/toolpad/resources/dataProvider.ts
similarity index 100%
rename from examples/basic-crud-app/toolpad/resources/dataProvider.ts
rename to examples/studio/basic-crud-app/toolpad/resources/dataProvider.ts
diff --git a/examples/basic-crud-app/toolpad/resources/functions.ts b/examples/studio/basic-crud-app/toolpad/resources/functions.ts
similarity index 100%
rename from examples/basic-crud-app/toolpad/resources/functions.ts
rename to examples/studio/basic-crud-app/toolpad/resources/functions.ts
diff --git a/examples/datagrid-premium/.gitignore b/examples/studio/charts/.gitignore
similarity index 96%
rename from examples/datagrid-premium/.gitignore
rename to examples/studio/charts/.gitignore
index a56149af866..826df8851c4 100644
--- a/examples/datagrid-premium/.gitignore
+++ b/examples/studio/charts/.gitignore
@@ -119,7 +119,7 @@ dist
# TernJS port file
.tern-port
-# Stores VSCode versions used for testing VSCode extensions
+# Stores VS Code versions used for testing VS Code extensions
.vscode-test
# yarn v2
diff --git a/examples/charts/README.md b/examples/studio/charts/README.md
similarity index 96%
rename from examples/charts/README.md
rename to examples/studio/charts/README.md
index 44c411953f3..857e35c76c9 100644
--- a/examples/charts/README.md
+++ b/examples/studio/charts/README.md
@@ -34,4 +34,4 @@ This app demonstrates the following capabilities of Toolpad Studio:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/charts)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/charts)
diff --git a/examples/charts/package.json b/examples/studio/charts/package.json
similarity index 100%
rename from examples/charts/package.json
rename to examples/studio/charts/package.json
diff --git a/examples/charts/toolpad/.gitignore b/examples/studio/charts/toolpad/.gitignore
similarity index 100%
rename from examples/charts/toolpad/.gitignore
rename to examples/studio/charts/toolpad/.gitignore
diff --git a/examples/charts/toolpad/pages/page/page.yml b/examples/studio/charts/toolpad/pages/page/page.yml
similarity index 92%
rename from examples/charts/toolpad/pages/page/page.yml
rename to examples/studio/charts/toolpad/pages/page/page.yml
index 4ab0cbd88fc..b7c2067ec6e 100644
--- a/examples/charts/toolpad/pages/page/page.yml
+++ b/examples/studio/charts/toolpad/pages/page/page.yml
@@ -1,3 +1,5 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/mui/mui-toolpad/v0.2.0/docs/schemas/v1/definitions.json#properties/Page
+
apiVersion: v1
kind: page
spec:
@@ -87,4 +89,5 @@ spec:
transform: return data.movies;
transformEnabled: true
searchParams: []
+ url: https://raw.githubusercontent.com/mui/toolpad/master/public/movies.json
display: shell
diff --git a/examples/custom-component/.gitignore b/examples/studio/custom-component/.gitignore
similarity index 96%
rename from examples/custom-component/.gitignore
rename to examples/studio/custom-component/.gitignore
index a56149af866..826df8851c4 100644
--- a/examples/custom-component/.gitignore
+++ b/examples/studio/custom-component/.gitignore
@@ -119,7 +119,7 @@ dist
# TernJS port file
.tern-port
-# Stores VSCode versions used for testing VSCode extensions
+# Stores VS Code versions used for testing VS Code extensions
.vscode-test
# yarn v2
diff --git a/examples/custom-component/README.md b/examples/studio/custom-component/README.md
similarity index 88%
rename from examples/custom-component/README.md
rename to examples/studio/custom-component/README.md
index 1b5a8e1fb83..dedd537ec89 100644
--- a/examples/custom-component/README.md
+++ b/examples/studio/custom-component/README.md
@@ -20,7 +20,7 @@ pnpm create toolpad-app --example custom-component
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/custom-component)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/custom-component)
## What's inside
@@ -30,4 +30,4 @@ This app demonstrates the following capabilities of Toolpad Studio:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/custom-component)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/custom-component)
diff --git a/examples/custom-component/application.yml b/examples/studio/custom-component/application.yml
similarity index 100%
rename from examples/custom-component/application.yml
rename to examples/studio/custom-component/application.yml
diff --git a/examples/custom-component/components/Clock.tsx b/examples/studio/custom-component/components/Clock.tsx
similarity index 100%
rename from examples/custom-component/components/Clock.tsx
rename to examples/studio/custom-component/components/Clock.tsx
diff --git a/examples/custom-component/package.json b/examples/studio/custom-component/package.json
similarity index 100%
rename from examples/custom-component/package.json
rename to examples/studio/custom-component/package.json
diff --git a/examples/custom-component/pages/page/page.yml b/examples/studio/custom-component/pages/page/page.yml
similarity index 100%
rename from examples/custom-component/pages/page/page.yml
rename to examples/studio/custom-component/pages/page/page.yml
diff --git a/examples/custom-datagrid-column/README.md b/examples/studio/custom-datagrid-column/README.md
similarity index 94%
rename from examples/custom-datagrid-column/README.md
rename to examples/studio/custom-datagrid-column/README.md
index 567da4695e8..1ac289c0743 100644
--- a/examples/custom-datagrid-column/README.md
+++ b/examples/studio/custom-datagrid-column/README.md
@@ -26,4 +26,4 @@ pnpm create toolpad-app --example custom-datagrid-column
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/custom-datagrid-column)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/custom-datagrid-column)
diff --git a/examples/custom-datagrid-column/package.json b/examples/studio/custom-datagrid-column/package.json
similarity index 100%
rename from examples/custom-datagrid-column/package.json
rename to examples/studio/custom-datagrid-column/package.json
diff --git a/examples/custom-datagrid-column/toolpad/.gitignore b/examples/studio/custom-datagrid-column/toolpad/.gitignore
similarity index 100%
rename from examples/custom-datagrid-column/toolpad/.gitignore
rename to examples/studio/custom-datagrid-column/toolpad/.gitignore
diff --git a/examples/custom-datagrid-column/toolpad/components/AgeColumn.tsx b/examples/studio/custom-datagrid-column/toolpad/components/AgeColumn.tsx
similarity index 100%
rename from examples/custom-datagrid-column/toolpad/components/AgeColumn.tsx
rename to examples/studio/custom-datagrid-column/toolpad/components/AgeColumn.tsx
diff --git a/examples/custom-datagrid-column/toolpad/components/FullNameColumn.tsx b/examples/studio/custom-datagrid-column/toolpad/components/FullNameColumn.tsx
similarity index 100%
rename from examples/custom-datagrid-column/toolpad/components/FullNameColumn.tsx
rename to examples/studio/custom-datagrid-column/toolpad/components/FullNameColumn.tsx
diff --git a/examples/custom-datagrid-column/toolpad/pages/example/page.yml b/examples/studio/custom-datagrid-column/toolpad/pages/example/page.yml
similarity index 100%
rename from examples/custom-datagrid-column/toolpad/pages/example/page.yml
rename to examples/studio/custom-datagrid-column/toolpad/pages/example/page.yml
diff --git a/examples/custom-server-nextjs/README.md b/examples/studio/custom-server-nextjs/README.md
similarity index 93%
rename from examples/custom-server-nextjs/README.md
rename to examples/studio/custom-server-nextjs/README.md
index e2be134be57..9322fcddc7d 100644
--- a/examples/custom-server-nextjs/README.md
+++ b/examples/studio/custom-server-nextjs/README.md
@@ -26,4 +26,4 @@ pnpm create toolpad-app --example custom-server-nextjs
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/custom-server-nextjs)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/custom-server-nextjs)
diff --git a/examples/custom-server-nextjs/index.mjs b/examples/studio/custom-server-nextjs/index.mjs
similarity index 100%
rename from examples/custom-server-nextjs/index.mjs
rename to examples/studio/custom-server-nextjs/index.mjs
diff --git a/examples/custom-server-nextjs/next-env.d.ts b/examples/studio/custom-server-nextjs/next-env.d.ts
similarity index 100%
rename from examples/custom-server-nextjs/next-env.d.ts
rename to examples/studio/custom-server-nextjs/next-env.d.ts
diff --git a/examples/custom-server-nextjs/next.config.mjs b/examples/studio/custom-server-nextjs/next.config.mjs
similarity index 100%
rename from examples/custom-server-nextjs/next.config.mjs
rename to examples/studio/custom-server-nextjs/next.config.mjs
diff --git a/examples/custom-server-nextjs/package.json b/examples/studio/custom-server-nextjs/package.json
similarity index 85%
rename from examples/custom-server-nextjs/package.json
rename to examples/studio/custom-server-nextjs/package.json
index 1c18e84224a..ba1a30c1cd3 100644
--- a/examples/custom-server-nextjs/package.json
+++ b/examples/studio/custom-server-nextjs/package.json
@@ -12,10 +12,10 @@
"@toolpad/studio": "0.5.2",
"express": "4.20.0",
"next": "14.2.10",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
},
"devDependencies": {
- "@types/react": "18.3.3"
+ "@types/react": "^19.0.0"
}
}
diff --git a/examples/custom-server-nextjs/pages/index.tsx b/examples/studio/custom-server-nextjs/pages/index.tsx
similarity index 100%
rename from examples/custom-server-nextjs/pages/index.tsx
rename to examples/studio/custom-server-nextjs/pages/index.tsx
diff --git a/examples/custom-server-nextjs/toolpad/.gitignore b/examples/studio/custom-server-nextjs/toolpad/.gitignore
similarity index 100%
rename from examples/custom-server-nextjs/toolpad/.gitignore
rename to examples/studio/custom-server-nextjs/toolpad/.gitignore
diff --git a/examples/custom-server-nextjs/toolpad/pages/page/page.yml b/examples/studio/custom-server-nextjs/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/custom-server-nextjs/toolpad/pages/page/page.yml
rename to examples/studio/custom-server-nextjs/toolpad/pages/page/page.yml
diff --git a/examples/custom-server-nextjs/tsconfig.json b/examples/studio/custom-server-nextjs/tsconfig.json
similarity index 100%
rename from examples/custom-server-nextjs/tsconfig.json
rename to examples/studio/custom-server-nextjs/tsconfig.json
diff --git a/examples/custom-server/README.md b/examples/studio/custom-server/README.md
similarity index 94%
rename from examples/custom-server/README.md
rename to examples/studio/custom-server/README.md
index 3b52d3409a1..c2df40dc82f 100644
--- a/examples/custom-server/README.md
+++ b/examples/studio/custom-server/README.md
@@ -26,4 +26,4 @@ pnpm create toolpad-app --example custom-server
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/custom-server)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/custom-server)
diff --git a/examples/custom-server/index.mjs b/examples/studio/custom-server/index.mjs
similarity index 100%
rename from examples/custom-server/index.mjs
rename to examples/studio/custom-server/index.mjs
diff --git a/examples/custom-server/package.json b/examples/studio/custom-server/package.json
similarity index 100%
rename from examples/custom-server/package.json
rename to examples/studio/custom-server/package.json
diff --git a/examples/custom-server/toolpad/.gitignore b/examples/studio/custom-server/toolpad/.gitignore
similarity index 100%
rename from examples/custom-server/toolpad/.gitignore
rename to examples/studio/custom-server/toolpad/.gitignore
diff --git a/examples/custom-server/toolpad/pages/page/page.yml b/examples/studio/custom-server/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/custom-server/toolpad/pages/page/page.yml
rename to examples/studio/custom-server/toolpad/pages/page/page.yml
diff --git a/examples/datagrid-columns/README.md b/examples/studio/datagrid-columns/README.md
similarity index 94%
rename from examples/datagrid-columns/README.md
rename to examples/studio/datagrid-columns/README.md
index 9a9ee4c44ba..79ca3b376fc 100644
--- a/examples/datagrid-columns/README.md
+++ b/examples/studio/datagrid-columns/README.md
@@ -26,4 +26,4 @@ pnpm create toolpad-app --example datagrid-columns
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/datagrid-columns)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/datagrid-columns)
diff --git a/examples/datagrid-columns/package.json b/examples/studio/datagrid-columns/package.json
similarity index 100%
rename from examples/datagrid-columns/package.json
rename to examples/studio/datagrid-columns/package.json
diff --git a/examples/datagrid-columns/toolpad/.gitignore b/examples/studio/datagrid-columns/toolpad/.gitignore
similarity index 100%
rename from examples/datagrid-columns/toolpad/.gitignore
rename to examples/studio/datagrid-columns/toolpad/.gitignore
diff --git a/examples/datagrid-columns/toolpad/components/OrderIdChip.tsx b/examples/studio/datagrid-columns/toolpad/components/OrderIdChip.tsx
similarity index 100%
rename from examples/datagrid-columns/toolpad/components/OrderIdChip.tsx
rename to examples/studio/datagrid-columns/toolpad/components/OrderIdChip.tsx
diff --git a/examples/datagrid-columns/toolpad/pages/customers/page.yml b/examples/studio/datagrid-columns/toolpad/pages/customers/page.yml
similarity index 100%
rename from examples/datagrid-columns/toolpad/pages/customers/page.yml
rename to examples/studio/datagrid-columns/toolpad/pages/customers/page.yml
diff --git a/examples/datagrid-columns/toolpad/resources/functions.ts b/examples/studio/datagrid-columns/toolpad/resources/functions.ts
similarity index 100%
rename from examples/datagrid-columns/toolpad/resources/functions.ts
rename to examples/studio/datagrid-columns/toolpad/resources/functions.ts
diff --git a/examples/stripe-script/.gitignore b/examples/studio/datagrid-premium/.gitignore
similarity index 96%
rename from examples/stripe-script/.gitignore
rename to examples/studio/datagrid-premium/.gitignore
index a56149af866..826df8851c4 100644
--- a/examples/stripe-script/.gitignore
+++ b/examples/studio/datagrid-premium/.gitignore
@@ -119,7 +119,7 @@ dist
# TernJS port file
.tern-port
-# Stores VSCode versions used for testing VSCode extensions
+# Stores VS Code versions used for testing VS Code extensions
.vscode-test
# yarn v2
diff --git a/examples/datagrid-premium/README.md b/examples/studio/datagrid-premium/README.md
similarity index 94%
rename from examples/datagrid-premium/README.md
rename to examples/studio/datagrid-premium/README.md
index e2be56f1bf3..76800632789 100644
--- a/examples/datagrid-premium/README.md
+++ b/examples/studio/datagrid-premium/README.md
@@ -26,4 +26,4 @@ This app demonstrates the following capabilities of Toolpad Studio:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/datagrid-premium)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/datagrid-premium)
diff --git a/examples/datagrid-premium/application.yml b/examples/studio/datagrid-premium/application.yml
similarity index 100%
rename from examples/datagrid-premium/application.yml
rename to examples/studio/datagrid-premium/application.yml
diff --git a/examples/datagrid-premium/components/CustomDataGrid.tsx b/examples/studio/datagrid-premium/components/CustomDataGrid.tsx
similarity index 93%
rename from examples/datagrid-premium/components/CustomDataGrid.tsx
rename to examples/studio/datagrid-premium/components/CustomDataGrid.tsx
index 3c78c89e3f6..241e2da4cb3 100644
--- a/examples/datagrid-premium/components/CustomDataGrid.tsx
+++ b/examples/studio/datagrid-premium/components/CustomDataGrid.tsx
@@ -3,7 +3,7 @@ import { createComponent } from '@toolpad/studio/browser';
import { DataGridPremium, GridColDef } from '@mui/x-data-grid-premium';
import { LicenseInfo } from '@mui/x-license';
-LicenseInfo.setLicenseKey('LICENSE_KEY');
+LicenseInfo.setLicenseKey('YOUR_LICENSE_KEY');
export interface CustomDataGridProps {
rows: any[];
diff --git a/examples/datagrid-premium/components/tsconfig.json b/examples/studio/datagrid-premium/components/tsconfig.json
similarity index 100%
rename from examples/datagrid-premium/components/tsconfig.json
rename to examples/studio/datagrid-premium/components/tsconfig.json
diff --git a/examples/datagrid-premium/package.json b/examples/studio/datagrid-premium/package.json
similarity index 100%
rename from examples/datagrid-premium/package.json
rename to examples/studio/datagrid-premium/package.json
diff --git a/examples/datagrid-premium/pages/page/page.yml b/examples/studio/datagrid-premium/pages/page/page.yml
similarity index 100%
rename from examples/datagrid-premium/pages/page/page.yml
rename to examples/studio/datagrid-premium/pages/page/page.yml
diff --git a/examples/dog-app/README.md b/examples/studio/dog-app/README.md
similarity index 94%
rename from examples/dog-app/README.md
rename to examples/studio/dog-app/README.md
index 41c148379f4..a887986ef12 100644
--- a/examples/dog-app/README.md
+++ b/examples/studio/dog-app/README.md
@@ -28,7 +28,7 @@ pnpm create toolpad-app --example dog-app
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/dog-app)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/dog-app)
## What's inside
@@ -38,4 +38,4 @@ To build this app step-by-step, visit the [docs](https://mui.com/toolpad/studio/
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/dog-app)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/dog-app)
diff --git a/examples/dog-app/package.json b/examples/studio/dog-app/package.json
similarity index 100%
rename from examples/dog-app/package.json
rename to examples/studio/dog-app/package.json
diff --git a/examples/dog-app/toolpad/.gitignore b/examples/studio/dog-app/toolpad/.gitignore
similarity index 100%
rename from examples/dog-app/toolpad/.gitignore
rename to examples/studio/dog-app/toolpad/.gitignore
diff --git a/examples/dog-app/toolpad/pages/page/page.yml b/examples/studio/dog-app/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/dog-app/toolpad/pages/page/page.yml
rename to examples/studio/dog-app/toolpad/pages/page/page.yml
diff --git a/examples/google-sheet/.gitignore b/examples/studio/google-sheet/.gitignore
similarity index 100%
rename from examples/google-sheet/.gitignore
rename to examples/studio/google-sheet/.gitignore
diff --git a/examples/google-sheet/README.md b/examples/studio/google-sheet/README.md
similarity index 100%
rename from examples/google-sheet/README.md
rename to examples/studio/google-sheet/README.md
diff --git a/examples/google-sheet/package.json b/examples/studio/google-sheet/package.json
similarity index 100%
rename from examples/google-sheet/package.json
rename to examples/studio/google-sheet/package.json
diff --git a/examples/google-sheet/toolpad/.gitignore b/examples/studio/google-sheet/toolpad/.gitignore
similarity index 100%
rename from examples/google-sheet/toolpad/.gitignore
rename to examples/studio/google-sheet/toolpad/.gitignore
diff --git a/examples/google-sheet/toolpad/pages/page/page.yml b/examples/studio/google-sheet/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/google-sheet/toolpad/pages/page/page.yml
rename to examples/studio/google-sheet/toolpad/pages/page/page.yml
diff --git a/examples/google-sheet/toolpad/resources/functions.ts b/examples/studio/google-sheet/toolpad/resources/functions.ts
similarity index 100%
rename from examples/google-sheet/toolpad/resources/functions.ts
rename to examples/studio/google-sheet/toolpad/resources/functions.ts
diff --git a/examples/graphql/.gitignore b/examples/studio/graphql/.gitignore
similarity index 100%
rename from examples/graphql/.gitignore
rename to examples/studio/graphql/.gitignore
diff --git a/examples/graphql/README.md b/examples/studio/graphql/README.md
similarity index 93%
rename from examples/graphql/README.md
rename to examples/studio/graphql/README.md
index 10a8bb57a63..a38d30f6b87 100644
--- a/examples/graphql/README.md
+++ b/examples/studio/graphql/README.md
@@ -28,7 +28,7 @@ pnpm create toolpad-app --example graphql
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/graphql)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/graphql)
## What's inside
@@ -40,4 +40,4 @@ A Toolpad Studio app that shows how to:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/graphql)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/graphql)
diff --git a/examples/graphql/package.json b/examples/studio/graphql/package.json
similarity index 100%
rename from examples/graphql/package.json
rename to examples/studio/graphql/package.json
diff --git a/examples/graphql/toolpad/.gitignore b/examples/studio/graphql/toolpad/.gitignore
similarity index 100%
rename from examples/graphql/toolpad/.gitignore
rename to examples/studio/graphql/toolpad/.gitignore
diff --git a/examples/graphql/toolpad/application.yml b/examples/studio/graphql/toolpad/application.yml
similarity index 100%
rename from examples/graphql/toolpad/application.yml
rename to examples/studio/graphql/toolpad/application.yml
diff --git a/examples/graphql/toolpad/pages/page/page.yml b/examples/studio/graphql/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/graphql/toolpad/pages/page/page.yml
rename to examples/studio/graphql/toolpad/pages/page/page.yml
diff --git a/examples/graphql/toolpad/resources/function.ts b/examples/studio/graphql/toolpad/resources/function.ts
similarity index 100%
rename from examples/graphql/toolpad/resources/function.ts
rename to examples/studio/graphql/toolpad/resources/function.ts
diff --git a/examples/hacker-news-client/README.md b/examples/studio/hacker-news-client/README.md
similarity index 91%
rename from examples/hacker-news-client/README.md
rename to examples/studio/hacker-news-client/README.md
index 92bbc7100da..de2eeeb8e79 100644
--- a/examples/hacker-news-client/README.md
+++ b/examples/studio/hacker-news-client/README.md
@@ -24,7 +24,7 @@ pnpm create toolpad-app --example hacker-news-client
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/hacker-news-client)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/hacker-news-client)
## What's inside
@@ -35,4 +35,4 @@ A Toolpad Studio app that shows how to:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/hacker-news-client)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/hacker-news-client)
diff --git a/examples/hacker-news-client/package.json b/examples/studio/hacker-news-client/package.json
similarity index 100%
rename from examples/hacker-news-client/package.json
rename to examples/studio/hacker-news-client/package.json
diff --git a/examples/hacker-news-client/toolpad/.gitignore b/examples/studio/hacker-news-client/toolpad/.gitignore
similarity index 100%
rename from examples/hacker-news-client/toolpad/.gitignore
rename to examples/studio/hacker-news-client/toolpad/.gitignore
diff --git a/examples/hacker-news-client/toolpad/application.yml b/examples/studio/hacker-news-client/toolpad/application.yml
similarity index 100%
rename from examples/hacker-news-client/toolpad/application.yml
rename to examples/studio/hacker-news-client/toolpad/application.yml
diff --git a/examples/hacker-news-client/toolpad/pages/hackernews/page.yml b/examples/studio/hacker-news-client/toolpad/pages/hackernews/page.yml
similarity index 100%
rename from examples/hacker-news-client/toolpad/pages/hackernews/page.yml
rename to examples/studio/hacker-news-client/toolpad/pages/hackernews/page.yml
diff --git a/examples/hacker-news-client/toolpad/resources/functions.ts b/examples/studio/hacker-news-client/toolpad/resources/functions.ts
similarity index 100%
rename from examples/hacker-news-client/toolpad/resources/functions.ts
rename to examples/studio/hacker-news-client/toolpad/resources/functions.ts
diff --git a/examples/npm-stats/README.md b/examples/studio/npm-stats/README.md
similarity index 94%
rename from examples/npm-stats/README.md
rename to examples/studio/npm-stats/README.md
index 61c0b4c9a89..9fcc4e974a2 100644
--- a/examples/npm-stats/README.md
+++ b/examples/studio/npm-stats/README.md
@@ -28,7 +28,7 @@ pnpm create toolpad-app --example npm-stats
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/npm-stats)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/npm-stats)
## What's inside
@@ -41,4 +41,4 @@ This app demonstrates the following capabilities of Toolpad Studio:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/npm-stats)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/npm-stats)
diff --git a/examples/npm-stats/package.json b/examples/studio/npm-stats/package.json
similarity index 100%
rename from examples/npm-stats/package.json
rename to examples/studio/npm-stats/package.json
diff --git a/examples/npm-stats/toolpad/.gitignore b/examples/studio/npm-stats/toolpad/.gitignore
similarity index 100%
rename from examples/npm-stats/toolpad/.gitignore
rename to examples/studio/npm-stats/toolpad/.gitignore
diff --git a/examples/npm-stats/toolpad/pages/page/page.yml b/examples/studio/npm-stats/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/npm-stats/toolpad/pages/page/page.yml
rename to examples/studio/npm-stats/toolpad/pages/page/page.yml
diff --git a/examples/qr-generator/README.md b/examples/studio/qr-generator/README.md
similarity index 93%
rename from examples/qr-generator/README.md
rename to examples/studio/qr-generator/README.md
index 590b139c6f8..a38b49a379a 100644
--- a/examples/qr-generator/README.md
+++ b/examples/studio/qr-generator/README.md
@@ -28,7 +28,7 @@ pnpm create toolpad-app --example qr-generator
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/qr-generator)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/qr-generator)
## What's inside
@@ -41,4 +41,4 @@ This app demonstrates the following capabilities of Toolpad Studio:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/qr-generator)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/qr-generator)
diff --git a/examples/qr-generator/package.json b/examples/studio/qr-generator/package.json
similarity index 100%
rename from examples/qr-generator/package.json
rename to examples/studio/qr-generator/package.json
diff --git a/examples/qr-generator/toolpad/.gitignore b/examples/studio/qr-generator/toolpad/.gitignore
similarity index 100%
rename from examples/qr-generator/toolpad/.gitignore
rename to examples/studio/qr-generator/toolpad/.gitignore
diff --git a/examples/qr-generator/toolpad/pages/qrcode/page.yml b/examples/studio/qr-generator/toolpad/pages/qrcode/page.yml
similarity index 100%
rename from examples/qr-generator/toolpad/pages/qrcode/page.yml
rename to examples/studio/qr-generator/toolpad/pages/qrcode/page.yml
diff --git a/examples/qr-generator/toolpad/resources/functions.ts b/examples/studio/qr-generator/toolpad/resources/functions.ts
similarity index 100%
rename from examples/qr-generator/toolpad/resources/functions.ts
rename to examples/studio/qr-generator/toolpad/resources/functions.ts
diff --git a/examples/react-pages/package.json b/examples/studio/react-pages/package.json
similarity index 100%
rename from examples/react-pages/package.json
rename to examples/studio/react-pages/package.json
diff --git a/examples/charts/.gitignore b/examples/studio/stripe-script/.gitignore
similarity index 96%
rename from examples/charts/.gitignore
rename to examples/studio/stripe-script/.gitignore
index a56149af866..826df8851c4 100644
--- a/examples/charts/.gitignore
+++ b/examples/studio/stripe-script/.gitignore
@@ -119,7 +119,7 @@ dist
# TernJS port file
.tern-port
-# Stores VSCode versions used for testing VSCode extensions
+# Stores VS Code versions used for testing VS Code extensions
.vscode-test
# yarn v2
diff --git a/examples/stripe-script/README.md b/examples/studio/stripe-script/README.md
similarity index 96%
rename from examples/stripe-script/README.md
rename to examples/studio/stripe-script/README.md
index 627207c16eb..32f4d8b4a76 100644
--- a/examples/stripe-script/README.md
+++ b/examples/studio/stripe-script/README.md
@@ -38,4 +38,4 @@ The app involves the following workflow:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/stripe-script)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/stripe-script)
diff --git a/examples/stripe-script/package.json b/examples/studio/stripe-script/package.json
similarity index 100%
rename from examples/stripe-script/package.json
rename to examples/studio/stripe-script/package.json
diff --git a/examples/stripe-script/toolpad/.gitignore b/examples/studio/stripe-script/toolpad/.gitignore
similarity index 100%
rename from examples/stripe-script/toolpad/.gitignore
rename to examples/studio/stripe-script/toolpad/.gitignore
diff --git a/examples/stripe-script/toolpad/pages/page/page.yml b/examples/studio/stripe-script/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/stripe-script/toolpad/pages/page/page.yml
rename to examples/studio/stripe-script/toolpad/pages/page/page.yml
diff --git a/examples/stripe-script/toolpad/resources/functions.ts b/examples/studio/stripe-script/toolpad/resources/functions.ts
similarity index 100%
rename from examples/stripe-script/toolpad/resources/functions.ts
rename to examples/studio/stripe-script/toolpad/resources/functions.ts
diff --git a/examples/stripe-script/toolpad/resources/stripe.ts b/examples/studio/stripe-script/toolpad/resources/stripe.ts
similarity index 100%
rename from examples/stripe-script/toolpad/resources/stripe.ts
rename to examples/studio/stripe-script/toolpad/resources/stripe.ts
diff --git a/examples/studio/supabase/.gitignore b/examples/studio/supabase/.gitignore
new file mode 100644
index 00000000000..826df8851c4
--- /dev/null
+++ b/examples/studio/supabase/.gitignore
@@ -0,0 +1,129 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VS Code versions used for testing VS Code extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
diff --git a/examples/supabase/README.md b/examples/studio/supabase/README.md
similarity index 63%
rename from examples/supabase/README.md
rename to examples/studio/supabase/README.md
index e596fe6d4bb..8e1fb2e7397 100644
--- a/examples/supabase/README.md
+++ b/examples/studio/supabase/README.md
@@ -2,13 +2,7 @@
A Toolpad Studio app that fetches data from Supabase and shows it in a list component.
-
-
-
-
-## Check out the live app
-
-[Open example](https://mui-toolpad-supabase-production.up.railway.app/prod/pages/page)
+
## How to run
@@ -33,6 +27,8 @@ This app demonstrates the following capabilities of Toolpad Studio:
1. Connecting to Supabase database using custom functions.
2. Using the list component to create a basic product catalogue manager.
+Note: The underlying supabase database is inactive, but you can use this configuration to setup your app.
+
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/supabase)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/supabase)
diff --git a/examples/supabase/package.json b/examples/studio/supabase/package.json
similarity index 100%
rename from examples/supabase/package.json
rename to examples/studio/supabase/package.json
diff --git a/examples/supabase/toolpad/.gitignore b/examples/studio/supabase/toolpad/.gitignore
similarity index 100%
rename from examples/supabase/toolpad/.gitignore
rename to examples/studio/supabase/toolpad/.gitignore
diff --git a/examples/supabase/toolpad/pages/page/page.yml b/examples/studio/supabase/toolpad/pages/page/page.yml
similarity index 100%
rename from examples/supabase/toolpad/pages/page/page.yml
rename to examples/studio/supabase/toolpad/pages/page/page.yml
diff --git a/examples/supabase/toolpad/resources/supabase.ts b/examples/studio/supabase/toolpad/resources/supabase.ts
similarity index 100%
rename from examples/supabase/toolpad/resources/supabase.ts
rename to examples/studio/supabase/toolpad/resources/supabase.ts
diff --git a/examples/tabs/README.md b/examples/studio/tabs/README.md
similarity index 91%
rename from examples/tabs/README.md
rename to examples/studio/tabs/README.md
index cdc10a7450f..34b8307a99c 100644
--- a/examples/tabs/README.md
+++ b/examples/studio/tabs/README.md
@@ -20,7 +20,7 @@ pnpm create toolpad-app --example tabs
or:
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/tabs)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/tabs)
## What's inside
@@ -31,4 +31,4 @@ This app demonstrates the following capabilities of Toolpad:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/tabs)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/tabs)
diff --git a/examples/tabs/package.json b/examples/studio/tabs/package.json
similarity index 100%
rename from examples/tabs/package.json
rename to examples/studio/tabs/package.json
diff --git a/examples/tabs/toolpad/.gitignore b/examples/studio/tabs/toolpad/.gitignore
similarity index 100%
rename from examples/tabs/toolpad/.gitignore
rename to examples/studio/tabs/toolpad/.gitignore
diff --git a/examples/tabs/toolpad/application.yml b/examples/studio/tabs/toolpad/application.yml
similarity index 100%
rename from examples/tabs/toolpad/application.yml
rename to examples/studio/tabs/toolpad/application.yml
diff --git a/examples/tabs/toolpad/pages/Tabs/page.yml b/examples/studio/tabs/toolpad/pages/Tabs/page.yml
similarity index 100%
rename from examples/tabs/toolpad/pages/Tabs/page.yml
rename to examples/studio/tabs/toolpad/pages/Tabs/page.yml
diff --git a/examples/with-prisma-data-provider/.gitignore b/examples/studio/with-prisma-data-provider/.gitignore
similarity index 100%
rename from examples/with-prisma-data-provider/.gitignore
rename to examples/studio/with-prisma-data-provider/.gitignore
diff --git a/examples/with-prisma-data-provider/README.md b/examples/studio/with-prisma-data-provider/README.md
similarity index 95%
rename from examples/with-prisma-data-provider/README.md
rename to examples/studio/with-prisma-data-provider/README.md
index 0b2516ad292..1ac5c31a909 100644
--- a/examples/with-prisma-data-provider/README.md
+++ b/examples/studio/with-prisma-data-provider/README.md
@@ -36,4 +36,4 @@ This app demonstrates the following capabilities of Toolpad:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/with-prisma-data-provider)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/with-prisma-data-provider)
diff --git a/examples/with-prisma-data-provider/package.json b/examples/studio/with-prisma-data-provider/package.json
similarity index 88%
rename from examples/with-prisma-data-provider/package.json
rename to examples/studio/with-prisma-data-provider/package.json
index a73fc238a2f..f7d60ee3369 100644
--- a/examples/with-prisma-data-provider/package.json
+++ b/examples/studio/with-prisma-data-provider/package.json
@@ -14,10 +14,10 @@
"qrcode": "^1.5.3"
},
"devDependencies": {
- "@types/node": "^20.16.11",
+ "@types/node": "^20.17.12",
"@types/qrcode": "^1.5.5",
"prisma": "^5.16.1",
"ts-node": "^10.9.2",
- "typescript": "^5.5.2"
+ "typescript": "^5"
}
}
diff --git a/examples/with-prisma-data-provider/prisma/dev.db b/examples/studio/with-prisma-data-provider/prisma/dev.db
similarity index 100%
rename from examples/with-prisma-data-provider/prisma/dev.db
rename to examples/studio/with-prisma-data-provider/prisma/dev.db
diff --git a/examples/with-prisma-data-provider/prisma/migrations/20230704094414_init/migration.sql b/examples/studio/with-prisma-data-provider/prisma/migrations/20230704094414_init/migration.sql
similarity index 100%
rename from examples/with-prisma-data-provider/prisma/migrations/20230704094414_init/migration.sql
rename to examples/studio/with-prisma-data-provider/prisma/migrations/20230704094414_init/migration.sql
diff --git a/examples/with-prisma-data-provider/prisma/migrations/migration_lock.toml b/examples/studio/with-prisma-data-provider/prisma/migrations/migration_lock.toml
similarity index 100%
rename from examples/with-prisma-data-provider/prisma/migrations/migration_lock.toml
rename to examples/studio/with-prisma-data-provider/prisma/migrations/migration_lock.toml
diff --git a/examples/with-prisma-data-provider/prisma/schema.prisma b/examples/studio/with-prisma-data-provider/prisma/schema.prisma
similarity index 100%
rename from examples/with-prisma-data-provider/prisma/schema.prisma
rename to examples/studio/with-prisma-data-provider/prisma/schema.prisma
diff --git a/examples/with-prisma-data-provider/toolpad/.gitignore b/examples/studio/with-prisma-data-provider/toolpad/.gitignore
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/.gitignore
rename to examples/studio/with-prisma-data-provider/toolpad/.gitignore
diff --git a/examples/with-prisma-data-provider/toolpad/application.yml b/examples/studio/with-prisma-data-provider/toolpad/application.yml
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/application.yml
rename to examples/studio/with-prisma-data-provider/toolpad/application.yml
diff --git a/examples/with-prisma-data-provider/toolpad/pages/crud/page.yml b/examples/studio/with-prisma-data-provider/toolpad/pages/crud/page.yml
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/pages/crud/page.yml
rename to examples/studio/with-prisma-data-provider/toolpad/pages/crud/page.yml
diff --git a/examples/with-prisma-data-provider/toolpad/pages/cursorBased/page.yml b/examples/studio/with-prisma-data-provider/toolpad/pages/cursorBased/page.yml
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/pages/cursorBased/page.yml
rename to examples/studio/with-prisma-data-provider/toolpad/pages/cursorBased/page.yml
diff --git a/examples/with-prisma-data-provider/toolpad/pages/indexBased/page.yml b/examples/studio/with-prisma-data-provider/toolpad/pages/indexBased/page.yml
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/pages/indexBased/page.yml
rename to examples/studio/with-prisma-data-provider/toolpad/pages/indexBased/page.yml
diff --git a/examples/with-prisma-data-provider/toolpad/prisma.ts b/examples/studio/with-prisma-data-provider/toolpad/prisma.ts
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/prisma.ts
rename to examples/studio/with-prisma-data-provider/toolpad/prisma.ts
diff --git a/examples/with-prisma-data-provider/toolpad/resources/crud.ts b/examples/studio/with-prisma-data-provider/toolpad/resources/crud.ts
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/resources/crud.ts
rename to examples/studio/with-prisma-data-provider/toolpad/resources/crud.ts
diff --git a/examples/with-prisma-data-provider/toolpad/resources/usersByCursor.ts b/examples/studio/with-prisma-data-provider/toolpad/resources/usersByCursor.ts
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/resources/usersByCursor.ts
rename to examples/studio/with-prisma-data-provider/toolpad/resources/usersByCursor.ts
diff --git a/examples/with-prisma-data-provider/toolpad/resources/usersByIndex.ts b/examples/studio/with-prisma-data-provider/toolpad/resources/usersByIndex.ts
similarity index 100%
rename from examples/with-prisma-data-provider/toolpad/resources/usersByIndex.ts
rename to examples/studio/with-prisma-data-provider/toolpad/resources/usersByIndex.ts
diff --git a/examples/with-prisma-data-provider/tsconfig.json b/examples/studio/with-prisma-data-provider/tsconfig.json
similarity index 100%
rename from examples/with-prisma-data-provider/tsconfig.json
rename to examples/studio/with-prisma-data-provider/tsconfig.json
diff --git a/examples/with-prisma/.gitignore b/examples/studio/with-prisma/.gitignore
similarity index 100%
rename from examples/with-prisma/.gitignore
rename to examples/studio/with-prisma/.gitignore
diff --git a/examples/with-prisma/README.md b/examples/studio/with-prisma/README.md
similarity index 96%
rename from examples/with-prisma/README.md
rename to examples/studio/with-prisma/README.md
index 14260d07def..ff891fd386e 100644
--- a/examples/with-prisma/README.md
+++ b/examples/studio/with-prisma/README.md
@@ -30,4 +30,4 @@ This app demonstrates the following capabilities of Toolpad Studio:
2. Using a Data Grid, Button and a text input component.
3. Using CRUD operations from [Prisma Client API](https://www.prisma.io/docs/concepts/components/prisma-client/crud).
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/with-prisma)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/with-prisma)
diff --git a/examples/with-prisma/package.json b/examples/studio/with-prisma/package.json
similarity index 93%
rename from examples/with-prisma/package.json
rename to examples/studio/with-prisma/package.json
index e00c0a53117..9d76d78a3ac 100644
--- a/examples/with-prisma/package.json
+++ b/examples/studio/with-prisma/package.json
@@ -14,7 +14,7 @@
"qrcode": "^1.5.3"
},
"devDependencies": {
- "@types/node": "^20.16.11",
+ "@types/node": "^20.17.12",
"@types/qrcode": "^1.5.5",
"prisma": "^5.16.1",
"ts-node": "^10.9.2",
diff --git a/examples/with-prisma/prisma/dev.db b/examples/studio/with-prisma/prisma/dev.db
similarity index 100%
rename from examples/with-prisma/prisma/dev.db
rename to examples/studio/with-prisma/prisma/dev.db
diff --git a/examples/with-prisma/prisma/migrations/20230704094414_init/migration.sql b/examples/studio/with-prisma/prisma/migrations/20230704094414_init/migration.sql
similarity index 100%
rename from examples/with-prisma/prisma/migrations/20230704094414_init/migration.sql
rename to examples/studio/with-prisma/prisma/migrations/20230704094414_init/migration.sql
diff --git a/examples/with-prisma/prisma/migrations/migration_lock.toml b/examples/studio/with-prisma/prisma/migrations/migration_lock.toml
similarity index 100%
rename from examples/with-prisma/prisma/migrations/migration_lock.toml
rename to examples/studio/with-prisma/prisma/migrations/migration_lock.toml
diff --git a/examples/with-prisma/prisma/schema.prisma b/examples/studio/with-prisma/prisma/schema.prisma
similarity index 100%
rename from examples/with-prisma/prisma/schema.prisma
rename to examples/studio/with-prisma/prisma/schema.prisma
diff --git a/examples/with-prisma/toolpad/.gitignore b/examples/studio/with-prisma/toolpad/.gitignore
similarity index 100%
rename from examples/with-prisma/toolpad/.gitignore
rename to examples/studio/with-prisma/toolpad/.gitignore
diff --git a/examples/with-prisma/toolpad/pages/users/page.yml b/examples/studio/with-prisma/toolpad/pages/users/page.yml
similarity index 100%
rename from examples/with-prisma/toolpad/pages/users/page.yml
rename to examples/studio/with-prisma/toolpad/pages/users/page.yml
diff --git a/examples/with-prisma/toolpad/resources/functions.ts b/examples/studio/with-prisma/toolpad/resources/functions.ts
similarity index 100%
rename from examples/with-prisma/toolpad/resources/functions.ts
rename to examples/studio/with-prisma/toolpad/resources/functions.ts
diff --git a/examples/with-prisma/tsconfig.json b/examples/studio/with-prisma/tsconfig.json
similarity index 100%
rename from examples/with-prisma/tsconfig.json
rename to examples/studio/with-prisma/tsconfig.json
diff --git a/examples/with-wasm/.gitignore b/examples/studio/with-wasm/.gitignore
similarity index 100%
rename from examples/with-wasm/.gitignore
rename to examples/studio/with-wasm/.gitignore
diff --git a/examples/with-wasm/README.md b/examples/studio/with-wasm/README.md
similarity index 92%
rename from examples/with-wasm/README.md
rename to examples/studio/with-wasm/README.md
index 6acec7f695f..8c7fbe8f4d6 100644
--- a/examples/with-wasm/README.md
+++ b/examples/studio/with-wasm/README.md
@@ -22,7 +22,7 @@ yarn create toolpad-app --example with-wasm
pnpm create toolpad-app --example with-wasm
```
-[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/with-wasm)
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/mui/toolpad/tree/master/examples/studio/with-wasm)
## What's inside
@@ -33,4 +33,4 @@ This app demonstrates the following capabilities of Toolpad:
## The source
-[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/with-wasm)
+[Check out the source code](https://github.com/mui/toolpad/tree/master/examples/studio/with-wasm)
diff --git a/examples/with-wasm/my-wasm-module.ts b/examples/studio/with-wasm/my-wasm-module.ts
similarity index 100%
rename from examples/with-wasm/my-wasm-module.ts
rename to examples/studio/with-wasm/my-wasm-module.ts
diff --git a/examples/with-wasm/package.json b/examples/studio/with-wasm/package.json
similarity index 100%
rename from examples/with-wasm/package.json
rename to examples/studio/with-wasm/package.json
diff --git a/examples/with-wasm/toolpad/.gitignore b/examples/studio/with-wasm/toolpad/.gitignore
similarity index 100%
rename from examples/with-wasm/toolpad/.gitignore
rename to examples/studio/with-wasm/toolpad/.gitignore
diff --git a/examples/with-wasm/toolpad/pages/wasm/page.yml b/examples/studio/with-wasm/toolpad/pages/wasm/page.yml
similarity index 100%
rename from examples/with-wasm/toolpad/pages/wasm/page.yml
rename to examples/studio/with-wasm/toolpad/pages/wasm/page.yml
diff --git a/examples/with-wasm/toolpad/resources/functions.ts b/examples/studio/with-wasm/toolpad/resources/functions.ts
similarity index 100%
rename from examples/with-wasm/toolpad/resources/functions.ts
rename to examples/studio/with-wasm/toolpad/resources/functions.ts
diff --git a/examples/supabase/.gitignore b/examples/supabase/.gitignore
deleted file mode 100644
index a56149af866..00000000000
--- a/examples/supabase/.gitignore
+++ /dev/null
@@ -1,129 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-.pnpm-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# Snowpack dependency directory (https://snowpack.dev/)
-web_modules/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional stylelint cache
-.stylelintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variable files
-.env
-.env.development.local
-.env.test.local
-.env.production.local
-.env.local
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-.parcel-cache
-
-# Next.js build output
-.next
-out
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and not Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# vuepress v2.x temp and cache directory
-.temp
-.cache
-
-# Docusaurus cache and generated files
-.docusaurus
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# TernJS port file
-.tern-port
-
-# Stores VSCode versions used for testing VSCode extensions
-.vscode-test
-
-# yarn v2
-.yarn/cache
-.yarn/unplugged
-.yarn/build-state.yml
-.yarn/install-state.gz
diff --git a/lerna.json b/lerna.json
index ec00344c01c..99b18d03402 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
- "version": "0.7.0",
+ "version": "0.12.0",
"npmClient": "pnpm"
}
diff --git a/package.json b/package.json
index d67360d8a28..fa63d05b25e 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"markdownlint": "markdownlint-cli2 \"**/*.md\"",
"prettier": "pretty-quick --ignore-path .eslintignore",
"prettier:all": "prettier --write . --ignore-path .eslintignore",
- "dev": "dotenv cross-env FORCE_COLOR=1 lerna -- run dev --stream --parallel --ignore docs --ignore playground-nextjs --ignore playground-nextjs-pages --ignore playground-vite",
+ "dev": "dotenv cross-env FORCE_COLOR=1 lerna -- run dev --stream --parallel --ignore docs --ignore playground-*",
"docs:dev": "pnpm --filter docs dev",
"docs:build": "pnpm --filter docs build",
"docs:build:api:core": "tsx --tsconfig ./scripts/tsconfig.json ./scripts/docs/buildCoreApiDocs/index.ts",
@@ -36,7 +36,6 @@
"check-types": "lerna run --concurrency 1 check-types",
"toolpad-studio": "node --enable-source-maps packages/toolpad-studio/cli.mjs",
"jsonSchemas": "tsx ./scripts/docs/generateJsonSchemas.ts",
- "update-monorepo": "tsx ./scripts/updateMonorepo.ts",
"monorepo:update": "tsx ./scripts/updateMonorepo.ts",
"monorepo:canary": "tsx ./scripts/canaryMonorepo.ts",
"check-changes": "git add -A && git diff --exit-code --staged",
@@ -44,99 +43,101 @@
"clean": "pnpm -r exec rm -rf build dist"
},
"devDependencies": {
- "@argos-ci/core": "2.8.1",
- "@babel/cli": "7.25.7",
- "@babel/core": "7.25.8",
- "@babel/node": "7.25.7",
- "@babel/plugin-transform-react-constant-elements": "7.25.7",
- "@babel/plugin-transform-runtime": "7.25.7",
- "@babel/preset-env": "7.25.8",
- "@babel/preset-react": "7.25.7",
- "@babel/preset-typescript": "7.25.7",
- "@mui/internal-babel-plugin-resolve-imports": "1.0.18",
- "@mui/internal-docs-utils": "1.0.14",
- "@mui/internal-markdown": "1.0.16",
- "@mui/internal-scripts": "1.0.23",
- "@mui/monorepo": "github:mui/material-ui#a49333cc4988f60e8d402ba069a65345c92a6c8d",
- "@mui/x-charts": "7.21.0",
- "@next/eslint-plugin-next": "14.2.15",
+ "@argos-ci/core": "2.11.0",
+ "@babel/cli": "7.25.9",
+ "@babel/core": "7.26.0",
+ "@babel/node": "7.26.0",
+ "@babel/plugin-transform-react-constant-elements": "7.25.9",
+ "@babel/plugin-transform-runtime": "7.25.9",
+ "@babel/preset-env": "7.26.0",
+ "@babel/preset-react": "7.25.9",
+ "@babel/preset-typescript": "7.26.0",
+ "@mui/internal-babel-plugin-resolve-imports": "1.0.20",
+ "@mui/internal-docs-utils": "1.0.16",
+ "@mui/internal-markdown": "1.0.24",
+ "@mui/internal-scripts": "1.0.32",
+ "@mui/monorepo": "github:mui/material-ui#dd69cf07e7aace1efad91e5b8e733c7efcf6c02c",
+ "@mui/x-charts": "7.23.6",
+ "@next/eslint-plugin-next": "14.2.23",
"@playwright/test": "1.47.2",
- "@testing-library/jest-dom": "^6.5.0",
- "@testing-library/react": "16.0.1",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "16.1.0",
"@testing-library/user-event": "14.5.2",
- "@types/archiver": "6.0.2",
+ "@types/archiver": "6.0.3",
"@types/fs-extra": "11.0.4",
"@types/gtag.js": "0.0.20",
"@types/invariant": "2.2.37",
- "@types/node": "^20.16.11",
+ "@types/node": "^20.17.12",
"@types/yargs": "17.0.33",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
- "@vitest/coverage-v8": "2.1.2",
+ "@vitest/coverage-v8": "2.1.8",
"babel-plugin-react-remove-properties": "0.3.0",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"chalk": "5.3.0",
- "concurrently": "9.0.1",
+ "concurrently": "9.1.0",
+ "css-mediaquery": "^0.1.2",
+ "danger": "^12.3.3",
"eslint": "8.57.1",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "18.0.0",
"eslint-config-prettier": "9.1.0",
- "eslint-import-resolver-webpack": "0.13.9",
+ "eslint-import-resolver-webpack": "0.13.10",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.31.0",
- "eslint-plugin-jsx-a11y": "6.10.0",
+ "eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-material-ui": "workspace:*",
"eslint-plugin-mocha": "10.5.0",
- "eslint-plugin-react": "7.37.1",
+ "eslint-plugin-react": "7.37.3",
"eslint-plugin-react-compiler": "latest",
- "eslint-plugin-react-hooks": "4.6.2",
- "eslint-plugin-testing-library": "^6.3.0",
+ "eslint-plugin-react-hooks": "5.1.0",
+ "eslint-plugin-testing-library": "^6.5.0",
"eslint-plugin-typescript-enum": "2.1.0",
- "execa": "9.4.0",
+ "execa": "9.5.2",
"fast-glob": "^3.3.2",
"format-util": "1.0.5",
"fs-extra": "11.2.0",
"globby": "14.0.2",
"jsdom": "25.0.1",
- "lerna": "8.1.8",
- "markdownlint-cli2": "0.14.0",
- "prettier": "3.3.3",
+ "lerna": "8.1.9",
+ "markdownlint-cli2": "0.15.0",
+ "prettier": "3.4.1",
"pretty-quick": "4.0.0",
"react-docgen": "5.4.3",
"react-inspector": "6.0.2",
"regenerator-runtime": "0.14.1",
"remark": "15.0.1",
"rimraf": "6.0.1",
- "typescript": "5.6.2",
+ "typescript": "5.5.4",
"unist-util-visit": "5.0.0",
"vitest-dom": "0.1.1",
"vitest-fail-on-console": "0.7.1"
},
"dependencies": {
- "@types/lodash": "4.17.10",
- "@vitest/browser": "2.1.2",
+ "@types/lodash": "4.17.14",
+ "@vitest/browser": "2.1.8",
"archiver": "7.0.1",
"cross-env": "7.0.3",
- "dotenv-cli": "7.4.2",
+ "dotenv-cli": "7.4.4",
"eslint-import-resolver-exports": "1.0.0-beta.5",
"invariant": "2.2.4",
"lodash": "4.17.21",
"react-swipeable-views": "^0.14.0",
"semver": "7.6.3",
- "tsup": "8.3.0",
- "tsx": "4.19.1",
- "vitest": "2.1.2",
+ "tsup": "8.3.5",
+ "tsx": "4.19.2",
+ "vitest": "2.1.8",
"yargs": "17.7.2",
"zod": "3.23.8",
- "zod-to-json-schema": "3.23.3"
+ "zod-to-json-schema": "3.23.5"
},
"engines": {
"node": ">=20",
- "pnpm": "9.12.1"
+ "pnpm": "9.12.3"
},
"resolutions": {
"google-auth-library": "*",
- "@types/node": "^20.16.11"
+ "@types/node": "^20.17.12"
},
- "packageManager": "pnpm@9.12.1"
+ "packageManager": "pnpm@9.12.3"
}
diff --git a/packages/create-toolpad-app/package.json b/packages/create-toolpad-app/package.json
index e1bf1169bee..b5e4443fb13 100644
--- a/packages/create-toolpad-app/package.json
+++ b/packages/create-toolpad-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-toolpad-app",
- "version": "0.7.0",
+ "version": "0.12.0",
"keywords": [
"react",
"toolpad",
@@ -34,7 +34,7 @@
"@toolpad/core": "workspace:*",
"@toolpad/utils": "workspace:*",
"chalk": "5.3.0",
- "execa": "9.4.0",
+ "execa": "9.5.2",
"invariant": "2.2.4",
"semver": "7.6.3",
"tar": "7.4.3",
@@ -43,7 +43,7 @@
"devDependencies": {
"@types/inquirer": "9.0.7",
"@types/invariant": "2.2.37",
- "@types/node": "^20.16.11",
+ "@types/node": "^20.17.12",
"@types/semver": "7.5.8",
"@types/tar": "6.1.13",
"@types/yargs": "17.0.33",
diff --git a/packages/create-toolpad-app/public/templates/gitignoreTemplate b/packages/create-toolpad-app/public/templates/gitignoreTemplate
index c8142b9775a..fb4775ed548 100644
--- a/packages/create-toolpad-app/public/templates/gitignoreTemplate
+++ b/packages/create-toolpad-app/public/templates/gitignoreTemplate
@@ -154,7 +154,7 @@ dist
.tern-port
-# Stores VSCode versions used for testing VSCode extensions
+# Stores VS Code versions used for testing VS Code extensions
.vscode-test
diff --git a/packages/create-toolpad-app/src/examples.ts b/packages/create-toolpad-app/src/examples.ts
index d979fb5d6cb..af0c24f1ca3 100644
--- a/packages/create-toolpad-app/src/examples.ts
+++ b/packages/create-toolpad-app/src/examples.ts
@@ -47,7 +47,9 @@ export async function downloadAndExtractExample(root: string, name: string) {
file: tempFile,
cwd: root,
strip: 2 + name.split('/').length,
- filter: (p) => p.includes(`toolpad-master/examples/${name}/`),
+ filter: (p) =>
+ p.includes(`toolpad-master/examples/studio/${name}/`) ||
+ p.includes(`toolpad-master/examples/core/${name}/`),
});
// eslint-disable-next-line no-console
diff --git a/packages/create-toolpad-app/src/generateProject.ts b/packages/create-toolpad-app/src/generateProject.ts
index 7a996579f83..0ea3b8e7641 100644
--- a/packages/create-toolpad-app/src/generateProject.ts
+++ b/packages/create-toolpad-app/src/generateProject.ts
@@ -1,49 +1,62 @@
// Common files for all apps
import theme from './templates/theme';
import eslintConfig from './templates/eslintConfig';
-import nextConfig from './templates/nextConfig';
-import nextTypes from './templates/nextTypes';
-import tsConfig from './templates/tsConfig';
import readme from './templates/readme';
import gitignore from './templates/gitignore';
import ordersPage from './templates/ordersPage';
import packageJson from './templates/packageJson';
import indexPage from './templates/indexPage';
+// Vite specific files
+import viteApp from './templates/vite/App';
+import viteConfig from './templates/vite/viteConfig';
+import viteMain from './templates/vite/main';
+import viteHtml from './templates/vite/html';
+import viteDashboardLayout from './templates/vite/dashboardLayout';
+// Vite Auth specific files
+import viteSessionContext from './templates/vite/SessionContext';
+import viteSignIn from './templates/vite/auth/signin';
+import viteEnv from './templates/vite/auth/env';
+import viteFirebaseAuth from './templates/vite/auth/firebase';
+import viteFirebaseConfig from './templates/vite/auth/firebaseConfig';
+
+// Nextjs specific files
+import tsConfig from './templates/tsConfig';
+import nextConfig from './templates/nextConfig';
+import nextTypes from './templates/nextTypes';
// App router specific files
-import rootLayout from './templates/nextjs-app/rootLayout';
-import dashboardLayout from './templates/nextjs-app/dashboardLayout';
+import rootLayout from './templates/nextjs/nextjs-app/rootLayout';
+import dashboardLayout from './templates/nextjs/nextjs-app/dashboardLayout';
// Pages router specific files
-import app from './templates/nextjs-pages/app';
-import document from './templates/nextjs-pages/document';
+import app from './templates/nextjs/nextjs-pages/app';
+import document from './templates/nextjs/nextjs-pages/document';
// Auth specific files for all apps
-import auth from './templates/auth/auth';
-import envLocal from './templates/auth/envLocal';
-import middleware from './templates/auth/middleware';
-import routeHandler from './templates/auth/route';
+import auth from './templates/nextjs/auth/auth';
+import envLocal from './templates/nextjs/auth/envLocal';
+import middleware from './templates/nextjs/auth/middleware';
+import routeHandler from './templates/nextjs/auth/route';
+import prisma from './templates/nextjs/auth/prisma';
+import env from './templates/nextjs/auth/env';
+import schemaPrisma from './templates/nextjs/auth/schemaPrisma';
// Auth files for app router
-import signInPage from './templates/auth/nextjs-app/signInPage';
+import signInPage from './templates/nextjs/auth/nextjs-app/signInPage';
+import signInAction from './templates/nextjs/auth/nextjs-app/actions';
// Auth files for pages router
-import signInPagePagesRouter from './templates/auth/nextjs-pages/signIn';
+import signInPagePagesRouter from './templates/nextjs/auth/nextjs-pages/signIn';
import { GenerateProjectOptions } from './types';
export default function generateProject(
options: GenerateProjectOptions,
): Map {
- // Add app name to package.json
-
- // Default files, common to all apps
- const files = new Map([
+ // Common files regardless of framework
+ const commonFiles = new Map([
['theme.ts', { content: theme }],
- ['next-env.d.ts', { content: nextTypes }],
- ['next.config.mjs', { content: nextConfig }],
['.eslintrc.json', { content: eslintConfig }],
- ['tsconfig.json', { content: tsConfig }],
['README.md', { content: readme }],
['.gitignore', { content: gitignore }],
[
@@ -53,51 +66,130 @@ export default function generateProject(
},
],
]);
- const indexPageContent = indexPage(options);
- switch (options.router) {
- case 'nextjs-pages': {
- const nextJsPagesRouterStarter = new Map([
- ['pages/index.tsx', { content: indexPageContent }],
- ['pages/orders/index.tsx', { content: ordersPage }],
- ['pages/_document.tsx', { content: document }],
- ['pages/_app.tsx', { content: app(options) }],
+ switch (options.framework) {
+ case 'vite': {
+ const viteFiles = new Map([
+ ['vite.config.mts', { content: viteConfig }],
+ ['src/main.tsx', { content: viteMain(options) }],
+ ['src/layouts/dashboard.tsx', { content: viteDashboardLayout(options) }],
+ ['src/App.tsx', { content: viteApp(options) }],
+ ['src/pages/index.tsx', { content: indexPage(options) }],
+ ['src/pages/orders.tsx', { content: ordersPage(options) }],
+ ['index.html', { content: viteHtml }],
]);
+
if (options.auth) {
const authFiles = new Map([
- ['auth.ts', { content: auth(options) }],
- ['.env.local', { content: envLocal(options) }],
- ['middleware.ts', { content: middleware }],
- // next-auth v5 does not provide an API route, so this file must be in the app router
- // even if the rest of the app is using pages router
- // https://authjs.dev/getting-started/installation#configure
- ['app/api/auth/[...nextAuth]/route.ts', { content: routeHandler }],
- ['pages/auth/signin.tsx', { content: signInPagePagesRouter(options) }],
+ ['src/firebase/auth.ts', { content: viteFirebaseAuth(options) }],
+ ['src/firebase/firebaseConfig.ts', { content: viteFirebaseConfig(options) }],
+ ['src/SessionContext.ts', { content: viteSessionContext }],
+ ['.env', { content: viteEnv }],
+ ['src/pages/signin.tsx', { content: viteSignIn(options) }],
]);
- return new Map([...files, ...nextJsPagesRouterStarter, ...authFiles]);
+
+ return new Map([...commonFiles, ...viteFiles, ...authFiles]);
}
- return new Map([...files, ...nextJsPagesRouterStarter]);
+
+ return new Map([...commonFiles, ...viteFiles]);
}
- case 'nextjs-app':
+
+ case 'nextjs':
default: {
- const nextJsAppRouterStarter = new Map([
- ['app/(dashboard)/layout.tsx', { content: dashboardLayout }],
- ['app/layout.tsx', { content: rootLayout(options) }],
- ['app/(dashboard)/page.tsx', { content: indexPageContent }],
- ['app/(dashboard)/orders/page.tsx', { content: ordersPage }],
+ const nextCommonFiles = new Map([
+ ['tsconfig.json', { content: tsConfig }],
+ ['next-env.d.ts', { content: nextTypes }],
+ ['next.config.mjs', { content: nextConfig(options) }],
]);
- if (options.auth) {
- const authFiles = new Map([
- ['auth.ts', { content: auth(options) }],
- ['.env.local', { content: envLocal(options) }],
- ['middleware.ts', { content: middleware }],
- ['app/api/auth/[...nextAuth]/route.ts', { content: routeHandler }],
- ['app/auth/signin/page.tsx', { content: signInPage(options) }],
- ]);
- return new Map([...files, ...nextJsAppRouterStarter, ...authFiles]);
+ switch (options.router) {
+ case 'nextjs-pages': {
+ const nextJsPagesRouterStarter = new Map([
+ ['pages/index.tsx', { content: indexPage(options) }],
+ ['pages/orders/index.tsx', { content: ordersPage(options) }],
+ ['pages/_document.tsx', { content: document }],
+ ['pages/_app.tsx', { content: app(options) }],
+ ]);
+ if (options.auth) {
+ const authFiles = new Map([
+ ['auth.ts', { content: auth(options) }],
+ ['.env.local', { content: envLocal(options) }],
+ ['middleware.ts', { content: middleware }],
+ // next-auth v5 does not provide an API route, so this file must be in the app router
+ // even if the rest of the app is using pages router
+ // https://authjs.dev/getting-started/installation#configure
+ ['app/api/auth/[...nextAuth]/route.ts', { content: routeHandler }],
+ ['pages/auth/signin.tsx', { content: signInPagePagesRouter(options) }],
+ ]);
+ if (options.hasNodemailerProvider || options.hasPasskeyProvider) {
+ // Prisma adapter support requires removal of middleware
+ authFiles.delete('middleware.ts');
+ const prismaFiles = new Map([
+ ['prisma.ts', { content: prisma }],
+ ['.env', { content: env }],
+ ['prisma/schema.prisma', { content: schemaPrisma(options) }],
+ ]);
+ return new Map([
+ ...commonFiles,
+ ...nextCommonFiles,
+ ...nextJsPagesRouterStarter,
+ ...authFiles,
+ ...prismaFiles,
+ ]);
+ }
+ return new Map([
+ ...commonFiles,
+ ...nextCommonFiles,
+ ...nextJsPagesRouterStarter,
+ ...authFiles,
+ ]);
+ }
+ return new Map([...commonFiles, ...nextCommonFiles, ...nextJsPagesRouterStarter]);
+ }
+ case 'nextjs-app':
+ default: {
+ const nextJsAppRouterStarter = new Map([
+ ['app/(dashboard)/layout.tsx', { content: dashboardLayout }],
+ ['app/layout.tsx', { content: rootLayout(options) }],
+ ['app/(dashboard)/page.tsx', { content: indexPage(options) }],
+ ['app/(dashboard)/orders/page.tsx', { content: ordersPage(options) }],
+ ]);
+ if (options.auth) {
+ const authFiles = new Map([
+ ['auth.ts', { content: auth(options) }],
+ ['.env.local', { content: envLocal(options) }],
+ ['middleware.ts', { content: middleware }],
+ ['app/api/auth/[...nextAuth]/route.ts', { content: routeHandler }],
+ ['app/auth/signin/page.tsx', { content: signInPage(options) }],
+ ['app/auth/signin/actions.ts', { content: signInAction(options) }],
+ ]);
+ if (options.hasNodemailerProvider || options.hasPasskeyProvider) {
+ // Prisma adapater support requires removal of middleware
+ authFiles.delete('middleware.ts');
+ const prismaFiles = new Map([
+ ['prisma.ts', { content: prisma }],
+ ['.env', { content: env }],
+ ['prisma/schema.prisma', { content: schemaPrisma(options) }],
+ ]);
+ return new Map([
+ ...commonFiles,
+ ...nextCommonFiles,
+ ...nextJsAppRouterStarter,
+ ...authFiles,
+ ...prismaFiles,
+ ]);
+ }
+
+ return new Map([
+ ...commonFiles,
+ ...nextCommonFiles,
+ ...nextJsAppRouterStarter,
+ ...authFiles,
+ ]);
+ }
+ return new Map([...commonFiles, ...nextCommonFiles, ...nextJsAppRouterStarter]);
+ }
}
- return new Map([...files, ...nextJsAppRouterStarter]);
}
}
}
diff --git a/packages/create-toolpad-app/src/index.ts b/packages/create-toolpad-app/src/index.ts
index fd202baab3a..e7d1f578cde 100644
--- a/packages/create-toolpad-app/src/index.ts
+++ b/packages/create-toolpad-app/src/index.ts
@@ -18,7 +18,12 @@ import generateStudioProject from './generateStudioProject';
import writeFiles from './writeFiles';
import { downloadAndExtractExample } from './examples';
import type { PackageJson } from './templates/packageType';
-import type { SupportedRouter, PackageManager, GenerateProjectOptions } from './types';
+import type {
+ SupportedFramework,
+ SupportedRouter,
+ PackageManager,
+ GenerateProjectOptions,
+} from './types';
/**
* Find package.json of the create-toolpad-app package
@@ -54,7 +59,6 @@ function getPackageManager(): PackageManager {
}
// From https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/is-folder-empty.ts
-
async function isFolderEmpty(pathDir: string): Promise {
const validFiles = [
'.DS_Store',
@@ -102,6 +106,7 @@ const validatePath = async (relativePath: string): Promise =>
await fs.access(absolutePath, fsConstants.F_OK);
// Directory exists, verify if it's empty to proceed
+
if (await isFolderEmpty(absolutePath)) {
return true;
}
@@ -272,13 +277,18 @@ const run = async () => {
message: example
? `Enter path of directory to download example "${chalk.cyan(example)}" into`
: 'Enter path of directory to bootstrap new app',
- validate: validatePath,
+ // This check is only necessary if an empty app is being bootstrapped,
+ // not if an example is being downloaded.
+ validate: example ? () => true : validatePath,
default: '.',
});
}
const absolutePath = bashResolvePath(projectPath);
+ let hasNodemailerProvider = false;
+ let hasPasskeyProvider = false;
+
// If the user has provided an example, download and extract it
if (example) {
await downloadAndExtractExample(absolutePath, example);
@@ -289,14 +299,28 @@ const run = async () => {
await scaffoldStudioProject(absolutePath, installFlag);
} else {
// Otherwise, create a new project with Toolpad Core
- const routerOption: SupportedRouter = await select({
- message: 'Which router would you like to use?',
- default: 'nextjs-app',
+ const frameworkOption: SupportedFramework = await select({
+ message: 'Which framework would you like to use?',
+ default: 'nextjs',
choices: [
- { name: 'Next.js App Router', value: 'nextjs-app' },
- { name: 'Next.js Pages Router', value: 'nextjs-pages' },
+ { name: 'Next.js', value: 'nextjs' },
+ { name: 'Vite', value: 'vite' },
],
});
+
+ let routerOption: SupportedRouter | undefined;
+
+ if (frameworkOption === 'nextjs') {
+ routerOption = await select({
+ message: 'Which router would you like to use?',
+ default: 'nextjs-app',
+ choices: [
+ { name: 'Next.js App Router', value: 'nextjs-app' },
+ { name: 'Next.js Pages Router', value: 'nextjs-pages' },
+ ],
+ });
+ }
+
const authFlag = await confirm({
message: 'Would you like to enable authentication?',
default: true,
@@ -306,38 +330,55 @@ const run = async () => {
authProviderOptions = await checkbox({
message: 'Select authentication providers to enable:',
required: true,
- choices: [
- { name: 'Google', value: 'google' },
- { name: 'GitHub', value: 'github' },
- { name: 'GitLab', value: 'gitlab' },
- { name: 'Twitter', value: 'twitter' },
- { name: 'Facebook', value: 'facebook' },
- { name: 'Cognito', value: 'cognito' },
- { name: 'Microsoft Entra ID', value: 'microsoft-entra-id' },
- { name: 'Apple', value: 'apple' },
- { name: 'Instagram', value: 'instagram' },
- { name: 'TikTok', value: 'tiktok' },
- { name: 'LinkedIn', value: 'linkedin' },
- { name: 'Slack', value: 'slack' },
- { name: 'Spotify', value: 'spotify' },
- { name: 'Twitch', value: 'twitch' },
- { name: 'Discord', value: 'discord' },
- { name: 'Line', value: 'line' },
- { name: 'Auth0', value: 'auth0' },
- { name: 'Keycloak', value: 'keycloak' },
- { name: 'Okta', value: 'okta' },
- { name: 'FusionAuth', value: 'fusionauth' },
- ],
+ choices:
+ frameworkOption === 'nextjs'
+ ? [
+ { name: 'Google', value: 'google' },
+ { name: 'GitHub', value: 'github' },
+ { name: 'Passkey', value: 'passkey' },
+ { name: 'Magic Link', value: 'nodemailer' },
+ { name: 'Credentials', value: 'credentials' },
+ { name: 'GitLab', value: 'gitlab' },
+ { name: 'Twitter', value: 'twitter' },
+ { name: 'Facebook', value: 'facebook' },
+ { name: 'Cognito', value: 'cognito' },
+ { name: 'Microsoft Entra ID', value: 'microsoft-entra-id' },
+ { name: 'Apple', value: 'apple' },
+ { name: 'Instagram', value: 'instagram' },
+ { name: 'TikTok', value: 'tiktok' },
+ { name: 'LinkedIn', value: 'linkedin' },
+ { name: 'Slack', value: 'slack' },
+ { name: 'Spotify', value: 'spotify' },
+ { name: 'Twitch', value: 'twitch' },
+ { name: 'Discord', value: 'discord' },
+ { name: 'Line', value: 'line' },
+ { name: 'Auth0', value: 'auth0' },
+ { name: 'Keycloak', value: 'keycloak' },
+ { name: 'Okta', value: 'okta' },
+ { name: 'FusionAuth', value: 'fusionauth' },
+ ]
+ : [
+ { name: 'Google', value: 'google' },
+ { name: 'GitHub', value: 'github' },
+ { name: 'Credentials', value: 'credentials' },
+ ],
});
+ hasNodemailerProvider = authProviderOptions?.includes('nodemailer');
+ hasPasskeyProvider = authProviderOptions?.includes('passkey');
}
+
const options = {
name: path.basename(absolutePath),
absolutePath,
coreVersion: args.coreVersion,
router: routerOption,
+ framework: frameworkOption,
auth: authFlag,
install: installFlag,
authProviders: authProviderOptions,
+ hasCredentialsProvider: authProviderOptions?.includes('credentials'),
+ hasNodemailerProvider,
+ hasPasskeyProvider,
};
await scaffoldCoreProject(options);
}
@@ -353,11 +394,17 @@ const run = async () => {
const installInstruction = example || !installFlag ? ` ${packageManager} install\n` : '';
+ const databaseInstruction =
+ hasNodemailerProvider || hasPasskeyProvider
+ ? ` npx prisma migrate dev --schema=prisma/schema.prisma\n`
+ : '';
+
const message = `Run the following to get started: \n\n${chalk.magentaBright(
- `${changeDirectoryInstruction}${installInstruction} ${packageManager}${
+ `${changeDirectoryInstruction}${databaseInstruction}${installInstruction} ${packageManager}${
packageManager === 'yarn' ? '' : ' run'
} dev`,
)}`;
+
// eslint-disable-next-line no-console
console.log(message);
// eslint-disable-next-line no-console
diff --git a/packages/create-toolpad-app/src/templates/auth/auth.ts b/packages/create-toolpad-app/src/templates/auth/auth.ts
deleted file mode 100644
index 89678eaf477..00000000000
--- a/packages/create-toolpad-app/src/templates/auth/auth.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import type { SupportedAuthProvider } from '@toolpad/core/SignInPage';
-import { kebabToConstant, kebabToPascal } from '@toolpad/utils/strings';
-import { requiresIssuer, requiresTenantId } from './utils';
-import { Template } from '../../types';
-
-const CredentialsProviderTemplate = `Credentials({
- credentials: {
- email: { label: 'Email Address', type: 'email' },
- password: { label: 'Password', type: 'password' },
- },
- authorize(c) {
- if (c.password !== 'password') {
- return null;
- }
- return {
- id: 'test',
- name: 'Test User',
- email: String(c.email),
- };
- },
-}),`;
-
-const oAuthProviderTemplate = (provider: SupportedAuthProvider) => `
- ${kebabToPascal(provider)}({
- clientId: process.env.${kebabToConstant(provider)}_CLIENT_ID,
- clientSecret: process.env.${kebabToConstant(provider)}_CLIENT_SECRET,${requiresIssuer(provider) ? `\nissuer: process.env.${kebabToConstant(provider)}_ISSUER,\n` : ''}${requiresTenantId(provider) ? `tenantId: process.env.${kebabToConstant(provider)}_TENANT_ID,` : ''}
- }),`;
-const checkEnvironmentVariables = (
- providers: SupportedAuthProvider[] | undefined,
-) => `const missingVars: string[] = [];
-
-const isMissing = (name: string, envVar: string | undefined) => {
- if (!envVar) {
- missingVars.push(name);
- }
-};
-
-${providers
- ?.filter((p) => p !== 'credentials')
- .map(
- (provider) =>
- `isMissing('${kebabToConstant(provider)}_CLIENT_ID', process.env.${kebabToConstant(provider)}_CLIENT_ID);\nisMissing('${kebabToConstant(provider)}_CLIENT_SECRET', process.env.${kebabToConstant(provider)}_CLIENT_SECRET)`,
- )
- .join('\n')}
-
-if (missingVars.length > 0) {
- const baseMessage = 'Authentication is configured but the following environment variables are missing:';
-
- if (process.env.NODE_ENV === 'production') {
- console.warn(\`warn: \${baseMessage} \${missingVars.join(', ')}\`);
- } else {
- console.warn(\`\\u001b[33mwarn:\\u001b[0m \${baseMessage} \\u001b[31m\${missingVars.join(', ')}\\u001b[0m\`);
- }
-}`;
-
-const auth: Template = (options) => {
- const providers = options.authProviders;
-
- return `import NextAuth from 'next-auth';\nimport { AuthProvider, SupportedAuthProvider } from '@toolpad/core/SignInPage';\n${providers
- ?.map(
- (provider) =>
- `import ${kebabToPascal(provider)} from 'next-auth/providers/${provider.toLowerCase()}';`,
- )
- .join('\n')}
-import type { Provider } from 'next-auth/providers';
-
-const providers: Provider[] = [${providers
- ?.map((provider) => {
- if (provider === 'credentials') {
- return CredentialsProviderTemplate;
- }
- return oAuthProviderTemplate(provider);
- })
- .join('\n')}
-];
-
-${checkEnvironmentVariables(providers)}
-
-export const providerMap = providers.map((provider) => {
- if (typeof provider === 'function') {
- const providerData = provider();
- return { id: providerData.id, name: providerData.name };
- }
- return { id: provider.id, name: provider.name };
-});
-
-export const { handlers, auth, signIn, signOut } = NextAuth({
- providers,
- secret: process.env.AUTH_SECRET,
- pages: {
- signIn: '/auth/signin',
- },
- callbacks: {
- authorized({ auth: session, request: { nextUrl } }) {
- const isLoggedIn = !!session?.user;
- const isPublicPage = nextUrl.pathname.startsWith('/public');
-
- if (isPublicPage || isLoggedIn) {
- return true;
- }
-
- return false; // Redirect unauthenticated users to login page
- },
- },
-});
- `;
-};
-
-export default auth;
diff --git a/packages/create-toolpad-app/src/templates/auth/nextjs-app/signInPage.ts b/packages/create-toolpad-app/src/templates/auth/nextjs-app/signInPage.ts
deleted file mode 100644
index a5d65da77e8..00000000000
--- a/packages/create-toolpad-app/src/templates/auth/nextjs-app/signInPage.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Template } from '../../../types';
-
-const signInPage: Template = (options) => {
- const { authProviders: providers } = options;
- const hasCredentialsProvider = providers?.includes('credentials');
-
- return `import * as React from 'react';
-import { SignInPage, type AuthProvider } from '@toolpad/core/SignInPage';
-import { AuthError } from 'next-auth';
-import { providerMap, signIn } from '../../../auth';
-
-export default function SignIn() {
- return (
- {
- 'use server';
- try {
- return await signIn(provider.id, {
- redirectTo: callbackUrl ?? '/',
- });
- } catch (error) {
- // The desired flow for successful sign in in all cases
- // and unsuccessful sign in for OAuth providers will cause a \`redirect\`,
- // and \`redirect\` is a throwing function, so we need to re-throw
- // to allow the redirect to happen
- // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642
- // Detect a \`NEXT_REDIRECT\` error and re-throw it
- if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
- throw error;
- }
- // Handle Auth.js errors
- if (error instanceof AuthError) {
- return {
- error: ${
- hasCredentialsProvider
- ? `error.type === 'CredentialsSignin' ? 'Invalid credentials.'
- : 'An error with Auth.js occurred.',`
- : `'An error with Auth.js occurred.'`
- },
- type: error.type,
- };
- }
- // An error boundary must exist to handle unknown errors
- return {
- error: 'Something went wrong.',
- type: 'UnknownError',
- };
- }
- }}
- />
- );
-}`;
-};
-
-export default signInPage;
diff --git a/packages/create-toolpad-app/src/templates/auth/nextjs-pages/signIn.ts b/packages/create-toolpad-app/src/templates/auth/nextjs-pages/signIn.ts
deleted file mode 100644
index d6731ab4f8c..00000000000
--- a/packages/create-toolpad-app/src/templates/auth/nextjs-pages/signIn.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Template } from '../../../types';
-
-const signIn: Template = (options) => {
- const { authProviders: providers } = options;
- const hasCredentialsProvider = providers?.includes('credentials');
-
- return `import * as React from 'react';
-import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
-import { SignInPage } from '@toolpad/core/SignInPage';
-import { signIn } from 'next-auth/react';
-import { useRouter } from 'next/router';
-import { auth, providerMap } from '../../auth';
-
-export default function SignIn({
- providers,
-}: InferGetServerSidePropsType) {
- const router = useRouter();
- return (
- {
- try {
- const signInResponse = await signIn(
- provider.id,
- { callbackUrl: callbackUrl ?? '/' },
- );
- if (signInResponse && signInResponse.error) {
- // Handle Auth.js errors
- return {
- error: ${
- hasCredentialsProvider
- ? `error.type === 'CredentialsSignIn' ? 'Invalid credentials.'
- : 'An error with Auth.js occurred.'`
- : `'An error with Auth.js occurred'`
- },
- type: signInResponse.error,
- };
- }
- ${hasCredentialsProvider ? `router.push(callbackUrl ?? '/');` : ''}
- return {};
- } catch (error) {
- // An error boundary must exist to handle unknown errors
- return {
- error: 'Something went wrong.',
- type: 'UnknownError',
- };
- }
- }}
- />
- );
-}
-
-SignIn.getLayout = (page: React.ReactNode) => page;
-
-SignIn.requireAuth = false;
-
-export async function getServerSideProps(context: GetServerSidePropsContext) {
- const session = await auth(context);
-
- // If the user is already logged in, redirect.
- // Note: Make sure not to redirect to the same page
- // To avoid an infinite loop!
- if (session) {
- return { redirect: { destination: '/' } };
- }
-
- return {
- props: {
- providers: providerMap,
- },
- };
-}`;
-};
-
-export default signIn;
diff --git a/packages/create-toolpad-app/src/templates/gitignore.ts b/packages/create-toolpad-app/src/templates/gitignore.ts
index 83fb41355f3..8ccd0b95cf7 100644
--- a/packages/create-toolpad-app/src/templates/gitignore.ts
+++ b/packages/create-toolpad-app/src/templates/gitignore.ts
@@ -91,6 +91,9 @@ const gitignore = `
# Nuxt.js build / generate output
.nuxt
dist
+
+ # Vite build output
+ dist-ssr
# Gatsby files
.cache/
@@ -119,8 +122,21 @@ const gitignore = `
# TernJS port file
.tern-port
+
+
+ # Editor directories and files
+ .vscode/*
+ !.vscode/extensions.json
+ .idea
+ .DS_Store
+ *.suo
+ *.ntvs*
+ *.njsproj
+ *.sln
+ *.sw?
+
- # Stores VSCode versions used for testing VSCode extensions
+ # Stores VS Code versions used for testing VS Code extensions
.vscode-test
# yarn v2
diff --git a/packages/create-toolpad-app/src/templates/indexPage.ts b/packages/create-toolpad-app/src/templates/indexPage.ts
index 3ba19486dd6..5576feda920 100644
--- a/packages/create-toolpad-app/src/templates/indexPage.ts
+++ b/packages/create-toolpad-app/src/templates/indexPage.ts
@@ -8,21 +8,33 @@ const indexPage: Template = (options) => {
import Typography from '@mui/material/Typography';`;
let sessionHandling = '';
- let welcomeMessage = `Welcome to Toolpad Core!`;
- if (authEnabled) {
- if (routerType === 'nextjs-app') {
- imports += `\nimport { auth } from '../../auth';`;
- sessionHandling = `const session = await auth();`;
- welcomeMessage = `Welcome to Toolpad, {session?.user?.name || 'User'}!`;
+ let welcomeMessage = `Welcome to Toolpad Core!`;
+
+ if (options.framework === 'nextjs') {
+ if (authEnabled) {
+ welcomeMessage = `Welcome to Toolpad, {session?.user?.name || ${options.hasNodemailerProvider || options.hasPasskeyProvider ? `session?.user?.email ||` : ''}'User'}!`;
+ if (routerType === 'nextjs-app') {
+ if (options.hasNodemailerProvider || options.hasPasskeyProvider) {
+ imports += `\nimport { redirect } from 'next/navigation';\nimport { headers } from 'next/headers';`;
+ }
+ imports += `\nimport { auth } from '../../auth';`;
+ sessionHandling = `const session = await auth();`;
+ if (options.hasNodemailerProvider || options.hasPasskeyProvider) {
+ sessionHandling += `\nconst currentUrl = (await headers()).get('referer') || process.env.AUTH_URL || 'http://localhost:3000';
+ if (!session) { // Get the current URL to redirect to signIn with \`callbackUrl\`
+ const redirectUrl = new URL('/auth/signin', currentUrl);\nredirectUrl.searchParams.set('callbackUrl', currentUrl);\nredirect(redirectUrl.toString());
+ }
+ `;
+ }
+ } else if (routerType === 'nextjs-pages') {
+ imports += `\nimport { useSession } from 'next-auth/react';`;
+ sessionHandling = `const { data: session } = useSession();`;
+ }
} else {
- imports += `\nimport { useSession } from 'next-auth/react';`;
- sessionHandling = `const { data: session } = useSession();`;
- welcomeMessage = `Welcome to Toolpad, {session?.user?.name || 'User'}!`;
+ imports += `\nimport Link from 'next/link';`;
+ welcomeMessage = `Welcome to Toolpad Core!`;
}
- } else {
- imports += `\nimport Link from 'next/link';`;
- welcomeMessage = `Welcome to Toolpad Core!`;
}
const isAsync = authEnabled && routerType === 'nextjs-app' ? 'async ' : '';
diff --git a/packages/create-toolpad-app/src/templates/nextConfig.ts b/packages/create-toolpad-app/src/templates/nextConfig.ts
index 919a2e044d8..1d4f7d7fdb3 100644
--- a/packages/create-toolpad-app/src/templates/nextConfig.ts
+++ b/packages/create-toolpad-app/src/templates/nextConfig.ts
@@ -1,7 +1,11 @@
-const nextConfig = `
+import { Template } from '../types';
+
+const nextConfig: Template = (options) => `
/** @type {import('next').NextConfig} */
- const nextConfig = {};
+ const nextConfig = {
+ ${options.router === 'nextjs-pages' ? 'transpilePackages: ["next-auth"],' : ''}
+ };
export default nextConfig;
- `;
+`;
export default nextConfig;
diff --git a/packages/create-toolpad-app/src/templates/nextjs/auth/auth.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/auth.ts
new file mode 100644
index 00000000000..79f71489c87
--- /dev/null
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/auth.ts
@@ -0,0 +1,145 @@
+import type { SupportedAuthProvider } from '@toolpad/core/SignInPage';
+import { kebabToConstant, kebabToPascal } from '@toolpad/utils/strings';
+import { requiresIssuer, requiresTenantId } from './utils';
+import { Template } from '../../../types';
+
+const CredentialsProviderTemplate = `Credentials({
+ credentials: {
+ email: { label: 'Email Address', type: 'email' },
+ password: { label: 'Password', type: 'password' },
+ },
+ authorize(c) {
+ if (c.password !== 'password') {
+ return null;
+ }
+ return {
+ id: 'test',
+ name: 'Test User',
+ email: String(c.email),
+ };
+ },
+}),`;
+
+const NodemailerTemplate = `Nodemailer({
+ server: {
+ host: process.env.EMAIL_SERVER_HOST,
+ port: process.env.EMAIL_SERVER_PORT,
+ auth: {
+ user: process.env.EMAIL_SERVER_USER,
+ pass: process.env.EMAIL_SERVER_PASSWORD,
+ },
+ secure: true,
+ },
+ from: process.env.EMAIL_FROM,
+ }),`;
+
+const PasskeyTemplate = 'Passkey,';
+
+const oAuthProviderTemplate = (provider: SupportedAuthProvider) => `
+ ${kebabToPascal(provider)}({
+ clientId: process.env.${kebabToConstant(provider)}_CLIENT_ID,
+ clientSecret: process.env.${kebabToConstant(provider)}_CLIENT_SECRET,${requiresIssuer(provider) ? `\n\t\tissuer: process.env.${kebabToConstant(provider)}_ISSUER,` : ''}${requiresTenantId(provider) ? `\n\t\ttenantId: process.env.${kebabToConstant(provider)}_TENANT_ID,` : ''}
+ }),`;
+const checkEnvironmentVariables = (providers: SupportedAuthProvider[] | undefined) => `${providers
+ ?.filter((p) => p !== 'credentials')
+ .map((provider) => {
+ if (provider === 'nodemailer') {
+ return `if(!process.env.DATABASE_URL || !process.env.EMAIL_SERVER_HOST) { \nconsole.warn('The Nodemailer provider requires configuring a database and an email server.')\n}`;
+ }
+ if (provider === 'passkey') {
+ return `if(!process.env.DATABASE_URL) { \nconsole.warn('The passkey provider requires configuring a database.')\n}`;
+ }
+ return `if(!process.env.${kebabToConstant(provider)}_CLIENT_ID) {
+ console.warn('Missing environment variable "${kebabToConstant(provider)}_CLIENT_ID"');
+}
+if(!process.env.${kebabToConstant(provider)}_CLIENT_SECRET) {
+ console.warn('Missing environment variable "${kebabToConstant(provider)}_CLIENT_SECRET"');
+}${
+ requiresTenantId(provider)
+ ? `
+if(!process.env.${kebabToConstant(provider)}_TENANT_ID) {
+ console.warn('Missing environment variable "${kebabToConstant(provider)}_TENANT_ID"');
+}`
+ : ''
+ }${
+ requiresIssuer(provider)
+ ? `
+if(!process.env.${kebabToConstant(provider)}_ISSUER) {
+ console.warn('Missing environment variable "${kebabToConstant(provider)}_ISSUER"');
+}`
+ : ''
+ }`;
+ })
+ .join('\n')}
+`;
+
+const auth: Template = (options) => {
+ const providers = options.authProviders;
+
+ return `import NextAuth from 'next-auth';\n${providers
+ ?.map(
+ (provider) =>
+ `import ${kebabToPascal(provider)} from 'next-auth/providers/${provider.toLowerCase()}';`,
+ )
+ .join('\n')}
+import type { Provider } from 'next-auth/providers';
+${options.hasNodemailerProvider || options.hasPasskeyProvider ? `\nimport { PrismaAdapter } from '@auth/prisma-adapter';\nimport { prisma } from './prisma';` : ''}
+
+const providers: Provider[] = [${providers
+ ?.map((provider) => {
+ if (provider === 'credentials') {
+ return CredentialsProviderTemplate;
+ }
+ if (provider === 'nodemailer') {
+ return NodemailerTemplate;
+ }
+ if (provider === 'passkey') {
+ return PasskeyTemplate;
+ }
+ return oAuthProviderTemplate(provider);
+ })
+ .join('\n')}
+];
+
+${checkEnvironmentVariables(providers)}
+
+export const providerMap = providers.map((provider) => {
+ if (typeof provider === 'function') {
+ const providerData = provider();
+ return { id: providerData.id, name: providerData.name };
+ }
+ return { id: provider.id, name: provider.name };
+});
+
+export const { handlers, auth, signIn, signOut } = NextAuth({
+ providers,
+ ${options.hasNodemailerProvider || options.hasPasskeyProvider ? `\nadapter: PrismaAdapter(prisma),` : ''}
+ ${options.hasNodemailerProvider || (options.router === 'nextjs-app' && options.hasPasskeyProvider && providers && providers.length > 1) ? `\nsession: { strategy: 'jwt' },` : ''}
+ ${
+ options.hasPasskeyProvider
+ ? `\nexperimental: {
+ enableWebAuthn: true,
+ },`
+ : ''
+ }
+ secret: process.env.AUTH_SECRET,
+ pages: {
+ signIn: '/auth/signin',
+ },
+ callbacks: {
+ authorized({ auth: session, request: { nextUrl } }) {
+ const isLoggedIn = !!session?.user;
+ const isPublicPage = nextUrl.pathname.startsWith('/public');
+
+ if (isPublicPage || isLoggedIn) {
+ return true;
+ }
+
+ return false; // Redirect unauthenticated users to login page
+ },
+ },
+});
+ `;
+};
+
+export default auth;
diff --git a/packages/create-toolpad-app/src/templates/nextjs/auth/env.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/env.ts
new file mode 100644
index 00000000000..57d5ab57473
--- /dev/null
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/env.ts
@@ -0,0 +1,5 @@
+const env = `
+DATABASE_URL =
+`;
+
+export default env;
diff --git a/packages/create-toolpad-app/src/templates/auth/envLocal.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/envLocal.ts
similarity index 67%
rename from packages/create-toolpad-app/src/templates/auth/envLocal.ts
rename to packages/create-toolpad-app/src/templates/nextjs/auth/envLocal.ts
index 26f270a9b8e..befc0072fb1 100644
--- a/packages/create-toolpad-app/src/templates/auth/envLocal.ts
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/envLocal.ts
@@ -1,17 +1,18 @@
import { kebabToConstant } from '@toolpad/utils/strings';
import { requiresIssuer, requiresTenantId } from './utils';
-import { Template } from '../../types';
+import { Template } from '../../../types';
const env: Template = (options) => {
const { authProviders: providers } = options;
- const nonCredentialProviders = providers?.filter((provider) => provider !== 'credentials');
+ const nonCredentialProviders = providers?.filter(
+ (provider) => provider !== 'credentials' && provider !== 'nodemailer' && provider !== 'passkey',
+ );
return `
# Generate a secret with \`npx auth secret\`
# and replace the value below with it
AUTH_SECRET=secret
# Add secrets for your auth providers to the .env.local file
-
${nonCredentialProviders
?.map(
(provider) => `
@@ -20,7 +21,12 @@ ${kebabToConstant(provider)}_CLIENT_SECRET=
${requiresIssuer(provider) ? `${kebabToConstant(provider)}_ISSUER=\n` : ''}${requiresTenantId(provider) ? `${kebabToConstant(provider)}_TENANT_ID=\n` : ''}`,
)
.join('\n')}
-`;
+
+${
+ options.hasNodemailerProvider
+ ? `EMAIL_SERVER_HOST=\nEMAIL_SERVER_PORT=\nEMAIL_SERVER_USER=\nEMAIL_SERVER_PASSWORD=\nEMAIL_FROM=`
+ : ''
+}`;
};
export default env;
diff --git a/packages/create-toolpad-app/src/templates/auth/middleware.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/middleware.ts
similarity index 100%
rename from packages/create-toolpad-app/src/templates/auth/middleware.ts
rename to packages/create-toolpad-app/src/templates/nextjs/auth/middleware.ts
diff --git a/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-app/actions.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-app/actions.ts
new file mode 100644
index 00000000000..6ed7e9b1e56
--- /dev/null
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-app/actions.ts
@@ -0,0 +1,48 @@
+import { Template } from '../../../../types';
+
+const actionsTemplate: Template = (options) => {
+ const { hasCredentialsProvider, hasNodemailerProvider } = options;
+
+ return `'use server';
+import { AuthError } from 'next-auth';
+import type { AuthProvider } from '@toolpad/core';
+import { signIn } from '../../../auth';
+
+export default async function serverSignIn(provider: AuthProvider, formData: FormData, callbackUrl?: string) {
+ try {
+ return await signIn(provider.id, {
+ ...(formData && { email: formData.get('email'), password: formData.get('password') }),
+ redirectTo: callbackUrl ?? '/',
+ });
+ } catch (error) {
+ if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
+ ${
+ hasNodemailerProvider
+ ? `if (provider.id === 'nodemailer' && (error as any).digest?.includes('verify-request')) {
+ return {
+ success: 'Check your email for a verification link.',
+ };
+ }`
+ : ''
+ }
+ throw error;
+ }
+ if (error instanceof AuthError) {
+ return {
+ error: ${
+ hasCredentialsProvider
+ ? `error.type === 'CredentialsSignin' ? 'Invalid credentials.' : 'An error with Auth.js occurred.'`
+ : `'An error with Auth.js occurred.'`
+ },
+ type: error.type,
+ };
+ }
+ return {
+ error: 'Something went wrong.',
+ type: 'UnknownError',
+ };
+ }
+}`;
+};
+
+export default actionsTemplate;
diff --git a/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-app/signInPage.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-app/signInPage.ts
new file mode 100644
index 00000000000..d30264a6fd2
--- /dev/null
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-app/signInPage.ts
@@ -0,0 +1,64 @@
+import { Template } from '../../../../types';
+
+const signInPage: Template = (options) => {
+ const { hasPasskeyProvider, hasNodemailerProvider } = options;
+
+ return `${hasPasskeyProvider ? "'use client';" : ''}
+ import * as React from 'react';
+import { SignInPage } from '@toolpad/core/SignInPage';
+${hasPasskeyProvider ? "import { signIn as webauthnSignIn } from 'next-auth/webauthn';" : ''}
+${hasPasskeyProvider && hasNodemailerProvider ? `import { getProviders } from "next-auth/react";` : `import { providerMap } from '../../../auth';`}
+${hasPasskeyProvider ? `import type { AuthProvider } from '@toolpad/core';` : ''}
+${hasPasskeyProvider ? `import serverSignIn from './actions';` : `import signIn from './actions';`}
+
+${
+ hasPasskeyProvider
+ ? `const signIn = async (provider: AuthProvider, formData: FormData, callbackUrl?: string) => {
+ if (provider.id === 'passkey') {
+ try {
+ return await webauthnSignIn('passkey', {
+ email: formData.get('email'),
+ callbackUrl: callbackUrl || '/',
+ });
+ } catch (error) {
+ console.error(error);
+ return {
+ error: (error as Error)?.message || 'Something went wrong',
+ type: 'WebAuthnError',
+ };
+ }
+ }
+ // Use server action for other providers
+ return serverSignIn(provider, formData, callbackUrl);
+};`
+ : ''
+}
+
+export default function SignIn() {
+${
+ hasPasskeyProvider && hasNodemailerProvider
+ ? `const [providerMap, setProviderMap] = React.useState();
+ React.useEffect(() => {
+ const loadProviders = async () => {
+ const providers = await getProviders();
+ const providerList =
+ Object.entries(providers || {}).map(([id, data]) => ({
+ id,
+ name: data.name,
+ })) || [];
+ setProviderMap(providerList);
+ };
+ loadProviders();
+ }, []);`
+ : ''
+}
+ return (
+
+ );
+}`;
+};
+
+export default signInPage;
diff --git a/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-pages/signIn.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-pages/signIn.ts
new file mode 100644
index 00000000000..8fe3c4aefdd
--- /dev/null
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/nextjs-pages/signIn.ts
@@ -0,0 +1,117 @@
+import { Template } from '../../../../types';
+
+const signIn: Template = (options) => {
+ const { hasCredentialsProvider, hasNodemailerProvider, hasPasskeyProvider } = options;
+
+ return `import * as React from 'react';
+import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
+import { SignInPage } from '@toolpad/core/SignInPage';
+${hasPasskeyProvider ? "import { signIn as webauthnSignIn } from 'next-auth/webauthn';" : ''}
+import { signIn } from 'next-auth/react';
+import { useRouter } from 'next/router';
+import { auth, providerMap } from '../../auth';
+
+export default function SignIn({
+ providers,
+}: InferGetServerSidePropsType) {
+ const router = useRouter();
+ return (
+ {
+ try {
+ ${
+ hasPasskeyProvider
+ ? `if (provider.id === 'passkey') {
+ try {
+ return await webauthnSignIn('passkey', {
+ email: formData.get('email'),
+ callbackUrl: callbackUrl || '/',
+ });
+ } catch (error) {
+ console.error(error);
+ return {
+ error: (error as Error)?.message || 'Something went wrong',
+ type: 'WebAuthnError',
+ };
+ }
+ }`
+ : ''
+ }
+ const signInResponse = await signIn(
+ provider.id, {
+ ...(formData ? { email: formData.get('email'), password: formData.get('password')${hasNodemailerProvider || hasCredentialsProvider ? `, redirect: false` : ''}
+ } :
+ { callbackUrl: callbackUrl ?? '/' })
+ }
+ );
+ ${
+ hasNodemailerProvider
+ ? `\n// For the nodemailer provider, we want to return a success message
+ // instead of redirecting to a \`verify-request\` page
+ if (
+ provider.id === "nodemailer"
+ ) {
+ return {
+ success: "Check your email for a verification link.",
+ };
+ }
+ `
+ : ''
+ }
+ if (signInResponse && signInResponse.error) {
+ // Handle Auth.js errors
+ return {
+ error: ${
+ hasCredentialsProvider
+ ? `signInResponse.error === 'CredentialsSignin' ? 'Invalid credentials.'
+ : 'An error with Auth.js occurred.'`
+ : `'An error with Auth.js occurred'`
+ },
+ type: signInResponse.error,
+ };
+ }
+ ${
+ hasCredentialsProvider || hasNodemailerProvider
+ ? `
+ if(provider.id === "credentials" || provider.id === "nodemailer") {
+ router.push(callbackUrl ?? '/');
+ }`
+ : ''
+ }
+ return {};
+ } catch (error) {
+ // An error boundary must exist to handle unknown errors
+ return {
+ error: 'Something went wrong.',
+ type: 'UnknownError',
+ };
+ }
+ }}
+ />
+ );
+}
+
+SignIn.getLayout = (page: React.ReactNode) => page;
+
+SignIn.requireAuth = false;
+
+export async function getServerSideProps(context: GetServerSidePropsContext) {
+ const session = await auth(context);
+
+ // If the user is already logged in, redirect.
+ // Note: Make sure not to redirect to the same page
+ // To avoid an infinite loop!
+ if (session) {
+ return { redirect: { destination: '/' } };
+ }
+
+ return {
+ props: {
+ providers: providerMap,
+ },
+ };
+}`;
+};
+
+export default signIn;
diff --git a/packages/create-toolpad-app/src/templates/nextjs/auth/prisma.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/prisma.ts
new file mode 100644
index 00000000000..34d03e0ef8c
--- /dev/null
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/prisma.ts
@@ -0,0 +1,11 @@
+const prisma = `import { PrismaClient } from '@prisma/client';
+
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
+
+export const prisma = globalForPrisma.prisma || new PrismaClient();
+
+if (process.env.NODE_ENV !== 'production') {
+ globalForPrisma.prisma = prisma;
+}`;
+
+export default prisma;
diff --git a/packages/create-toolpad-app/src/templates/auth/route.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/route.ts
similarity index 100%
rename from packages/create-toolpad-app/src/templates/auth/route.ts
rename to packages/create-toolpad-app/src/templates/nextjs/auth/route.ts
diff --git a/packages/create-toolpad-app/src/templates/nextjs/auth/schemaPrisma.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/schemaPrisma.ts
new file mode 100644
index 00000000000..4fb2b066f13
--- /dev/null
+++ b/packages/create-toolpad-app/src/templates/nextjs/auth/schemaPrisma.ts
@@ -0,0 +1,88 @@
+import { Template } from '../../../types';
+
+const schemaPrisma: Template = (options) => `
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+model User {
+ id String @id @default(cuid())
+ name String?
+ email String @unique
+ emailVerified DateTime?
+ image String?
+ accounts Account[]
+ sessions Session[]
+ ${options.hasPasskeyProvider ? 'Authenticator Authenticator[]' : ''}
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+model Account {
+ userId String
+ type String
+ provider String
+ providerAccountId String
+ refresh_token String?
+ access_token String?
+ expires_at Int?
+ token_type String?
+ scope String?
+ id_token String?
+ session_state String?
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@id([provider, providerAccountId])
+}
+
+model Session {
+ sessionToken String @unique
+ userId String
+ expires DateTime
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+model VerificationToken {
+ identifier String
+ token String
+ expires DateTime
+
+ @@id([identifier, token])
+}
+
+${
+ options.hasPasskeyProvider
+ ? `
+model Authenticator {
+ credentialID String @unique
+ userId String
+ providerAccountId String
+ credentialPublicKey String
+ counter Int
+ credentialDeviceType String
+ credentialBackedUp Boolean
+ transports String?
+
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@id([userId, credentialID])
+}`
+ : ''
+}
+`;
+
+export default schemaPrisma;
diff --git a/packages/create-toolpad-app/src/templates/auth/utils.ts b/packages/create-toolpad-app/src/templates/nextjs/auth/utils.ts
similarity index 100%
rename from packages/create-toolpad-app/src/templates/auth/utils.ts
rename to packages/create-toolpad-app/src/templates/nextjs/auth/utils.ts
diff --git a/packages/create-toolpad-app/src/templates/nextjs-app/dashboardLayout.ts b/packages/create-toolpad-app/src/templates/nextjs/nextjs-app/dashboardLayout.ts
similarity index 100%
rename from packages/create-toolpad-app/src/templates/nextjs-app/dashboardLayout.ts
rename to packages/create-toolpad-app/src/templates/nextjs/nextjs-app/dashboardLayout.ts
diff --git a/packages/create-toolpad-app/src/templates/nextjs-app/rootLayout.ts b/packages/create-toolpad-app/src/templates/nextjs/nextjs-app/rootLayout.ts
similarity index 80%
rename from packages/create-toolpad-app/src/templates/nextjs-app/rootLayout.ts
rename to packages/create-toolpad-app/src/templates/nextjs/nextjs-app/rootLayout.ts
index e229705445e..9ab535e257a 100644
--- a/packages/create-toolpad-app/src/templates/nextjs-app/rootLayout.ts
+++ b/packages/create-toolpad-app/src/templates/nextjs/nextjs-app/rootLayout.ts
@@ -1,13 +1,14 @@
-import { Template } from '../../types';
+import { Template } from '../../../types';
const rootLayout: Template = (options) => {
const authEnabled = options.auth;
return `import * as React from 'react';
-import { AppProvider } from '@toolpad/core/nextjs';
-import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
+import { NextAppProvider } from '@toolpad/core/nextjs';
+import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+${authEnabled ? '' : `import LinearProgress from '@mui/material/LinearProgress'`}
import type { Navigation } from '@toolpad/core/AppProvider';
${
authEnabled
@@ -57,7 +58,8 @@ export default ${authEnabled ? 'async ' : ''}function RootLayout(props: { childr
${authEnabled ? '' : ''}
- }>'}
+
{props.children}
-