From 51dc2e468c94d0271f24c9f9c8519914a22478b4 Mon Sep 17 00:00:00 2001 From: adrien guernier Date: Tue, 4 Jul 2023 17:08:59 +0200 Subject: [PATCH 1/5] [Doc] update some js and jsx snippet to ts and tsx --- docs/Tutorial.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 89510fe07ac..2a05fed2ff7 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -273,13 +273,13 @@ But on desktop, `` takes too much space for a low information densit To do so, we'll use [the `useMediaQuery` hook](https://mui.com/material-ui/react-use-media-query/) from Material UI: -```jsx +```tsx // in src/users.tsx -import { useMediaQuery } from "@mui/material"; +import { useMediaQuery, Theme } from "@mui/material"; import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin"; export const UserList = () => { - const isSmall = useMediaQuery((theme) => theme.breakpoints.down("sm")); + const isSmall = useMediaQuery((theme) => theme.breakpoints.down("sm")); return ( {isSmall ? ( @@ -372,11 +372,11 @@ In react-admin, fields are just React components. When rendered, they grab the ` That means that you can do the same to write a custom Field. For instance, here is a simplified version of the ``: -```jsx +```tsx // in src/MyUrlField.tsx import { useRecordContext } from "react-admin"; -const MyUrlField = ({ source }) => { +const MyUrlField = ({ source }: { source: string }) => { const record = useRecordContext(); if (!record) return null; return {record[source]}; @@ -415,13 +415,13 @@ The `` component is a perfect opportunity to illustrate how to custo React-admin relies on [Material UI](https://mui.com/material-ui/getting-started/overview/), a set of React components modeled after Google's [Material Design Guidelines](https://material.io/). All Material UI components (and most react-admin components) support a prop called `sx`, which allows custom inline styles. Let's take advantage of the `sx` prop to remove the underline from the link and add an icon: {% raw %} -```jsx +```tsx // in src/MyUrlField.tsx import { useRecordContext } from "react-admin"; import { Link } from "@mui/material"; import LaunchIcon from "@mui/icons-material/Launch"; -const MyUrlField = ({ source }) => { +const MyUrlField = ({ source }: { source: string }) => { const record = useRecordContext(); return record ? ( @@ -877,13 +877,13 @@ For this tutorial, since there is no public authentication API, we can use a fak The `authProvider` must expose 5 methods, each returning a `Promise`: -```js +```ts // in src/authProvider.ts +import { AuthProvider } from "react-admin"; -// TypeScript users must reference the type: `AuthProvider` -export const authProvider = { +export const authProvider: AuthProvider = { // called when the user attempts to log in - login: ({ username }) => { + login: ({ username }: { username: string }) => { localStorage.setItem("username", username); // accept all username/password combinations return Promise.resolve(); @@ -894,7 +894,7 @@ export const authProvider = { return Promise.resolve(); }, // called when the API returns an error - checkError: ({ status }) => { + checkError: ({ status }: { status: number }) => { if (status === 401 || status === 403) { localStorage.removeItem("username"); return Promise.reject(); @@ -962,16 +962,15 @@ React-admin calls the Data Provider with one method for each of the actions of t The code for a Data Provider for the `my.api.url` API is as follows: -```js +```ts // in src/dataProvider.ts -import { fetchUtils } from "react-admin"; +import { DataProvider, fetchUtils } from "react-admin"; import { stringify } from "query-string"; const apiUrl = 'https://my.api.com/'; const httpClient = fetchUtils.fetchJson; -// TypeScript users must reference the type `DataProvider` -export const dataProvider = { +export const dataProvider: DataProvider = { getList: (resource, params) => { const { page, perPage } = params.pagination; const { field, order } = params.sort; From c6e758af8dade3c5d2ade5c9f641bb365a954056 Mon Sep 17 00:00:00 2001 From: adrien guernier Date: Wed, 5 Jul 2023 10:10:27 +0200 Subject: [PATCH 2/5] remove types for login params --- docs/Tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 2a05fed2ff7..1865b086897 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -883,7 +883,7 @@ import { AuthProvider } from "react-admin"; export const authProvider: AuthProvider = { // called when the user attempts to log in - login: ({ username }: { username: string }) => { + login: ({ username }) => { localStorage.setItem("username", username); // accept all username/password combinations return Promise.resolve(); From 2637adaa5ae22c5c538fe95793dc31c0b2c14107 Mon Sep 17 00:00:00 2001 From: adrien guernier Date: Wed, 5 Jul 2023 10:12:10 +0200 Subject: [PATCH 3/5] replace all jsx snippet by tsx --- docs/Tutorial.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 1865b086897..919bf76242f 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -110,7 +110,7 @@ JSONPlaceholder provides endpoints for users, posts, and comments. The admin we' The `test-admin` project you just created already contains a data provider pre-configured for JSONPlaceholder. -```jsx +```tsx // in src/dataProvider.ts import jsonServerProvider from 'ra-data-json-server'; @@ -159,7 +159,7 @@ The `` component is not meant to be used in production - it's just Let's copy this code, and create a new `UserList` component, in a new file named `users.tsx`: -```jsx +```tsx // in src/users.tsx import { List, Datagrid, TextField, EmailField } from "react-admin"; @@ -204,7 +204,7 @@ There is no visible change in the browser - except now, the app uses a component Let's take a moment to analyze the code of the `` component: -```jsx +```tsx export const UserList = () => ( @@ -223,7 +223,7 @@ export const UserList = () => ( The root component, ``, reads the query parameters from the URL, calls the API based on these parameters, and puts the result in a React context. It also builds a set of callbacks allowing child components to modify the list filters, pagination, and sorting. `` does a lot of things, yet its syntax couldn't be simpler: -```jsx +```tsx {/* children */} @@ -235,7 +235,7 @@ But in most frameworks, "simple" means "limited", and it's hard to go beyond bas This means we can compose `` with another component - for instance ``: -```jsx +```tsx // in src/users.tsx import { List, SimpleList } from "react-admin"; @@ -477,7 +477,7 @@ export const App = () => ( The `ListGuesser` suggests using a `` for the `userId` field. Let's play with this new field by creating the `PostList` component based on the code dumped by the guesser: -```jsx +```tsx // in src/posts.tsx import { List, Datagrid, TextField, ReferenceField } from "react-admin"; @@ -587,7 +587,7 @@ Users can display the edit page just by clicking on the Edit button. The form is Copy the `` code dumped by the guesser in the console to the `posts.tsx` file so that you can customize the view: -```jsx +```tsx // in src/posts.tsx import { List, @@ -787,7 +787,7 @@ Let's get back to the post list for a minute. It offers sorting and pagination, React-admin can use Input components to create a multi-criteria search engine in the list view. Pass an array of such Input components to the List `filters` prop to enable filtering: -```jsx +```tsx // in src/posts.tsx const postFilters = [ , @@ -818,7 +818,7 @@ Filters are "search-as-you-type", meaning that when the user enters new values i The sidebar menu shows the same icon for both posts and users. Customizing the menu icon is just a matter of passing an `icon` attribute to each ``: -```jsx +```tsx // in src/App.tsx import PostIcon from "@mui/icons-material/Book"; import UserIcon from "@mui/icons-material/Group"; @@ -842,7 +842,7 @@ export const App = () => ( By default, react-admin displays the list page of the first `Resource` element as home page. If you want to display a custom component instead, pass it in the `dashboard` prop of the `` component. -```jsx +```tsx // in src/Dashboard.tsx import { Card, CardContent, CardHeader } from "@mui/material"; @@ -854,7 +854,7 @@ export const Dashboard = () => ( ); ``` -```jsx +```tsx // in src/App.tsx import { Dashboard } from './Dashboard'; @@ -916,7 +916,7 @@ export const authProvider: AuthProvider = { To enable this authentication strategy, pass the `authProvider` to the `` component: -```jsx +```tsx // in src/App.tsx import { Dashboard } from './Dashboard'; import { authProvider } from './authProvider'; @@ -1063,7 +1063,7 @@ export const dataProvider: DataProvider = { Using this provider instead of the previous `jsonServerProvider` is just a matter of switching a function: -```jsx +```tsx // in src/app.tsx import { dataProvider } from './dataProvider'; From fb1b8063a6cfb957b73166d626e801bb17d4bc01 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Wed, 5 Jul 2023 16:00:37 +0200 Subject: [PATCH 4/5] Improve TS/JS switch experience --- docs/Tutorial.md | 266 +++++++++--------- docs/js/ra-doc-exec.js | 2 + examples/tutorial/src/main.tsx | 2 +- .../templates/common/src/index.tsx | 2 +- 4 files changed, 137 insertions(+), 135 deletions(-) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 919bf76242f..68cacfb2752 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -47,7 +47,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { App } from './App'; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById('root')!).render( @@ -164,18 +164,18 @@ Let's copy this code, and create a new `UserList` component, in a new file named import { List, Datagrid, TextField, EmailField } from "react-admin"; export const UserList = () => ( - - - - - - - - - - - - + + + + + + + + + + + + ); ``` @@ -206,18 +206,18 @@ Let's take a moment to analyze the code of the `` component: ```tsx export const UserList = () => ( - - - - - - - - - - - - + + + + + + + + + + + + ); ``` @@ -240,13 +240,13 @@ This means we can compose `` with another component - for instance ` ( - - record.name} - secondaryText={(record) => record.username} - tertiaryText={(record) => record.email} - /> - + + record.name} + secondaryText={(record) => record.username} + tertiaryText={(record) => record.email} + /> + ); ``` @@ -279,29 +279,29 @@ import { useMediaQuery, Theme } from "@mui/material"; import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin"; export const UserList = () => { - const isSmall = useMediaQuery((theme) => theme.breakpoints.down("sm")); - return ( - - {isSmall ? ( - record.name} - secondaryText={(record) => record.username} - tertiaryText={(record) => record.email} - /> - ) : ( - - - - - - - - - - - )} - - ); + const isSmall = useMediaQuery((theme) => theme.breakpoints.down("sm")); + return ( + + {isSmall ? ( + record.name} + secondaryText={(record) => record.username} + tertiaryText={(record) => record.email} + /> + ) : ( + + + + + + + + + + + )} + + ); }; ``` @@ -377,9 +377,9 @@ That means that you can do the same to write a custom Field. For instance, here import { useRecordContext } from "react-admin"; const MyUrlField = ({ source }: { source: string }) => { - const record = useRecordContext(); - if (!record) return null; - return {record[source]}; + const record = useRecordContext(); + if (!record) return null; + return {record[source]}; }; export default MyUrlField; @@ -422,13 +422,13 @@ import { Link } from "@mui/material"; import LaunchIcon from "@mui/icons-material/Launch"; const MyUrlField = ({ source }: { source: string }) => { - const record = useRecordContext(); - return record ? ( - - {record[source]} - - - ) : null; + const record = useRecordContext(); + return record ? ( + + {record[source]} + + + ) : null; }; export default MyUrlField; @@ -482,14 +482,14 @@ The `ListGuesser` suggests using a `` for the `userId` field. Le import { List, Datagrid, TextField, ReferenceField } from "react-admin"; export const PostList = () => ( - - - - - - - - + + + + + + + + ); ``` @@ -590,30 +590,30 @@ Copy the `` code dumped by the guesser in the console to the `posts.ts ```tsx // in src/posts.tsx import { - List, - Datagrid, - TextField, - ReferenceField, - EditButton, - Edit, - SimpleForm, - ReferenceInput, - TextInput, + List, + Datagrid, + TextField, + ReferenceField, + EditButton, + Edit, + SimpleForm, + ReferenceInput, + TextInput, } from "react-admin"; -export const PostList = () => ( - { /* ... */ } -); +export const PostList = () => { + /* ... */ +}; export const PostEdit = () => ( - - - - - - - - + + + + + + + + ); ``` @@ -824,10 +824,10 @@ import PostIcon from "@mui/icons-material/Book"; import UserIcon from "@mui/icons-material/Group"; export const App = () => ( - - - - + + + + ); ``` @@ -847,10 +847,10 @@ By default, react-admin displays the list page of the first `Resource` element a import { Card, CardContent, CardHeader } from "@mui/material"; export const Dashboard = () => ( - - - Lorem ipsum sic dolor amet... - + + + Lorem ipsum sic dolor amet... + ); ``` @@ -859,9 +859,9 @@ export const Dashboard = () => ( import { Dashboard } from './Dashboard'; export const App = () => ( - - // ... - + + // ... + ); ``` @@ -877,38 +877,38 @@ For this tutorial, since there is no public authentication API, we can use a fak The `authProvider` must expose 5 methods, each returning a `Promise`: -```ts +```tsx // in src/authProvider.ts import { AuthProvider } from "react-admin"; export const authProvider: AuthProvider = { - // called when the user attempts to log in - login: ({ username }) => { - localStorage.setItem("username", username); - // accept all username/password combinations - return Promise.resolve(); - }, - // called when the user clicks on the logout button - logout: () => { - localStorage.removeItem("username"); - return Promise.resolve(); - }, - // called when the API returns an error - checkError: ({ status }: { status: number }) => { - if (status === 401 || status === 403) { - localStorage.removeItem("username"); - return Promise.reject(); - } - return Promise.resolve(); - }, - // called when the user navigates to a new location, to check for authentication - checkAuth: () => { - return localStorage.getItem("username") - ? Promise.resolve() - : Promise.reject(); - }, - // called when the user navigates to a new location, to check for permissions / roles - getPermissions: () => Promise.resolve(), + // called when the user attempts to log in + login: ({ username }) => { + localStorage.setItem("username", username); + // accept all username/password combinations + return Promise.resolve(); + }, + // called when the user clicks on the logout button + logout: () => { + localStorage.removeItem("username"); + return Promise.resolve(); + }, + // called when the API returns an error + checkError: ({ status }: { status: number }) => { + if (status === 401 || status === 403) { + localStorage.removeItem("username"); + return Promise.reject(); + } + return Promise.resolve(); + }, + // called when the user navigates to a new location, to check for authentication + checkAuth: () => { + return localStorage.getItem("username") + ? Promise.resolve() + : Promise.reject(); + }, + // called when the user navigates to a new location, to check for permissions / roles + getPermissions: () => Promise.resolve(), }; ``` @@ -962,7 +962,7 @@ React-admin calls the Data Provider with one method for each of the actions of t The code for a Data Provider for the `my.api.url` API is as follows: -```ts +```tsx // in src/dataProvider.ts import { DataProvider, fetchUtils } from "react-admin"; import { stringify } from "query-string"; diff --git a/docs/js/ra-doc-exec.js b/docs/js/ra-doc-exec.js index 4330b23f88d..7441c7b4bfc 100644 --- a/docs/js/ra-doc-exec.js +++ b/docs/js/ra-doc-exec.js @@ -131,8 +131,10 @@ const buildJSCodeBlocksFromTS = async () => { // Containers for Prism highlighter const highlight = document.createElement('div'); + highlight.className = 'highlight'; jsTabContent.appendChild(highlight); const jsTabContentPre = document.createElement('pre'); + jsTabContentPre.className = 'highlight'; highlight.appendChild(jsTabContentPre); // The actual JS code element diff --git a/examples/tutorial/src/main.tsx b/examples/tutorial/src/main.tsx index a3f01d61399..9dbd1478564 100644 --- a/examples/tutorial/src/main.tsx +++ b/examples/tutorial/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import './index.css'; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/packages/create-react-admin/templates/common/src/index.tsx b/packages/create-react-admin/templates/common/src/index.tsx index 6e3c16c5696..8f223f2fb63 100644 --- a/packages/create-react-admin/templates/common/src/index.tsx +++ b/packages/create-react-admin/templates/common/src/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { App } from './App'; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById('root')!).render( From 779f4ac8f14f2d6999d561c082e8286c22b73401 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Wed, 5 Jul 2023 16:28:59 +0200 Subject: [PATCH 5/5] show tsx snippets in the docs by default --- docs/js/ra-doc-exec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/js/ra-doc-exec.js b/docs/js/ra-doc-exec.js index 7441c7b4bfc..139231fb674 100644 --- a/docs/js/ra-doc-exec.js +++ b/docs/js/ra-doc-exec.js @@ -3,7 +3,7 @@ var allMenus, navLinks, versionsLinks; const applyPreferredLanguage = async () => { const preferredLanguage = - window.localStorage.getItem('preferred-language') || 'jsx'; + window.localStorage.getItem('preferred-language') || 'tsx'; const languageSwitchers = document.querySelectorAll('.language-switcher'); const codeFences = document.querySelectorAll('div[class^=language-]');