From 924402eb44baf8feb83455450d7b9257cde07583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Tue, 16 Jan 2024 11:17:08 +0300 Subject: [PATCH 01/35] docs(tutorial): new tutorial layout (#5506) * docs: remove unused imports * docs: add new settings to tailwind config * docs(search-bar): fix props * docs: add tutorial components * docs: add tutorial contexts * docs: add conditional tutorial content component * docs: add tutorial navigation plugin * docs: add tutorial plugins * docs: init tutorial data * docs: add tutorial redirects * docs: add next steps unit * docs: add dummy tutorial docs * docs(tutorial): add contentOnly prop * docs(prose): fix blockquote margins * docs(sandpack): update background by theme * docs(tutorial-0): add welcome page * docs(tutorial): initial intro * docs(tutorial): fix tutorial links in menus * docs(tutorial): fix parameter dropdown * docs(tutorial): infer tutorial parameters * docs(sandpack): update next.js sandpack layout --- documentation/docusaurus.config.js | 13 + documentation/plugins/tutorial-navigation.js | 93 +++ documentation/plugins/tutorial-navigation.ts | 37 + documentation/sidebars.js | 4 +- documentation/src/assets/nav-menu.ts | 2 +- .../src/components/sandpack/index.tsx | 81 ++- .../context/tutorial-parameter-context.tsx | 150 ++++ .../src/context/tutorial-visit-context.tsx | 72 ++ documentation/src/pages/tutorial/index.tsx | 6 + .../refine-theme/common-header/constants.ts | 2 +- documentation/src/refine-theme/css/custom.css | 14 +- documentation/src/refine-theme/doc-toc.tsx | 2 - .../src/refine-theme/footer-data.tsx | 2 +- .../src/refine-theme/landing-ghost-button.tsx | 2 +- .../src/refine-theme/tutorial-conditional.tsx | 18 + .../refine-theme/tutorial-document-layout.tsx | 94 +++ .../src/refine-theme/tutorial-footer.tsx | 79 +++ .../src/refine-theme/tutorial-header.tsx | 354 ++++++++++ .../refine-theme/tutorial-item-content.tsx | 39 ++ .../src/refine-theme/tutorial-item-layout.tsx | 6 + .../src/refine-theme/tutorial-item.tsx | 32 + .../src/refine-theme/tutorial-navigation.tsx | 403 +++++++++++ .../src/refine-theme/tutorial-page-layout.tsx | 13 + .../src/refine-theme/tutorial-paginator.tsx | 174 +++++ .../tutorial-parameter-dropdown.tsx | 210 ++++++ .../src/refine-theme/tutorial-refine-logo.tsx | 79 +++ .../src/refine-theme/tutorial-sandpack.tsx | 648 ++++++++++++++++++ .../src/refine-theme/tutorial-utils.tsx | 141 ++++ .../src/theme/MDXComponents/index.js | 4 +- documentation/src/theme/SearchBar/index.tsx | 2 +- .../src/theme/TutorialItem/Layout/index.js | 1 + .../src/theme/TutorialItem/Paginator/index.js | 1 + documentation/src/theme/TutorialItem/index.js | 1 + .../src/theme/TutorialPage/Layout/index.js | 11 + documentation/src/theme/TutorialPage/index.js | 65 ++ documentation/tailwind.config.js | 5 + .../authentication/auth-pages/index.md | 11 + .../authentication/auth-pages/sandpack.tsx | 20 + .../authentication/auth-provider/index.md | 11 + .../authentication/auth-provider/sandpack.tsx | 20 + .../tutorial/authentication/intro/index.md | 11 + .../authentication/intro/sandpack.tsx | 20 + .../protecting-content/index.md | 11 + .../protecting-content/sandpack.tsx | 20 + .../essentials/data-fetching/index.md | 11 + .../essentials/data-fetching/sandpack.tsx | 20 + .../tutorial/essentials/forms/index.md | 11 + .../tutorial/essentials/forms/sandpack.tsx | 20 + .../tutorial/essentials/intro/index.md | 26 + .../tutorial/essentials/intro/sandpack.tsx | 39 ++ .../tutorial/essentials/setup/index.md | 11 + .../tutorial/essentials/setup/sandpack.tsx | 20 + .../tutorial/essentials/tables/index.md | 11 + .../tutorial/essentials/tables/sandpack.tsx | 20 + .../tutorial/next-steps/cli/next-js/index.md | 11 + .../next-steps/cli/next-js/sandpack.tsx | 20 + .../next-steps/cli/react-router/index.md | 11 + .../next-steps/cli/react-router/sandpack.tsx | 20 + .../tutorial/next-steps/cli/remix/index.md | 11 + .../next-steps/cli/remix/sandpack.tsx | 20 + .../next-steps/devtools/next-js/index.md | 11 + .../next-steps/devtools/next-js/sandpack.tsx | 20 + .../next-steps/devtools/react-router/index.md | 11 + .../devtools/react-router/sandpack.tsx | 20 + .../next-steps/devtools/remix/index.md | 11 + .../next-steps/devtools/remix/sandpack.tsx | 20 + .../next-steps/inferencer/ant-design/index.md | 11 + .../inferencer/ant-design/sandpack.tsx | 20 + .../inferencer/material-ui/index.md | 11 + .../inferencer/material-ui/sandpack.tsx | 20 + .../tutorial/next-steps/intro/index.md | 11 + .../tutorial/next-steps/intro/sandpack.tsx | 20 + .../tutorial/next-steps/summary/index.md | 11 + .../tutorial/next-steps/summary/sandpack.tsx | 20 + .../routing/authentication/next-js/index.md | 11 + .../authentication/next-js/sandpack.tsx | 20 + .../authentication/react-router/index.md | 11 + .../authentication/react-router/sandpack.tsx | 20 + .../routing/authentication/remix/index.md | 11 + .../routing/authentication/remix/sandpack.tsx | 20 + .../routing/inferring-parameters/index.md | 11 + .../routing/inferring-parameters/sandpack.tsx | 20 + documentation/tutorial/routing/intro/index.md | 11 + .../tutorial/routing/intro/sandpack.tsx | 20 + .../routing/redirects/next-js/index.md | 11 + .../routing/redirects/next-js/sandpack.tsx | 20 + .../routing/redirects/react-router/index.md | 11 + .../redirects/react-router/sandpack.tsx | 20 + .../tutorial/routing/redirects/remix/index.md | 11 + .../routing/redirects/remix/sandpack.tsx | 20 + .../routing/resource-definition/index.md | 11 + .../routing/resource-definition/sandpack.tsx | 20 + .../routing/syncing-state/next-js/index.md | 11 + .../syncing-state/next-js/sandpack.tsx | 20 + .../syncing-state/react-router/index.md | 11 + .../syncing-state/react-router/sandpack.tsx | 20 + .../routing/syncing-state/remix/index.md | 11 + .../routing/syncing-state/remix/sandpack.tsx | 20 + .../ant-design/next-js/index.md | 11 + .../ant-design/next-js/sandpack.tsx | 20 + .../ant-design/react-router/index.md | 11 + .../ant-design/react-router/sandpack.tsx | 20 + .../crud-components/ant-design/remix/index.md | 11 + .../ant-design/remix/sandpack.tsx | 20 + .../material-ui/next-js/index.md | 11 + .../material-ui/next-js/sandpack.tsx | 20 + .../material-ui/react-router/index.md | 11 + .../material-ui/react-router/sandpack.tsx | 20 + .../material-ui/remix/index.md | 11 + .../material-ui/remix/sandpack.tsx | 20 + .../tutorial/ui-libraries/intro/index.md | 11 + .../tutorial/ui-libraries/intro/sandpack.tsx | 20 + .../layout/ant-design/next-js/index.md | 11 + .../layout/ant-design/next-js/sandpack.tsx | 20 + .../layout/ant-design/react-router/index.md | 11 + .../ant-design/react-router/sandpack.tsx | 20 + .../layout/ant-design/remix/index.md | 11 + .../layout/ant-design/remix/sandpack.tsx | 20 + .../layout/material-ui/next-js/index.md | 11 + .../layout/material-ui/next-js/sandpack.tsx | 20 + .../layout/material-ui/react-router/index.md | 11 + .../material-ui/react-router/sandpack.tsx | 20 + .../layout/material-ui/remix/index.md | 11 + .../layout/material-ui/remix/sandpack.tsx | 20 + .../refactoring/ant-design/next-js/index.md | 11 + .../ant-design/next-js/sandpack.tsx | 20 + .../ant-design/react-router/index.md | 11 + .../ant-design/react-router/sandpack.tsx | 20 + .../refactoring/ant-design/remix/index.md | 11 + .../refactoring/ant-design/remix/sandpack.tsx | 20 + .../refactoring/material-ui/next-js/index.md | 11 + .../material-ui/next-js/sandpack.tsx | 20 + .../material-ui/react-router/index.md | 11 + .../material-ui/react-router/sandpack.tsx | 20 + .../refactoring/material-ui/remix/index.md | 11 + .../material-ui/remix/sandpack.tsx | 20 + documentation/tutorials.js | 92 +++ 137 files changed, 4507 insertions(+), 29 deletions(-) create mode 100644 documentation/plugins/tutorial-navigation.js create mode 100644 documentation/plugins/tutorial-navigation.ts create mode 100644 documentation/src/context/tutorial-parameter-context.tsx create mode 100644 documentation/src/context/tutorial-visit-context.tsx create mode 100644 documentation/src/pages/tutorial/index.tsx create mode 100644 documentation/src/refine-theme/tutorial-conditional.tsx create mode 100644 documentation/src/refine-theme/tutorial-document-layout.tsx create mode 100644 documentation/src/refine-theme/tutorial-footer.tsx create mode 100644 documentation/src/refine-theme/tutorial-header.tsx create mode 100644 documentation/src/refine-theme/tutorial-item-content.tsx create mode 100644 documentation/src/refine-theme/tutorial-item-layout.tsx create mode 100644 documentation/src/refine-theme/tutorial-item.tsx create mode 100644 documentation/src/refine-theme/tutorial-navigation.tsx create mode 100644 documentation/src/refine-theme/tutorial-page-layout.tsx create mode 100644 documentation/src/refine-theme/tutorial-paginator.tsx create mode 100644 documentation/src/refine-theme/tutorial-parameter-dropdown.tsx create mode 100644 documentation/src/refine-theme/tutorial-refine-logo.tsx create mode 100644 documentation/src/refine-theme/tutorial-sandpack.tsx create mode 100644 documentation/src/refine-theme/tutorial-utils.tsx create mode 100644 documentation/src/theme/TutorialItem/Layout/index.js create mode 100644 documentation/src/theme/TutorialItem/Paginator/index.js create mode 100644 documentation/src/theme/TutorialItem/index.js create mode 100644 documentation/src/theme/TutorialPage/Layout/index.js create mode 100644 documentation/src/theme/TutorialPage/index.js create mode 100644 documentation/tutorial/authentication/auth-pages/index.md create mode 100644 documentation/tutorial/authentication/auth-pages/sandpack.tsx create mode 100644 documentation/tutorial/authentication/auth-provider/index.md create mode 100644 documentation/tutorial/authentication/auth-provider/sandpack.tsx create mode 100644 documentation/tutorial/authentication/intro/index.md create mode 100644 documentation/tutorial/authentication/intro/sandpack.tsx create mode 100644 documentation/tutorial/authentication/protecting-content/index.md create mode 100644 documentation/tutorial/authentication/protecting-content/sandpack.tsx create mode 100644 documentation/tutorial/essentials/data-fetching/index.md create mode 100644 documentation/tutorial/essentials/data-fetching/sandpack.tsx create mode 100644 documentation/tutorial/essentials/forms/index.md create mode 100644 documentation/tutorial/essentials/forms/sandpack.tsx create mode 100644 documentation/tutorial/essentials/intro/index.md create mode 100644 documentation/tutorial/essentials/intro/sandpack.tsx create mode 100644 documentation/tutorial/essentials/setup/index.md create mode 100644 documentation/tutorial/essentials/setup/sandpack.tsx create mode 100644 documentation/tutorial/essentials/tables/index.md create mode 100644 documentation/tutorial/essentials/tables/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/cli/next-js/index.md create mode 100644 documentation/tutorial/next-steps/cli/next-js/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/cli/react-router/index.md create mode 100644 documentation/tutorial/next-steps/cli/react-router/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/cli/remix/index.md create mode 100644 documentation/tutorial/next-steps/cli/remix/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/devtools/next-js/index.md create mode 100644 documentation/tutorial/next-steps/devtools/next-js/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/devtools/react-router/index.md create mode 100644 documentation/tutorial/next-steps/devtools/react-router/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/devtools/remix/index.md create mode 100644 documentation/tutorial/next-steps/devtools/remix/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/inferencer/ant-design/index.md create mode 100644 documentation/tutorial/next-steps/inferencer/ant-design/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/inferencer/material-ui/index.md create mode 100644 documentation/tutorial/next-steps/inferencer/material-ui/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/intro/index.md create mode 100644 documentation/tutorial/next-steps/intro/sandpack.tsx create mode 100644 documentation/tutorial/next-steps/summary/index.md create mode 100644 documentation/tutorial/next-steps/summary/sandpack.tsx create mode 100644 documentation/tutorial/routing/authentication/next-js/index.md create mode 100644 documentation/tutorial/routing/authentication/next-js/sandpack.tsx create mode 100644 documentation/tutorial/routing/authentication/react-router/index.md create mode 100644 documentation/tutorial/routing/authentication/react-router/sandpack.tsx create mode 100644 documentation/tutorial/routing/authentication/remix/index.md create mode 100644 documentation/tutorial/routing/authentication/remix/sandpack.tsx create mode 100644 documentation/tutorial/routing/inferring-parameters/index.md create mode 100644 documentation/tutorial/routing/inferring-parameters/sandpack.tsx create mode 100644 documentation/tutorial/routing/intro/index.md create mode 100644 documentation/tutorial/routing/intro/sandpack.tsx create mode 100644 documentation/tutorial/routing/redirects/next-js/index.md create mode 100644 documentation/tutorial/routing/redirects/next-js/sandpack.tsx create mode 100644 documentation/tutorial/routing/redirects/react-router/index.md create mode 100644 documentation/tutorial/routing/redirects/react-router/sandpack.tsx create mode 100644 documentation/tutorial/routing/redirects/remix/index.md create mode 100644 documentation/tutorial/routing/redirects/remix/sandpack.tsx create mode 100644 documentation/tutorial/routing/resource-definition/index.md create mode 100644 documentation/tutorial/routing/resource-definition/sandpack.tsx create mode 100644 documentation/tutorial/routing/syncing-state/next-js/index.md create mode 100644 documentation/tutorial/routing/syncing-state/next-js/sandpack.tsx create mode 100644 documentation/tutorial/routing/syncing-state/react-router/index.md create mode 100644 documentation/tutorial/routing/syncing-state/react-router/sandpack.tsx create mode 100644 documentation/tutorial/routing/syncing-state/remix/index.md create mode 100644 documentation/tutorial/routing/syncing-state/remix/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/index.md create mode 100644 documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/index.md create mode 100644 documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/crud-components/ant-design/remix/index.md create mode 100644 documentation/tutorial/ui-libraries/crud-components/ant-design/remix/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/index.md create mode 100644 documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/index.md create mode 100644 documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/crud-components/material-ui/remix/index.md create mode 100644 documentation/tutorial/ui-libraries/crud-components/material-ui/remix/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/intro/index.md create mode 100644 documentation/tutorial/ui-libraries/intro/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/layout/ant-design/next-js/index.md create mode 100644 documentation/tutorial/ui-libraries/layout/ant-design/next-js/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/layout/ant-design/react-router/index.md create mode 100644 documentation/tutorial/ui-libraries/layout/ant-design/react-router/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/layout/ant-design/remix/index.md create mode 100644 documentation/tutorial/ui-libraries/layout/ant-design/remix/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/layout/material-ui/next-js/index.md create mode 100644 documentation/tutorial/ui-libraries/layout/material-ui/next-js/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/layout/material-ui/react-router/index.md create mode 100644 documentation/tutorial/ui-libraries/layout/material-ui/react-router/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/layout/material-ui/remix/index.md create mode 100644 documentation/tutorial/ui-libraries/layout/material-ui/remix/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/index.md create mode 100644 documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/index.md create mode 100644 documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/refactoring/ant-design/remix/index.md create mode 100644 documentation/tutorial/ui-libraries/refactoring/ant-design/remix/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/index.md create mode 100644 documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/index.md create mode 100644 documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/sandpack.tsx create mode 100644 documentation/tutorial/ui-libraries/refactoring/material-ui/remix/index.md create mode 100644 documentation/tutorial/ui-libraries/refactoring/material-ui/remix/sandpack.tsx create mode 100644 documentation/tutorials.js diff --git a/documentation/docusaurus.config.js b/documentation/docusaurus.config.js index efb9301ae9ec..13e1ad5f0129 100644 --- a/documentation/docusaurus.config.js +++ b/documentation/docusaurus.config.js @@ -143,6 +143,19 @@ const siteConfig = { "./plugins/clarity.js", "./plugins/templates.js", "./plugins/example-redirects.js", + "./plugins/tutorial-navigation.js", + [ + "@docusaurus/plugin-content-docs", + { + id: "tutorial", + path: "tutorial", + routeBasePath: "tutorial", + sidebarPath: false, + docLayoutComponent: "@theme/TutorialPage", + docItemComponent: "@theme/TutorialItem", + include: ["**/index.md"], + }, + ], ], themeConfig: { prism: { diff --git a/documentation/plugins/tutorial-navigation.js b/documentation/plugins/tutorial-navigation.js new file mode 100644 index 000000000000..c9003c102d65 --- /dev/null +++ b/documentation/plugins/tutorial-navigation.js @@ -0,0 +1,93 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function _interopRequireWildcard(obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + newObj[key] = obj[key]; + } + } + } + newObj.default = obj; + return newObj; + } +} +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; +} +function _optionalChain(ops) { + let lastAccessLHS = undefined; + let value = ops[0]; + let i = 1; + while (i < ops.length) { + const op = ops[i]; + const fn = ops[i + 1]; + i += 2; + if ( + (op === "optionalAccess" || op === "optionalCall") && + value == null + ) { + return undefined; + } + if (op === "access" || op === "optionalAccess") { + lastAccessLHS = value; + value = fn(value); + } else if (op === "call" || op === "optionalCall") { + value = fn((...args) => value.call(lastAccessLHS, ...args)); + lastAccessLHS = undefined; + } + } + return value; +} +var _path = require("path"); +var _path2 = _interopRequireDefault(_path); + +function plugin() { + return { + name: "docusaurus-plugin-refine-tutorial-navigation", + configureWebpack(config) { + return { + resolve: { + alias: { + "@tutorial-navigation": _path2.default.join( + _optionalChain([ + config, + "access", + (_) => _.resolve, + "optionalAccess", + (_2) => _2.alias, + "optionalAccess", + (_3) => _3["@generated"], + ]), + "docusaurus-plugin-refine-tutorial-navigation", + "default", + ), + }, + }, + }; + }, + getPathsToWatch() { + return [_path2.default.join(__dirname, "../tutorials.js")]; + }, + async loadContent() { + const tutorials = await Promise.resolve().then(() => + _interopRequireWildcard(require("../tutorials.js")), + ); + + return tutorials.tutorial; + }, + async contentLoaded({ content, allContent, actions }) { + const { createData } = actions; + + await createData( + `tutorial-navigation-data.json`, + JSON.stringify(content), + ); + }, + }; +} +exports.default = plugin; diff --git a/documentation/plugins/tutorial-navigation.ts b/documentation/plugins/tutorial-navigation.ts new file mode 100644 index 000000000000..6382f28d7fac --- /dev/null +++ b/documentation/plugins/tutorial-navigation.ts @@ -0,0 +1,37 @@ +import { Plugin } from "@docusaurus/types"; +import path from "path"; + +export default function plugin(): Plugin { + return { + name: "docusaurus-plugin-refine-tutorial-navigation", + configureWebpack(config) { + return { + resolve: { + alias: { + "@tutorial-navigation": path.join( + config.resolve?.alias?.["@generated"], + "docusaurus-plugin-refine-tutorial-navigation", + "default", + ), + }, + }, + }; + }, + getPathsToWatch() { + return [path.join(__dirname, "../tutorials.js")]; + }, + async loadContent() { + const tutorials = await import("../tutorials.js"); + + return tutorials.tutorial; + }, + async contentLoaded({ content, allContent, actions }): Promise { + const { createData } = actions; + + await createData( + `tutorial-navigation-data.json`, + JSON.stringify(content), + ); + }, + }; +} diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 86b912b8ade1..33a80ab0c6df 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -10,8 +10,8 @@ module.exports = { "getting-started/overview", "getting-started/quickstart", { - type: "doc", - id: "tutorial/introduction/index", + type: "link", + href: "/tutorial/essentials/intro", label: "Tutorial", }, { diff --git a/documentation/src/assets/nav-menu.ts b/documentation/src/assets/nav-menu.ts index 3b1a2315fc56..664ea9d367c0 100644 --- a/documentation/src/assets/nav-menu.ts +++ b/documentation/src/assets/nav-menu.ts @@ -33,7 +33,7 @@ export const POPOVERMENUS: NavMenu[] = [ { label: "Tutorial", description: "Your first Refine application", - link: "/docs/tutorial/introduction/index/", + link: "/tutorial/essentials/intro", icon: TutorialIcon, }, { diff --git a/documentation/src/components/sandpack/index.tsx b/documentation/src/components/sandpack/index.tsx index 5d829f1da8f1..488633f871eb 100644 --- a/documentation/src/components/sandpack/index.tsx +++ b/documentation/src/components/sandpack/index.tsx @@ -78,6 +78,7 @@ const SandpackBase = ({ showTabs: true, initMode: "lazy", classes: { + "sp-bridge-frame": "!hidden", "sp-layout": "!rounded-lg !border-gray-300 dark:!border-gray-700", "sp-editor": "!gap-0 border-r !border-r-gray-300 dark:!border-r-gray-700", @@ -93,6 +94,36 @@ const SandpackBase = ({ ), "sp-icon-standalone": "!bg-gray-300 dark:!bg-gray-700 !text-gray-400 dark:!text-gray-500", + "sp-file-explorer": + "border-r !border-r-gray-300 dark:!border-r-gray-700", + "sp-console": clsx( + "not-prose", + "!border-t-0 !border !border-solid !border-t-none", + "!border-gray-300 dark:!border-gray-700", + "!rounded-bl-lg !rounded-br-lg", + "!bg-refine-react-light-code", + "dark:!bg-refine-react-dark-code", + ), + "sp-console-header": clsx( + "!bg-gray-0 dark:!bg-gray-800", + "border-b border-solid !border-b-gray-300 dark:!border-b-gray-700", + "!h-[32px] !min-h-[32px]", + ), + "sp-console-header-actions": clsx("h-full", "!gap-0"), + "sp-console-header-button": clsx( + "!bg-transparent", + "!border !border-solid !border-b-0 !border-x-gray-300 dark:!border-x-gray-700", + "!border-t-2 !border-t-transparent [&[data-active='true']]:!border-t-refine-react-light-link dark:[&[data-active='true']]:!border-t-refine-react-dark-link", + "h-full", + "!text-gray-800 dark:!text-gray-100", + "!rounded-none", + "-ml-px", + ), + "sp-console-list": clsx( + "!bg-refine-react-light-code", + "dark:!bg-refine-react-dark-code", + "[&>code]:!bg-transparent", + ), "sp-tab-button": clsx( "!h-8", "!px-2 !pb-2 !pt-1.5", @@ -196,7 +227,17 @@ const SandpackBase = ({ files={ files as TemplateFiles } - options={providerOptions} + options={{ + ...providerOptions, + classes: { + ...providerOptions.classes, + "sp-layout": clsx( + providerOptions.classes?.["sp-layout"], + showConsole && + "!rounded-bl-none !rounded-br-none", + ), + }, + }} template={template} theme={ colorMode === "light" @@ -261,21 +302,6 @@ const SandpackBase = ({ }} /> )} - {showConsole ? ( - - ) : null} {showHandle ? ( )} + {showConsole ? ( + + ) : null}
+

{`Dependencies: ${Object.keys(dependencies ?? {}).map( @@ -397,13 +444,13 @@ const SandpackNextJS = (props: Props) => { showConsole: true, showNavigator: true, dependencies: { - ...props.dependencies, "@refinedev/core": "latest", "@refinedev/simple-rest": "latest", "@refinedev/nextjs-router": "latest", "@types/react": "^18.0.0", "@types/node": "^16.0.0", typescript: "^4.7.4", + ...props.dependencies, }, files: { "/pages/index.tsx": { diff --git a/documentation/src/context/tutorial-parameter-context.tsx b/documentation/src/context/tutorial-parameter-context.tsx new file mode 100644 index 000000000000..4c4180c2f9ef --- /dev/null +++ b/documentation/src/context/tutorial-parameter-context.tsx @@ -0,0 +1,150 @@ +import React from "react"; + +/* @ts-expect-error `/internal` is not directly exported but required in this case */ +import { useDoc } from "@docusaurus/theme-common/internal"; + +import { useLocation } from "@docusaurus/router"; +import { DocElement, tutorialData } from "../refine-theme/tutorial-utils"; + +type Tutorial = { + label: string; + defaultParameters: Record; + parameterOptions: Record< + string, + Array<{ + label: string; + value: string; + }> + >; + units: Array<{ + title: string; + id: string; + items: Array; + }>; +}; + +type TutorialParameterContextType = { + parameters?: Record; + options: Record< + string, + Array<{ label: string; value: string }> | undefined + >; + settled: boolean; + setParameters: (parameters: Record) => void; +}; + +export const TutorialParameterContext = + React.createContext({ + parameters: {}, + options: {}, + settled: false, + setParameters: () => 0, + }); + +export const TUTORIAL_PARAMETER_LOCAL_STORAGE_KEY = "tutorial-parameters"; + +const getCurrentParameterFromDocumentId = ( + id: string, + options: (typeof tutorialData.parameterOptions)[string], +) => { + for (const option of options) { + if (id.includes(`/${option.value}`)) { + return option.value; + } + } + return undefined; +}; + +const getCurrentParametersFromDocumentId = (id: string) => { + const parameters: Record = {}; + for (const [key, options] of Object.entries( + tutorialData.parameterOptions, + )) { + const currentParameter = getCurrentParameterFromDocumentId(id, options); + if (currentParameter) { + parameters[key] = currentParameter; + } + } + return parameters; +}; + +const useCurrentParameters = () => { + const { pathname } = useLocation(); + + const currentParameters = React.useMemo( + () => getCurrentParametersFromDocumentId(pathname), + [pathname], + ); + + return currentParameters; +}; + +export const TutorialParameterProvider: React.FC = ({ children }) => { + const [parameters, _setParameters] = React.useState< + Record | undefined + >(undefined); + const [settled, setSettled] = React.useState(false); + const currentParameters = useCurrentParameters(); + + const setParameters = React.useCallback((next: Record) => { + _setParameters((p) => { + try { + localStorage.setItem( + TUTORIAL_PARAMETER_LOCAL_STORAGE_KEY, + JSON.stringify({ ...p, ...next }), + ); + } catch (e) { + console.error(e); + } + + return { ...p, ...next }; + }); + }, []); + + React.useEffect(() => { + try { + const storedParameters = localStorage.getItem( + TUTORIAL_PARAMETER_LOCAL_STORAGE_KEY, + ); + + if (storedParameters) { + setParameters({ + ...tutorialData.defaultParameters, + ...JSON.parse(storedParameters), + ...currentParameters, + }); + } else { + setParameters({ + ...tutorialData.defaultParameters, + ...currentParameters, + }); + } + } catch (e) { + setParameters({ + ...tutorialData.defaultParameters, + ...currentParameters, + }); + } + + setSettled(true); + }, []); + + return ( + + {children} + + ); +}; + +export const useTutorialParameters = () => { + const ctx = React.useContext(TutorialParameterContext); + + return ctx; +}; diff --git a/documentation/src/context/tutorial-visit-context.tsx b/documentation/src/context/tutorial-visit-context.tsx new file mode 100644 index 000000000000..073fa3907c97 --- /dev/null +++ b/documentation/src/context/tutorial-visit-context.tsx @@ -0,0 +1,72 @@ +import React from "react"; + +type Status = { + visited: boolean; +}; + +type TutorialVisitContextType = { + visited: Record; + setVisited: (id: string, status?: boolean) => void; +}; + +export const TutorialVisitContext = + React.createContext({ + visited: {}, + setVisited: () => 0, + }); + +export const TUTORIAL_VISIT_LOCAL_STORAGE_KEY = "tutorial-visits"; + +export const TutorialVisitProvider: React.FC = ({ children }) => { + const [visited, _setVisited] = React.useState< + TutorialVisitContextType["visited"] + >({}); + + React.useEffect(() => { + // read from local storage + try { + const storedVisits = localStorage.getItem( + TUTORIAL_VISIT_LOCAL_STORAGE_KEY, + ); + + if (storedVisits) { + _setVisited(JSON.parse(storedVisits)); + } + } catch (e) { + console.error(e); + } + }, []); + + const setVisited = React.useCallback((id: string, status = true) => { + _setVisited((p) => { + const next = { + ...p, + [id]: { + visited: status, + }, + }; + try { + localStorage.setItem( + TUTORIAL_VISIT_LOCAL_STORAGE_KEY, + JSON.stringify(next), + ); + } catch (e) { + console.error(e); + } + + return next; + }); + }, []); + + return ( + + {children} + + ); +}; + +export const useTutorialVisits = () => { + const ctx = React.useContext(TutorialVisitContext); + + return ctx; +}; diff --git a/documentation/src/pages/tutorial/index.tsx b/documentation/src/pages/tutorial/index.tsx new file mode 100644 index 000000000000..683bc4d25701 --- /dev/null +++ b/documentation/src/pages/tutorial/index.tsx @@ -0,0 +1,6 @@ +import React from "react"; +import { Redirect } from "@docusaurus/router"; + +export default function TutorialIndex() { + return ; +} diff --git a/documentation/src/refine-theme/common-header/constants.ts b/documentation/src/refine-theme/common-header/constants.ts index 0004d75ac513..624ce3d62420 100644 --- a/documentation/src/refine-theme/common-header/constants.ts +++ b/documentation/src/refine-theme/common-header/constants.ts @@ -48,7 +48,7 @@ export const MENU_ITEMS: MenuItemType[] = [ { label: "Tutorial", description: "Create your first Refine application.", - link: "/docs/tutorial/introduction/index/", + link: "/tutorial/essentials/intro", icon: TutorialIcon, }, { diff --git a/documentation/src/refine-theme/css/custom.css b/documentation/src/refine-theme/css/custom.css index a956471adc00..7759ca517b57 100644 --- a/documentation/src/refine-theme/css/custom.css +++ b/documentation/src/refine-theme/css/custom.css @@ -66,7 +66,7 @@ @apply prose-pre:font-jetBrains-mono; - @apply prose-blockquote:last:mb-0 prose-blockquote:border-l-4 prose-blockquote:rounded-lg prose-blockquote:py-4 prose-blockquote:pr-4 prose-blockquote:pl-3 prose-blockquote:font-normal prose-blockquote:not-italic; + @apply prose-blockquote:border-l-4 prose-blockquote:rounded-lg prose-blockquote:py-4 prose-blockquote:pr-4 prose-blockquote:pl-3 prose-blockquote:font-normal prose-blockquote:not-italic; @apply prose-blockquote:bg-gray-100 prose-blockquote:text-gray-800 prose-blockquote:border-l-gray-400; @apply prose-blockquote:dark:bg-gray-700 prose-blockquote:dark:text-gray-100 prose-blockquote:dark:border-l-gray-500; @@ -420,6 +420,7 @@ html[data-active-page="index"] body { } .sp-preview .sp-loading + .sp-custom-loading { + @apply bg-gray-0 dark:bg-gray-800; display: flex; position: absolute; left: 0; @@ -539,4 +540,15 @@ h4 del code { .docs-wrapper *::selection { @apply bg-refine-selection text-gray-800; +} + +.sp-bridge-frame { + display: none !important; +} + +.sp-console-list code { + @apply !bg-none !bg-transparent !border-none; +} +.sp-console-list .sp-console-item { + @apply !px-2 !py-px text-gray-600 dark:text-gray-200; } \ No newline at end of file diff --git a/documentation/src/refine-theme/doc-toc.tsx b/documentation/src/refine-theme/doc-toc.tsx index 09df2d63b54e..0114926a2cef 100644 --- a/documentation/src/refine-theme/doc-toc.tsx +++ b/documentation/src/refine-theme/doc-toc.tsx @@ -1,7 +1,6 @@ import { useDoc } from "@docusaurus/theme-common/internal"; import clsx from "clsx"; import React from "react"; -// import { useDocTOCwithTutorial } from "../components/tutorial-toc/index"; import { useHistory, useLocation } from "@docusaurus/router"; export const TOCItem = ({ @@ -139,7 +138,6 @@ export const useTOC = () => { setActiveId(baseActiveId); }, [baseActiveId]); - // const docTOC = useDocTOCwithTutorial(); const { toc } = useDoc(); const hasTOC = toc?.length > 0; diff --git a/documentation/src/refine-theme/footer-data.tsx b/documentation/src/refine-theme/footer-data.tsx index 3629617f1be3..d88f9b4b4c74 100644 --- a/documentation/src/refine-theme/footer-data.tsx +++ b/documentation/src/refine-theme/footer-data.tsx @@ -16,7 +16,7 @@ export const menuItems = [ }, { label: "Tutorials", - href: "/docs/tutorial/introduction/index/", + href: "/tutorial/essentials/intro", }, { label: "Blog", diff --git a/documentation/src/refine-theme/landing-ghost-button.tsx b/documentation/src/refine-theme/landing-ghost-button.tsx index 072a233f8246..2d5dc7d02374 100644 --- a/documentation/src/refine-theme/landing-ghost-button.tsx +++ b/documentation/src/refine-theme/landing-ghost-button.tsx @@ -7,7 +7,7 @@ import { DocItemOutlined } from "./icons/doc-item-outlined"; export const LandingGhostButton = () => { return ( { + const { parameters } = useTutorialParameters(); + + if (parameters?.[parameter] === value) { + return <>{children}; + } + + return null; +}; diff --git a/documentation/src/refine-theme/tutorial-document-layout.tsx b/documentation/src/refine-theme/tutorial-document-layout.tsx new file mode 100644 index 000000000000..798a710b60d5 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-document-layout.tsx @@ -0,0 +1,94 @@ +import clsx from "clsx"; +import React from "react"; +import TutorialItemContent from "./tutorial-item-content"; + +import { FULL_WIDTH_TABLE_VARIABLE_NAME } from "./common-table"; +import { TutorialParameterDropdown } from "./tutorial-parameter-dropdown"; +import { TutorialPaginator } from "./tutorial-paginator"; + +export const TutorialDocumentLayout = ({ children }) => { + const containerRef = React.useRef(null); + + React.useLayoutEffect(() => { + const containerElement = containerRef.current; + if (containerElement) { + const width = containerElement.getBoundingClientRect().width; + containerElement.style.setProperty( + `--${FULL_WIDTH_TABLE_VARIABLE_NAME}`, + `${width}px`, + ); + } + + // on resize, recompute the full width table variable + const handleResize = () => { + const width = containerElement.getBoundingClientRect().width; + containerElement.style.setProperty( + `--${FULL_WIDTH_TABLE_VARIABLE_NAME}`, + `${width}px`, + ); + }; + + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [containerRef]); + + return ( + <> +

+
+
+
+ + +
+
+
+ {children} +
+
+
+ +
+
+ + ); +}; diff --git a/documentation/src/refine-theme/tutorial-footer.tsx b/documentation/src/refine-theme/tutorial-footer.tsx new file mode 100644 index 000000000000..b05ceebb4cb8 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-footer.tsx @@ -0,0 +1,79 @@ +import clsx from "clsx"; +import React from "react"; +import { socialLinks } from "./footer-data"; + +export const TutorialFooter = () => { + return ( +
+
+
+ Refine © 2023 +
+ +
+
+ Join us on +
+
+ {socialLinks.map(({ href, icon: Icon }, i) => { + return ( + + + + ); + })} +
+
+
+
+ ); +}; diff --git a/documentation/src/refine-theme/tutorial-header.tsx b/documentation/src/refine-theme/tutorial-header.tsx new file mode 100644 index 000000000000..940d35ee7f16 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-header.tsx @@ -0,0 +1,354 @@ +import React from "react"; +import clsx from "clsx"; + +import SearchBar from "../theme/SearchBar"; + +import { TutorialRefineLogo } from "./tutorial-refine-logo"; +import { CommonHamburgerIcon } from "./common-hamburger-icon"; +import { CommonThemeToggle, CommonThemeToggleAlt } from "./common-theme-toggle"; +import { DocSearchButton } from "./doc-search-button"; +import { TutorialParameterDropdown } from "./tutorial-parameter-dropdown"; +import { TutorialNavigation } from "./tutorial-navigation"; +import { Dialog, Transition } from "@headlessui/react"; +import { socialLinks } from "./footer-data"; + +import Link from "@docusaurus/Link"; + +export const HEADER_HEIGHT = 65; + +const TutorialModal = ({ + isOpen, + onClose, +}: { + isOpen: boolean; + onClose: () => void; +}) => { + return ( + + + +
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ + Appearance + + +
+
+ + Refine Home + +
+
+ + Documentation + +
+
+
+ Join us on +
+
+ {socialLinks.map( + ({ href, icon: Icon }, i) => { + return ( + + + + ); + }, + )} +
+
+
+
+
+
+
+
+
+ ); +}; + +const Header = () => { + const [isSidebarOpen, setIsSidebarOpen] = React.useState(false); + + return ( +
+
+ +
+ +
+
+
+
+ + +
+
+ +
+ +
+ + >(function CustomButton(props, ref) { + return ( + + ); + })} + /> +
+ + + + setIsSidebarOpen(true)} + active={isSidebarOpen} + className="flex tutorial-md:!hidden" + /> +
+ setIsSidebarOpen(false)} + /> +
+ ); +}; + +export const MobileNavigation = () => { + return ( +
+ +
+ ); +}; + +export const TutorialHeader = React.memo(function TutorialHeaderComponent() { + return ( +
+
+ +
+ ); +}); diff --git a/documentation/src/refine-theme/tutorial-item-content.tsx b/documentation/src/refine-theme/tutorial-item-content.tsx new file mode 100644 index 000000000000..2b5c1d65cc01 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-item-content.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; +import { useDoc } from "@docusaurus/theme-common/internal"; +import Heading from "@theme/Heading"; +import MDXContent from "@theme/MDXContent"; +/** + Title can be declared inside md content or declared through + front matter and added manually. To make both cases consistent, + the added title is added under the same div.markdown block + See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120 + + We render a "synthetic title" if: + - user doesn't ask to hide it with front matter + - the markdown content does not already contain a top-level h1 heading +*/ +function useSyntheticTitle() { + const { metadata, frontMatter, contentTitle } = useDoc(); + const shouldRender = + !frontMatter.hide_title && typeof contentTitle === "undefined"; + if (!shouldRender) { + return null; + } + return metadata.title; +} + +export default function TutorialItemContent({ children }) { + const syntheticTitle = useSyntheticTitle(); + return ( +
+ {syntheticTitle && ( +
+ {syntheticTitle} +
+ )} + {children} +
+ ); +} diff --git a/documentation/src/refine-theme/tutorial-item-layout.tsx b/documentation/src/refine-theme/tutorial-item-layout.tsx new file mode 100644 index 000000000000..a51dbf97bdaf --- /dev/null +++ b/documentation/src/refine-theme/tutorial-item-layout.tsx @@ -0,0 +1,6 @@ +import React from "react"; +import MDXContent from "@theme/MDXContent"; + +export const TutorialItemLayout = ({ children }) => { + return {children}; +}; diff --git a/documentation/src/refine-theme/tutorial-item.tsx b/documentation/src/refine-theme/tutorial-item.tsx new file mode 100644 index 000000000000..94686e4ac638 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-item.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import clsx from "clsx"; +import { DocProvider } from "@docusaurus/theme-common/internal"; +import DocItemMetadata from "@theme/DocItem/Metadata"; +import TutorialItemLayout from "@theme/TutorialItem/Layout"; + +import { TutorialHeader } from "./tutorial-header"; +import { TutorialFooter } from "./tutorial-footer"; + +export const TutorialItem = (props) => { + const MDXComponent = props.content; + + return ( + + +
+ + + + +
+ +
+ ); +}; diff --git a/documentation/src/refine-theme/tutorial-navigation.tsx b/documentation/src/refine-theme/tutorial-navigation.tsx new file mode 100644 index 000000000000..513cd6be8028 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-navigation.tsx @@ -0,0 +1,403 @@ +import React from "react"; +import clsx from "clsx"; + +import Link from "@docusaurus/Link"; + +/* @ts-expect-error `/internal` is not directly exported but required in this case */ +import { useDoc } from "@docusaurus/theme-common/internal"; + +import { TriangleDownIcon } from "./icons/triangle-down"; +import { CommonCircleChevronLeft } from "./common-circle-chevron-left"; +import { useTutorialParameters } from "../context/tutorial-parameter-context"; +import { + DocElement, + findUnitByItemId, + getNext, + getPathFromId, + getPrevious, + getTitleFromId, + populateParametrizedId, + tutorialData, + useTutorialDocs, +} from "./tutorial-utils"; +import { useTutorialVisits } from "../context/tutorial-visit-context"; + +export const HEADER_HEIGHT = 65; + +const NavigationCheckEmpty = (props: React.SVGProps) => ( + + + +); + +const NavigationCheckFilled = (props: React.SVGProps) => ( + + + + +); + +const NavigationDropdown = () => { + const currentDoc = useDoc() as DocElement; + const tutorialDocs = useTutorialDocs(); + const { parameters: params, settled } = useTutorialParameters(); + const { visited, setVisited } = useTutorialVisits(); + + const currentUnit = settled + ? findUnitByItemId(currentDoc.metadata.id, params) + : undefined; + + const parameterPopulatedUnits = settled + ? tutorialData.units.map((unit) => { + return { + ...unit, + items: unit.items.map((item) => + populateParametrizedId(item, params), + ), + }; + }) + : []; + + const [dropdownOpen, setDropdownOpen] = React.useState(false); + const dropdownRef = React.useRef(null); + const buttonRef = React.useRef(null); + + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + !buttonRef.current.contains(event.target as Node) + ) { + setDropdownOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [dropdownRef]); + + React.useEffect(() => { + if (!visited[currentDoc.metadata.id]?.visited) { + const timeout = setTimeout(() => { + setVisited(currentDoc.metadata.id); + }, 10 * 1000); + + return () => clearTimeout(timeout); + } + + return () => 0; + }, [currentDoc?.metadata?.id, visited]); + + const isUnitVisited = (unit: string) => { + return parameterPopulatedUnits + .find((u) => u.id === unit) + ?.items.every((item) => visited[item]?.visited); + }; + + return ( +
+ +
+
+
+ {parameterPopulatedUnits.map((unit) => { + return ( +
+
+ {isUnitVisited(unit.id) ? ( + + ) : ( + + )} + + {unit.title} + +
+ {unit.items.map((item) => { + return ( + + {visited[item]?.visited ? ( + + ) : ( + + )} + + {getTitleFromId( + item, + tutorialDocs, + )} + + + ); + })} +
+ ); + })} +
+
+
+
+ ); +}; + +export const TutorialNavigation = () => { + const currentDoc = useDoc() as DocElement; + const tutorialDocs = useTutorialDocs(); + const { parameters: params, settled } = useTutorialParameters(); + + const nextId = settled + ? getNext(currentDoc.metadata.id, params) + : undefined; + const previousId = settled + ? getPrevious(currentDoc.metadata.id, params) + : undefined; + + const nextDoc = + settled && nextId + ? getPathFromId( + populateParametrizedId(nextId, params), + tutorialDocs, + ) + : undefined; + + const previousDoc = + settled && previousId + ? getPathFromId( + populateParametrizedId(previousId, params), + tutorialDocs, + ) + : undefined; + + return ( +
+ e.preventDefault()} + className={clsx( + previousDoc ? "opacity-100" : "opacity-50", + previousDoc ? "cursor-pointer" : "cursor-not-allowed", + "p-2 tutorial-md:p-2.5", + "text-gray-400 dark:text-gray-400", + "hover:text-gray-400 dark:hover:text-gray-400", + "bg-gray-100 dark:bg-refine-react-dark-code", + "rounded-full", + )} + > + + + + e.preventDefault()} + className={clsx( + nextDoc ? "opacity-100" : "opacity-50", + nextDoc ? "cursor-pointer" : "cursor-not-allowed", + "p-2 tutorial-md:p-2.5", + "text-gray-400 dark:text-gray-400", + "hover:text-gray-400 dark:hover:text-gray-400", + "bg-gray-100 dark:bg-refine-react-dark-code", + "rounded-full", + )} + > + + +
+ ); +}; diff --git a/documentation/src/refine-theme/tutorial-page-layout.tsx b/documentation/src/refine-theme/tutorial-page-layout.tsx new file mode 100644 index 000000000000..60e24ed4a87d --- /dev/null +++ b/documentation/src/refine-theme/tutorial-page-layout.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { TutorialParameterProvider } from "../context/tutorial-parameter-context"; +import { TutorialVisitProvider } from "../context/tutorial-visit-context"; + +type Props = React.PropsWithChildren<{}>; + +export const TutorialPageLayout = ({ children }: Props) => { + return ( + + {children} + + ); +}; diff --git a/documentation/src/refine-theme/tutorial-paginator.tsx b/documentation/src/refine-theme/tutorial-paginator.tsx new file mode 100644 index 000000000000..b80211d3108c --- /dev/null +++ b/documentation/src/refine-theme/tutorial-paginator.tsx @@ -0,0 +1,174 @@ +import React from "react"; +import clsx from "clsx"; +import Link from "@docusaurus/Link"; +import { ArrowLeftIcon } from "./icons/arrow-left"; +import { ArrowRightIcon } from "./icons/arrow-right"; + +import { useDoc } from "@docusaurus/theme-common/internal"; +import { + DocElement, + getNext, + getPathFromId, + getPrevious, + populateParametrizedId, + useTutorialDocs, +} from "./tutorial-utils"; +import { useTutorialParameters } from "../context/tutorial-parameter-context"; + +const TutorialPaginatorBase = (props) => { + const { previous, next } = props; + + return ( +
+ {previous && ( + +
+
+ + + + Previous +
+ + {previous.title} + +
+ + )} + {next && ( + +
+
+ Next + + + +
+ + + {next.title} + +
+ + )} +
+ ); +}; + +export const TutorialPaginator = () => { + const currentDoc = useDoc() as DocElement; + const tutorialDocs = useTutorialDocs(); + const { parameters: params, settled } = useTutorialParameters(); + + const nextIdRaw = settled + ? getNext(currentDoc.metadata.id, params) + : undefined; + const previousIdRaw = settled + ? getPrevious(currentDoc.metadata.id, params) + : undefined; + + const nextId = nextIdRaw + ? populateParametrizedId(nextIdRaw, params) + : undefined; + const previousId = previousIdRaw + ? populateParametrizedId(previousIdRaw, params) + : undefined; + + const nextPath = + settled && nextId ? getPathFromId(nextId, tutorialDocs) : undefined; + + const previousPath = + settled && previousId + ? getPathFromId(previousId, tutorialDocs) + : undefined; + + const nextTitle = + settled && nextId ? tutorialDocs[nextId]?.title : undefined; + const previousTitle = + settled && previousId ? tutorialDocs[previousId]?.title : undefined; + + return ( + + ); +}; diff --git a/documentation/src/refine-theme/tutorial-parameter-dropdown.tsx b/documentation/src/refine-theme/tutorial-parameter-dropdown.tsx new file mode 100644 index 000000000000..8c4daa7ca6e9 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-parameter-dropdown.tsx @@ -0,0 +1,210 @@ +import clsx from "clsx"; +import React, { SVGProps } from "react"; +import { TriangleDownIcon } from "@site/src/refine-theme/icons/triangle-down"; +import { Menu, Transition } from "@headlessui/react"; + +import { useHistory } from "@docusaurus/router"; + +/* @ts-expect-error Imports through webpack aliases does not work */ +import data from "@tutorial-navigation/tutorial-navigation-data.json"; +/* @ts-expect-error `/internal` is not directly exported but required in this case */ +import { useDoc } from "@docusaurus/theme-common/internal"; + +import { useTutorialParameters } from "../context/tutorial-parameter-context"; + +type DocElement = { + frontMatter: Record; + metadata: { + frontMatter: Record; + id: string; + permalink: string; + slug: string; + title: string; + }; +}; + +type Tutorial = { + label: string; + defaultParameters: Record; + parameterOptions: Record< + string, + Array<{ + label: string; + value: string; + }> + >; + units: Array<{ + title: string; + id: string; + items: Array; + }>; +}; + +const { units, parameterOptions, defaultParameters } = data as Tutorial; + +const Triangle = (props: SVGProps) => ( + + + +); + +type Props = { + className?: string; + label: string; + parameter: string; +}; + +export const TutorialParameterDropdown = ({ + label, + parameter, + className, +}: Props) => { + const { parameters, options, setParameters } = useTutorialParameters(); + const { replace } = useHistory(); + const { + metadata: { permalink }, + } = useDoc() as DocElement; + + return ( +
+ + {({ open }) => ( + <> + +
+ {`${label}: `} + + {options[parameter].find( + (el) => + el.value === + parameters?.[parameter], + )?.label ?? "Unknown"} + +
+ +
+ + + + + {options[parameter].map((option) => { + return ( + + {() => { + const isActive = + parameters?.[parameter] === + option.value; + + return ( + + ); + }} + + ); + })} + + + + )} +
+
+ ); +}; diff --git a/documentation/src/refine-theme/tutorial-refine-logo.tsx b/documentation/src/refine-theme/tutorial-refine-logo.tsx new file mode 100644 index 000000000000..6258782697f2 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-refine-logo.tsx @@ -0,0 +1,79 @@ +import Link from "@docusaurus/Link"; +import clsx from "clsx"; +import React from "react"; +import { openFigma } from "../utils/open-figma"; + +interface Props { + className?: string; +} + +export const TutorialRefineLogo = ({ className }: Props) => { + return ( +
+ + + + Refine Tutorial + + + {/* + + + Documentation + + */} +
+ ); +}; + +const Logo = (props: React.SVGProps) => ( + + + + + + +); diff --git a/documentation/src/refine-theme/tutorial-sandpack.tsx b/documentation/src/refine-theme/tutorial-sandpack.tsx new file mode 100644 index 000000000000..cf93992e5121 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-sandpack.tsx @@ -0,0 +1,648 @@ +import React from "react"; +import { TutorialDocumentLayout } from "./tutorial-document-layout"; +import clsx from "clsx"; + +import type { + CodeEditorProps, + SandpackFiles, + SandpackInternal, + SandpackInternalOptions, + SandpackPredefinedTemplate, + TemplateFiles, +} from "@codesandbox/sandpack-react"; + +import { nightOwl, aquaBlue } from "@codesandbox/sandpack-themes"; + +import { + SandpackCodeEditor, + SandpackFileExplorer, + SandpackPreview, + SandpackProvider, +} from "@codesandbox/sandpack-react"; + +import { useColorMode } from "@docusaurus/theme-common"; + +type SandpackProps = React.ComponentProps & { + startRoute?: string; + showOpenInCodeSandbox?: boolean; + showNavigator?: boolean; + showLineNumbers?: boolean; + initialPercentage?: number; + dependencies?: React.ComponentProps["customSetup"]["dependencies"]; + height?: number; + previewOnly?: boolean; + layout?: "row" | "col" | "col-reverse"; + className?: string; + wrapperClassName?: string; + showFiles?: boolean; + showReadOnly?: boolean; + showConsole?: boolean; + hidePreview?: boolean; + parentResizing?: boolean; +}; + +type Props = React.PropsWithChildren; + +const maxPercentage = 70; + +export const TutorialSandpack = ({ + children, + contentOnly, + ...sandpackProps +}: Props) => { + const [viewPercentage, setViewPercentage] = React.useState(45); + const [resizing, setResizing] = React.useState(false); + const containerRef = React.useRef(null); + + const [mobileVisiblePanel, setMobileVisiblePanel] = React.useState< + "editor" | "tutorial" + >("tutorial"); + + React.useEffect(() => { + const handleMouseUp = () => { + setResizing(false); + }; + + if (resizing !== null) { + window.addEventListener("mouseup", handleMouseUp); + + return () => { + window.removeEventListener("mouseup", handleMouseUp); + }; + } + + return; + }, [resizing]); + + React.useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (resizing) { + const containerRect = + containerRef.current?.getBoundingClientRect(); + + if (!containerRect) return; + + const newViewPercentage = Math.min( + maxPercentage, + Math.max( + 100 - maxPercentage, + ((e.clientX - containerRect.left) / + containerRect.width) * + 100, + ), + ); + + setViewPercentage(newViewPercentage); + } + }; + + if (resizing !== null) { + window.addEventListener("mousemove", handleMouseMove); + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + }; + } + + return; + }, [resizing]); + + React.useEffect(() => { + const currentCursor = document.body.style.cursor; + + if (resizing) { + document.body.style.cursor = "col-resize"; + } else { + document.body.style.cursor = "auto"; + } + + return () => { + document.body.style.cursor = currentCursor; + }; + }, [resizing]); + + const codeEditorOptions: CodeEditorProps = { + showTabs: sandpackProps?.options?.showTabs, + showLineNumbers: sandpackProps?.options?.showLineNumbers, + showInlineErrors: sandpackProps?.options?.showInlineErrors, + wrapContent: sandpackProps?.options?.wrapContent, + closableTabs: sandpackProps?.options?.closableTabs, + initMode: sandpackProps?.options?.initMode, + extensions: sandpackProps?.options?.codeEditor?.extensions, + extensionsKeymap: sandpackProps?.options?.codeEditor?.extensionsKeymap, + readOnly: sandpackProps?.options?.readOnly, + showReadOnly: + sandpackProps.showReadOnly ?? sandpackProps?.options?.showReadOnly, + additionalLanguages: + sandpackProps?.options?.codeEditor?.additionalLanguages, + }; + + return ( + +
+
+
+ +
{children}
+
+
+
+ +
+ +
+
+
+
+ + +
+
+
+ ); +}; + +const TutorialSandpackBase = ({ + children, + initialPercentage = 50, + dependencies, + options = { + showTabs: true, + initMode: "lazy", + classes: { + "sp-file-explorer": + "!h-full !w-[200px] border-r border-r-gray-300 dark:border-r-gray-700", + "sp-layout": "!rounded-lg !border-gray-300 dark:!border-gray-700", + "sp-close-button": "!visible", + "sp-editor": + "!h-full !gap-0 border-r !border-r-gray-300 dark:!border-r-gray-700", + "sp-stack": "!h-full", + "sp-tabs": + "!border-b-gray-300 dark:!border-b-gray-700 !bg-gray-0 dark:!bg-gray-800", + "sp-tabs-scrollable-container": "!min-h-[32px]", + "sp-input": "!text-gray-800 dark:!text-gray-100", + "sp-cm": clsx( + "p-0 bg-transparent", + "[&>.cm-editor]:!bg-refine-react-light-code", + "[&>.cm-editor]:dark:!bg-refine-react-dark-code", + "[&_.cm-activeLine]:!bg-gray-100 [&_.cm-activeLine]:dark:!bg-gray-800", + ), + "sp-icon-standalone": + "!bg-gray-300 dark:!bg-gray-700 !text-gray-400 dark:!text-gray-500", + "sp-tab-button": clsx( + "!h-8", + "!px-2 !pb-2 !pt-1.5", + "!text-gray-800 dark:!text-gray-100", + "!border !border-solid !border-b-0 !border-x-gray-300 dark:!border-x-gray-700", + "-ml-px first:ml-0", + "!border-t-2 !border-t-transparent [&[data-active='true']]:!border-t-refine-react-light-link dark:[&[data-active='true']]:!border-t-refine-react-dark-link", + ), + }, + }, + template = "react-ts", + customSetup, + files, + ...props +}: Props) => { + const [mounted, setMounted] = React.useState(false); + React.useEffect(() => { + setMounted(true); + }, []); + + const { colorMode } = useColorMode(); + options ??= {}; + options.resizablePanels ??= true; + options.editorWidthPercentage ??= initialPercentage ?? 50; + + const providerOptions: SandpackInternalOptions< + SandpackFiles, + SandpackPredefinedTemplate + > = { + /** + * TS-why: Type 'string | number | symbol' is not assignable to type 'string' + */ + activeFile: options.activeFile as unknown as string, + visibleFiles: options.visibleFiles as unknown as string[], + recompileMode: options.recompileMode, + recompileDelay: options.recompileDelay, + autorun: options.autorun, + autoReload: options.autoReload, + bundlerURL: options.bundlerURL, + startRoute: options.startRoute, + skipEval: options.skipEval, + fileResolver: options.fileResolver, + initMode: options.initMode, + initModeObserverOptions: options.initModeObserverOptions, + externalResources: options.externalResources, + logLevel: options.logLevel, + classes: options.classes, + }; + + return ( + } + options={providerOptions} + template={template} + theme={ + colorMode === "light" + ? { + ...aquaBlue, + colors: { + ...aquaBlue.colors, + accent: "#1D1E30", + surface1: "transparent", + surface2: "transparent", + surface3: "transparent", + }, + } + : { + ...nightOwl, + colors: { + ...nightOwl.colors, + surface1: "transparent", + surface2: "transparent", + surface3: "transparent", + }, + } + } + className="!w-full" + {...props} + > + {children} + + ); +}; + +const SandpackRightSide = ({ + codeEditorOptions, + startRoute, + parentResizing, + sandpackProps, +}: { + parentResizing: boolean; + startRoute: string; + codeEditorOptions: CodeEditorProps; + sandpackProps: SandpackProps; +}) => { + const [viewPercentage, setViewPercentage] = React.useState(50); + const [resizing, setResizing] = React.useState(false); + const containerRef = React.useRef(null); + + const { previewOnly, hidePreview, showFiles = true } = sandpackProps ?? {}; + + React.useEffect(() => { + const handleMouseUp = () => { + setResizing(false); + }; + + if (resizing !== null) { + window.addEventListener("mouseup", handleMouseUp); + + return () => { + window.removeEventListener("mouseup", handleMouseUp); + }; + } + + return; + }, [resizing]); + + React.useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (resizing) { + const containerRect = + containerRef.current?.getBoundingClientRect(); + + if (!containerRect) return; + + const newViewPercentage = Math.min( + maxPercentage, + Math.max( + 100 - maxPercentage, + ((e.clientY - containerRect.top) / + containerRect.height) * + 100, + ), + ); + + setViewPercentage(newViewPercentage); + } + }; + + if (resizing !== null) { + window.addEventListener("mousemove", handleMouseMove); + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + }; + } + + return; + }, [resizing]); + + React.useEffect(() => { + const currentCursor = document.body.style.cursor; + + if (resizing) { + document.body.style.cursor = "row-resize"; + } else { + document.body.style.cursor = "auto"; + } + + return () => { + document.body.style.cursor = currentCursor; + }; + }, [resizing]); + + return ( +
+
+
+ {previewOnly ? null : ( +
+ {showFiles ? ( + + ) : null} + +
+ )} + {hidePreview || previewOnly ? null : ( + + )} + {hidePreview ? null : ( +
+ +
+ +
+
+
+ )} +
+
+
+ ); +}; + +const ResizeHandleIcon = (props: React.SVGProps) => ( + + + + + +); diff --git a/documentation/src/refine-theme/tutorial-utils.tsx b/documentation/src/refine-theme/tutorial-utils.tsx new file mode 100644 index 000000000000..16adf25bafd0 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-utils.tsx @@ -0,0 +1,141 @@ +import { usePluginData } from "@docusaurus/useGlobalData"; +/* @ts-expect-error Imports through webpack aliases does not work */ +import data from "@tutorial-navigation/tutorial-navigation-data.json"; + +/* @ts-expect-error `/internal` is not directly exported but required in this case */ +import { useDocsVersion } from "@docusaurus/theme-common/internal"; + +export const HEADER_HEIGHT = 65; + +export type DocElement = { + frontMatter: Record; + metadata: { + frontMatter: Record; + id: string; + permalink: string; + slug: string; + title: string; + }; +}; + +export type Tutorial = { + label: string; + defaultParameters: Record; + parameterOptions: Record< + string, + Array<{ + label: string; + value: string; + }> + >; + units: Array<{ + title: string; + id: string; + items: Array; + }>; +}; + +export const tutorialData: Tutorial = data as Tutorial; + +export type TutorialDocItem = { + id: string; + path: string; + title: string; +}; + +export const populateParametrizedId = ( + id: string, + parameters: Record, +) => { + const idParts = id.split("/"); + const populatedIdParts = idParts.map((part) => { + if (part.startsWith(":")) { + const paramName = part.slice(1); + return parameters[paramName]; + } + return part; + }); + return populatedIdParts.join("/"); +}; + +export const findUnitByItemId = ( + itemId: string, + parameters: Record, +) => { + for (const unit of tutorialData.units) { + for (const item of unit.items) { + if (itemId === populateParametrizedId(item, parameters)) { + return unit; + } + } + } +}; + +export const getNext = (itemId: string, parameters: Record) => { + let found = false; + for (const unit of tutorialData.units) { + for (const item of unit.items) { + if (found) { + return item; + } + if (populateParametrizedId(item, parameters) === itemId) { + found = true; + } + } + } +}; + +export const getPrevious = ( + itemId: string, + parameters: Record, +) => { + let previous = null; + for (const unit of tutorialData.units) { + for (const item of unit.items) { + if (populateParametrizedId(item, parameters) === itemId) { + return previous; + } + previous = item; + } + } +}; + +export const useTutorialDocs = () => { + const { docs } = useDocsVersion(); + const { versions } = usePluginData( + "docusaurus-plugin-content-docs", + "tutorial", + ) as any; + + const tutorialDocs = versions[0].docs; + + for (const doc of tutorialDocs) { + doc.title = docs[doc.id].title; + } + + return Object.fromEntries( + tutorialDocs.map((doc) => [doc.id, doc]), + ) as Record; +}; + +export const getPathFromId = ( + id: string, + items: Record, +) => { + const item = items[id]; + if (!item) { + throw new Error(`Cannot find item with id ${id}`); + } + return item.path; +}; + +export const getTitleFromId = ( + id: string, + docs: Record, +) => { + const doc = docs[id]; + if (!doc) { + throw new Error(`Cannot find doc with id ${id}`); + } + return doc.title; +}; diff --git a/documentation/src/theme/MDXComponents/index.js b/documentation/src/theme/MDXComponents/index.js index 46342b04911f..b1875834733c 100644 --- a/documentation/src/theme/MDXComponents/index.js +++ b/documentation/src/theme/MDXComponents/index.js @@ -16,7 +16,6 @@ import { GuideBadge } from "@site/src/components/guide-badge"; import PropTag from "@site/src/components/prop-tag"; import PropsTable from "@site/src/components/props-table"; import { RouterBadge } from "@site/src/components/router-badge"; -import { Sandpack } from "@site/src/components/sandpack"; import UIConditional from "@site/src/components/ui-conditional"; import CommonDetails from "@site/src/refine-theme/common-details"; import CommonSummary from "@site/src/refine-theme/common-summary"; @@ -27,6 +26,7 @@ import { Image } from "@site/src/components/image"; import { Table, FullTable } from "@site/src/refine-theme/common-table"; import { CreateRefineAppCommand } from "@site/src/partials/npm-scripts/create-refine-app-command.tsx"; import { InstallPackagesCommand } from "@site/src/partials/npm-scripts/install-packages-commands"; +import { TutorialConditional } from "@site/src/refine-theme/tutorial-conditional"; export default { ...MDXComponents, @@ -49,7 +49,6 @@ export default { ExampleLocalPrompt, ExampleSourcePrompt, BannerRandom, - Sandpack, GuideBadge, RouterBadge, GlobalConfigBadge, @@ -58,4 +57,5 @@ export default { CreateRefineAppCommand: CreateRefineAppCommand, InstallPackagesCommand: InstallPackagesCommand, FullTable: FullTable, + TutorialConditional, }; diff --git a/documentation/src/theme/SearchBar/index.tsx b/documentation/src/theme/SearchBar/index.tsx index 212b09c1593c..a4024fe5c8b3 100644 --- a/documentation/src/theme/SearchBar/index.tsx +++ b/documentation/src/theme/SearchBar/index.tsx @@ -202,7 +202,7 @@ function DocSearch({ ); } -export default function SearchBar({ CustomButton }) { +export default function SearchBar({ CustomButton }: { CustomButton?: any }) { const { siteConfig } = useDocusaurusContext(); return ( + {children} + + ); +} diff --git a/documentation/src/theme/TutorialPage/index.js b/documentation/src/theme/TutorialPage/index.js new file mode 100644 index 000000000000..22e2b8ac8f4c --- /dev/null +++ b/documentation/src/theme/TutorialPage/index.js @@ -0,0 +1,65 @@ +import React from "react"; +import clsx from "clsx"; +import { + HtmlClassNameProvider, + ThemeClassNames, + PageMetadata, +} from "@docusaurus/theme-common"; +import { + docVersionSearchTag, + DocsVersionProvider, + useDocRouteMetadata, +} from "@docusaurus/theme-common/internal"; +import TutorialPageLayout from "@theme/TutorialPage/Layout"; +import NotFound from "@theme/NotFound"; +import SearchMetadata from "@theme/SearchMetadata"; + +function DocPageMetadata(props) { + const { versionMetadata } = props; + return ( + <> + + + {versionMetadata.noIndex && ( + + )} + + + ); +} + +export default function TutorialPage(props) { + const { versionMetadata } = props; + + const currentDocRouteMetadata = useDocRouteMetadata(props); + + if (!currentDocRouteMetadata) { + return ; + } + + const { docElement } = currentDocRouteMetadata; + + return ( + <> + + + {/* Pass parameter contexts here */} + + {docElement} + + + + ); +} diff --git a/documentation/tailwind.config.js b/documentation/tailwind.config.js index cc027f18ebf6..e2131b6edbff 100644 --- a/documentation/tailwind.config.js +++ b/documentation/tailwind.config.js @@ -22,6 +22,7 @@ module.exports = { yellow: "#FFBF00", green: "#1FAD66", "green-alt": "#26D97F", + "tutorial-green": "#24A866", cyan: "#0F8A8A", "cyan-alt": "#47EBEB", blue: "#0080FF", @@ -55,6 +56,7 @@ module.exports = { "react-light-orange-bg": "#FEF5E7", "react-light-purple-bg": "#F3F4FD", "react-light-green-bg": "#F4FBF9", + "tutorial-dark-bg": "#1D2026", "bg-alt": "#262640", "link-dark": "#6EB3F7", "link-light": "#0080FF", @@ -851,6 +853,9 @@ module.exports = { "landing-md": "960px", "landing-lg": "1296px", "landing-xl": "1440px", + "tutorial-sm": "720px", + "tutorial-md": "960px", + "tutorial-lg": "1440px", "landing-footer": "1264px", "blog-sm": "688px", "blog-md": "1000px", diff --git a/documentation/tutorial/authentication/auth-pages/index.md b/documentation/tutorial/authentication/auth-pages/index.md new file mode 100644 index 000000000000..f9235fd06ece --- /dev/null +++ b/documentation/tutorial/authentication/auth-pages/index.md @@ -0,0 +1,11 @@ +--- +title: Auth Pages +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/authentication/auth-pages/sandpack.tsx b/documentation/tutorial/authentication/auth-pages/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/authentication/auth-pages/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/authentication/auth-provider/index.md b/documentation/tutorial/authentication/auth-provider/index.md new file mode 100644 index 000000000000..4b54653de1d3 --- /dev/null +++ b/documentation/tutorial/authentication/auth-provider/index.md @@ -0,0 +1,11 @@ +--- +title: Auth Provider +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/authentication/auth-provider/sandpack.tsx b/documentation/tutorial/authentication/auth-provider/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/authentication/auth-provider/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/authentication/intro/index.md b/documentation/tutorial/authentication/intro/index.md new file mode 100644 index 000000000000..9fcdfdc35c77 --- /dev/null +++ b/documentation/tutorial/authentication/intro/index.md @@ -0,0 +1,11 @@ +--- +title: Introduction +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/authentication/intro/sandpack.tsx b/documentation/tutorial/authentication/intro/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/authentication/intro/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/authentication/protecting-content/index.md b/documentation/tutorial/authentication/protecting-content/index.md new file mode 100644 index 000000000000..9eec677b573e --- /dev/null +++ b/documentation/tutorial/authentication/protecting-content/index.md @@ -0,0 +1,11 @@ +--- +title: Protecting Your Content +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/authentication/protecting-content/sandpack.tsx b/documentation/tutorial/authentication/protecting-content/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/authentication/protecting-content/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/essentials/data-fetching/index.md b/documentation/tutorial/essentials/data-fetching/index.md new file mode 100644 index 000000000000..a92cc0c4012d --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/index.md @@ -0,0 +1,11 @@ +--- +title: Data Fetching +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/essentials/data-fetching/sandpack.tsx b/documentation/tutorial/essentials/data-fetching/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/essentials/forms/index.md b/documentation/tutorial/essentials/forms/index.md new file mode 100644 index 000000000000..e77fadbeab95 --- /dev/null +++ b/documentation/tutorial/essentials/forms/index.md @@ -0,0 +1,11 @@ +--- +title: Forms +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/essentials/forms/sandpack.tsx b/documentation/tutorial/essentials/forms/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/essentials/forms/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/essentials/intro/index.md b/documentation/tutorial/essentials/intro/index.md new file mode 100644 index 000000000000..6149e947a6a6 --- /dev/null +++ b/documentation/tutorial/essentials/intro/index.md @@ -0,0 +1,26 @@ +--- +title: Introduction +--- + +import { Sandpack, ChangeAppTsxSpan, AddFileSpan, UpdateFileSpan } from "./sandpack.tsx"; +import { TutorialParameterDropdown } from "@site/src/refine-theme/tutorial-parameter-dropdown"; + + + +This tutorial will walk you through from essentials of Refine to advanced topics. You'll learn how to build a full-featured CRUD applications with Refine. + +Refine is router agnostic and you're free to choose the routing library you're most familiar with. Refine officially supports [React Router DOM](#), [Next.js](#) and [Remix](#). In the further steps of the tutorial, you'll be presented with the related content based on your routing selection. You can always change your selection and see the content for the other libraries throughout the tutorial. + +Pick the Routing library you want to continue with: + + + +In the further units of this tutorial, you'll be selecting a UI framework integration of Refine and learn more about how UI integrations are made and where they can be useful. While Refine provides official support for Ant Design, Material UI, Mantine and Chakra UI, the tutorial is prepared for the top two most used UI frameworks; [Ant Design](#) and [Material UI](#). + +Pick the UI framework you want to continue with: + + + +You can find the corresponding material that is taught in this tutorial for the other UI frameworks in the [documentation](#). + + diff --git a/documentation/tutorial/essentials/intro/sandpack.tsx b/documentation/tutorial/essentials/intro/sandpack.tsx new file mode 100644 index 000000000000..410dd5482f86 --- /dev/null +++ b/documentation/tutorial/essentials/intro/sandpack.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +const dependencies = { + "@refinedev/core": "latest", + "@refinedev/simple-rest": "latest", +}; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +const AppTsxCode = /* jsx */ ` +import React from "react"; +import { Refine, WelcomePage } from "@refinedev/core"; +import dataProvider from "@refinedev/simple-rest"; + +export default function App() { + return ( + + + + ) +} +`.trim(); diff --git a/documentation/tutorial/essentials/setup/index.md b/documentation/tutorial/essentials/setup/index.md new file mode 100644 index 000000000000..46d0a7d4d551 --- /dev/null +++ b/documentation/tutorial/essentials/setup/index.md @@ -0,0 +1,11 @@ +--- +title: Your First Refine App +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/essentials/setup/sandpack.tsx b/documentation/tutorial/essentials/setup/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/essentials/setup/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/essentials/tables/index.md b/documentation/tutorial/essentials/tables/index.md new file mode 100644 index 000000000000..c60844f4e40b --- /dev/null +++ b/documentation/tutorial/essentials/tables/index.md @@ -0,0 +1,11 @@ +--- +title: Tables +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/essentials/tables/sandpack.tsx b/documentation/tutorial/essentials/tables/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/essentials/tables/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/cli/next-js/index.md b/documentation/tutorial/next-steps/cli/next-js/index.md new file mode 100644 index 000000000000..df6a2e108ad9 --- /dev/null +++ b/documentation/tutorial/next-steps/cli/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Using CLI +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/cli/next-js/sandpack.tsx b/documentation/tutorial/next-steps/cli/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/cli/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/cli/react-router/index.md b/documentation/tutorial/next-steps/cli/react-router/index.md new file mode 100644 index 000000000000..df6a2e108ad9 --- /dev/null +++ b/documentation/tutorial/next-steps/cli/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Using CLI +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/cli/react-router/sandpack.tsx b/documentation/tutorial/next-steps/cli/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/cli/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/cli/remix/index.md b/documentation/tutorial/next-steps/cli/remix/index.md new file mode 100644 index 000000000000..df6a2e108ad9 --- /dev/null +++ b/documentation/tutorial/next-steps/cli/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Using CLI +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/cli/remix/sandpack.tsx b/documentation/tutorial/next-steps/cli/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/cli/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/devtools/next-js/index.md b/documentation/tutorial/next-steps/devtools/next-js/index.md new file mode 100644 index 000000000000..329946ae734b --- /dev/null +++ b/documentation/tutorial/next-steps/devtools/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Using Devtools +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/devtools/next-js/sandpack.tsx b/documentation/tutorial/next-steps/devtools/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/devtools/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/devtools/react-router/index.md b/documentation/tutorial/next-steps/devtools/react-router/index.md new file mode 100644 index 000000000000..329946ae734b --- /dev/null +++ b/documentation/tutorial/next-steps/devtools/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Using Devtools +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/devtools/react-router/sandpack.tsx b/documentation/tutorial/next-steps/devtools/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/devtools/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/devtools/remix/index.md b/documentation/tutorial/next-steps/devtools/remix/index.md new file mode 100644 index 000000000000..329946ae734b --- /dev/null +++ b/documentation/tutorial/next-steps/devtools/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Using Devtools +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/devtools/remix/sandpack.tsx b/documentation/tutorial/next-steps/devtools/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/devtools/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/inferencer/ant-design/index.md b/documentation/tutorial/next-steps/inferencer/ant-design/index.md new file mode 100644 index 000000000000..f202ba977ae4 --- /dev/null +++ b/documentation/tutorial/next-steps/inferencer/ant-design/index.md @@ -0,0 +1,11 @@ +--- +title: Using Inferencer +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/inferencer/ant-design/sandpack.tsx b/documentation/tutorial/next-steps/inferencer/ant-design/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/inferencer/ant-design/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/inferencer/material-ui/index.md b/documentation/tutorial/next-steps/inferencer/material-ui/index.md new file mode 100644 index 000000000000..f202ba977ae4 --- /dev/null +++ b/documentation/tutorial/next-steps/inferencer/material-ui/index.md @@ -0,0 +1,11 @@ +--- +title: Using Inferencer +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/inferencer/material-ui/sandpack.tsx b/documentation/tutorial/next-steps/inferencer/material-ui/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/inferencer/material-ui/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/intro/index.md b/documentation/tutorial/next-steps/intro/index.md new file mode 100644 index 000000000000..9fcdfdc35c77 --- /dev/null +++ b/documentation/tutorial/next-steps/intro/index.md @@ -0,0 +1,11 @@ +--- +title: Introduction +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/intro/sandpack.tsx b/documentation/tutorial/next-steps/intro/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/intro/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/next-steps/summary/index.md b/documentation/tutorial/next-steps/summary/index.md new file mode 100644 index 000000000000..2156f2f3bdd0 --- /dev/null +++ b/documentation/tutorial/next-steps/summary/index.md @@ -0,0 +1,11 @@ +--- +title: Summary +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/next-steps/summary/sandpack.tsx b/documentation/tutorial/next-steps/summary/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/next-steps/summary/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/authentication/next-js/index.md b/documentation/tutorial/routing/authentication/next-js/index.md new file mode 100644 index 000000000000..b3f66a639084 --- /dev/null +++ b/documentation/tutorial/routing/authentication/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Authentication +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/authentication/next-js/sandpack.tsx b/documentation/tutorial/routing/authentication/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/authentication/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/authentication/react-router/index.md b/documentation/tutorial/routing/authentication/react-router/index.md new file mode 100644 index 000000000000..b3f66a639084 --- /dev/null +++ b/documentation/tutorial/routing/authentication/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Authentication +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/authentication/react-router/sandpack.tsx b/documentation/tutorial/routing/authentication/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/authentication/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/authentication/remix/index.md b/documentation/tutorial/routing/authentication/remix/index.md new file mode 100644 index 000000000000..b3f66a639084 --- /dev/null +++ b/documentation/tutorial/routing/authentication/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Authentication +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/authentication/remix/sandpack.tsx b/documentation/tutorial/routing/authentication/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/authentication/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/inferring-parameters/index.md b/documentation/tutorial/routing/inferring-parameters/index.md new file mode 100644 index 000000000000..2653e5a5e61d --- /dev/null +++ b/documentation/tutorial/routing/inferring-parameters/index.md @@ -0,0 +1,11 @@ +--- +title: Inferring Parameters +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/inferring-parameters/sandpack.tsx b/documentation/tutorial/routing/inferring-parameters/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/inferring-parameters/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/intro/index.md b/documentation/tutorial/routing/intro/index.md new file mode 100644 index 000000000000..9fcdfdc35c77 --- /dev/null +++ b/documentation/tutorial/routing/intro/index.md @@ -0,0 +1,11 @@ +--- +title: Introduction +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/intro/sandpack.tsx b/documentation/tutorial/routing/intro/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/intro/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/redirects/next-js/index.md b/documentation/tutorial/routing/redirects/next-js/index.md new file mode 100644 index 000000000000..fc51d93e4b42 --- /dev/null +++ b/documentation/tutorial/routing/redirects/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Redirects +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/redirects/next-js/sandpack.tsx b/documentation/tutorial/routing/redirects/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/redirects/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/redirects/react-router/index.md b/documentation/tutorial/routing/redirects/react-router/index.md new file mode 100644 index 000000000000..fc51d93e4b42 --- /dev/null +++ b/documentation/tutorial/routing/redirects/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Redirects +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/redirects/react-router/sandpack.tsx b/documentation/tutorial/routing/redirects/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/redirects/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/redirects/remix/index.md b/documentation/tutorial/routing/redirects/remix/index.md new file mode 100644 index 000000000000..fc51d93e4b42 --- /dev/null +++ b/documentation/tutorial/routing/redirects/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Redirects +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/redirects/remix/sandpack.tsx b/documentation/tutorial/routing/redirects/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/redirects/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/resource-definition/index.md b/documentation/tutorial/routing/resource-definition/index.md new file mode 100644 index 000000000000..595b56959df5 --- /dev/null +++ b/documentation/tutorial/routing/resource-definition/index.md @@ -0,0 +1,11 @@ +--- +title: Defining Resources +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/resource-definition/sandpack.tsx b/documentation/tutorial/routing/resource-definition/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/resource-definition/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/syncing-state/next-js/index.md b/documentation/tutorial/routing/syncing-state/next-js/index.md new file mode 100644 index 000000000000..c28424febefe --- /dev/null +++ b/documentation/tutorial/routing/syncing-state/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Syncing State with Location +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/syncing-state/next-js/sandpack.tsx b/documentation/tutorial/routing/syncing-state/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/syncing-state/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/syncing-state/react-router/index.md b/documentation/tutorial/routing/syncing-state/react-router/index.md new file mode 100644 index 000000000000..c28424febefe --- /dev/null +++ b/documentation/tutorial/routing/syncing-state/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Syncing State with Location +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/syncing-state/react-router/sandpack.tsx b/documentation/tutorial/routing/syncing-state/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/syncing-state/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/routing/syncing-state/remix/index.md b/documentation/tutorial/routing/syncing-state/remix/index.md new file mode 100644 index 000000000000..c28424febefe --- /dev/null +++ b/documentation/tutorial/routing/syncing-state/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Syncing State with Location +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/routing/syncing-state/remix/sandpack.tsx b/documentation/tutorial/routing/syncing-state/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/routing/syncing-state/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/index.md b/documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/index.md new file mode 100644 index 000000000000..257b33a30889 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: CRUD Components +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/sandpack.tsx b/documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/ant-design/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/index.md b/documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/index.md new file mode 100644 index 000000000000..257b33a30889 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: CRUD Components +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/sandpack.tsx b/documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/ant-design/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/crud-components/ant-design/remix/index.md b/documentation/tutorial/ui-libraries/crud-components/ant-design/remix/index.md new file mode 100644 index 000000000000..257b33a30889 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/ant-design/remix/index.md @@ -0,0 +1,11 @@ +--- +title: CRUD Components +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/crud-components/ant-design/remix/sandpack.tsx b/documentation/tutorial/ui-libraries/crud-components/ant-design/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/ant-design/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/index.md b/documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/index.md new file mode 100644 index 000000000000..257b33a30889 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: CRUD Components +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/sandpack.tsx b/documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/material-ui/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/index.md b/documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/index.md new file mode 100644 index 000000000000..257b33a30889 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: CRUD Components +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/sandpack.tsx b/documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/material-ui/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/crud-components/material-ui/remix/index.md b/documentation/tutorial/ui-libraries/crud-components/material-ui/remix/index.md new file mode 100644 index 000000000000..257b33a30889 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/material-ui/remix/index.md @@ -0,0 +1,11 @@ +--- +title: CRUD Components +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/crud-components/material-ui/remix/sandpack.tsx b/documentation/tutorial/ui-libraries/crud-components/material-ui/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/crud-components/material-ui/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/intro/index.md b/documentation/tutorial/ui-libraries/intro/index.md new file mode 100644 index 000000000000..9fcdfdc35c77 --- /dev/null +++ b/documentation/tutorial/ui-libraries/intro/index.md @@ -0,0 +1,11 @@ +--- +title: Introduction +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/intro/sandpack.tsx b/documentation/tutorial/ui-libraries/intro/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/intro/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/layout/ant-design/next-js/index.md b/documentation/tutorial/ui-libraries/layout/ant-design/next-js/index.md new file mode 100644 index 000000000000..e476a09866e3 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/ant-design/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Using Layouts +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/layout/ant-design/next-js/sandpack.tsx b/documentation/tutorial/ui-libraries/layout/ant-design/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/ant-design/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/layout/ant-design/react-router/index.md b/documentation/tutorial/ui-libraries/layout/ant-design/react-router/index.md new file mode 100644 index 000000000000..e476a09866e3 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/ant-design/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Using Layouts +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/layout/ant-design/react-router/sandpack.tsx b/documentation/tutorial/ui-libraries/layout/ant-design/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/ant-design/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/layout/ant-design/remix/index.md b/documentation/tutorial/ui-libraries/layout/ant-design/remix/index.md new file mode 100644 index 000000000000..e476a09866e3 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/ant-design/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Using Layouts +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/layout/ant-design/remix/sandpack.tsx b/documentation/tutorial/ui-libraries/layout/ant-design/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/ant-design/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/layout/material-ui/next-js/index.md b/documentation/tutorial/ui-libraries/layout/material-ui/next-js/index.md new file mode 100644 index 000000000000..e476a09866e3 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/material-ui/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Using Layouts +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/layout/material-ui/next-js/sandpack.tsx b/documentation/tutorial/ui-libraries/layout/material-ui/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/material-ui/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/layout/material-ui/react-router/index.md b/documentation/tutorial/ui-libraries/layout/material-ui/react-router/index.md new file mode 100644 index 000000000000..e476a09866e3 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/material-ui/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Using Layouts +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/layout/material-ui/react-router/sandpack.tsx b/documentation/tutorial/ui-libraries/layout/material-ui/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/material-ui/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/layout/material-ui/remix/index.md b/documentation/tutorial/ui-libraries/layout/material-ui/remix/index.md new file mode 100644 index 000000000000..e476a09866e3 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/material-ui/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Using Layouts +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/layout/material-ui/remix/sandpack.tsx b/documentation/tutorial/ui-libraries/layout/material-ui/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/layout/material-ui/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/index.md b/documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/index.md new file mode 100644 index 000000000000..76009716e3e1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Refactoring +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/sandpack.tsx b/documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/ant-design/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/index.md b/documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/index.md new file mode 100644 index 000000000000..76009716e3e1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Refactoring +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/sandpack.tsx b/documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/ant-design/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/refactoring/ant-design/remix/index.md b/documentation/tutorial/ui-libraries/refactoring/ant-design/remix/index.md new file mode 100644 index 000000000000..76009716e3e1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/ant-design/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Refactoring +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/refactoring/ant-design/remix/sandpack.tsx b/documentation/tutorial/ui-libraries/refactoring/ant-design/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/ant-design/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/index.md b/documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/index.md new file mode 100644 index 000000000000..76009716e3e1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/index.md @@ -0,0 +1,11 @@ +--- +title: Refactoring +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/sandpack.tsx b/documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/material-ui/next-js/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/index.md b/documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/index.md new file mode 100644 index 000000000000..76009716e3e1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/index.md @@ -0,0 +1,11 @@ +--- +title: Refactoring +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/sandpack.tsx b/documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/material-ui/react-router/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorial/ui-libraries/refactoring/material-ui/remix/index.md b/documentation/tutorial/ui-libraries/refactoring/material-ui/remix/index.md new file mode 100644 index 000000000000..76009716e3e1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/material-ui/remix/index.md @@ -0,0 +1,11 @@ +--- +title: Refactoring +--- + +import { Sandpack } from "./sandpack.tsx"; + + + +Your content here. + + diff --git a/documentation/tutorial/ui-libraries/refactoring/material-ui/remix/sandpack.tsx b/documentation/tutorial/ui-libraries/refactoring/material-ui/remix/sandpack.tsx new file mode 100644 index 000000000000..db3bde6111b1 --- /dev/null +++ b/documentation/tutorial/ui-libraries/refactoring/material-ui/remix/sandpack.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + Hello world! +} + `.trim(), + }, + }} + > + {children} + + ); +}; diff --git a/documentation/tutorials.js b/documentation/tutorials.js new file mode 100644 index 000000000000..54c0e55ecd02 --- /dev/null +++ b/documentation/tutorials.js @@ -0,0 +1,92 @@ +module.exports = { + tutorial: { + label: "Tutorial", + path_prefix_segment: "tutorial", + defaultParameters: { + routerSelection: "react-router", + uiSelection: "ant-design", + }, + parameterOptions: { + routerSelection: [ + { + label: "React Router", + value: "react-router", + }, + { + label: "Next.js", + value: "next-js", + }, + { + label: "Remix", + value: "remix", + }, + ], + uiSelection: [ + { + label: "Ant Design", + value: "ant-design", + }, + { + label: "Material UI", + value: "material-ui", + }, + ], + }, + units: [ + { + title: "Essentials", + id: "essentials", + items: [ + "essentials/intro/index", + "essentials/setup/index", + "essentials/data-fetching/index", + "essentials/forms/index", + "essentials/tables/index", + ], + }, + { + title: "Authentication", + id: "authentication", + items: [ + "authentication/intro/index", + "authentication/auth-provider/index", + "authentication/protecting-content/index", + "authentication/auth-pages/index", + ], + }, + { + title: "Routing", + id: "routing", + items: [ + "routing/intro/index", + "routing/resource-definition/index", + "routing/inferring-parameters/index", + "routing/redirects/:routerSelection/index", + "routing/syncing-state/:routerSelection/index", + "routing/authentication/:routerSelection/index", + ], + }, + { + title: "UI Libraries", + id: "ui-libraries", + items: [ + "ui-libraries/intro/index", + "ui-libraries/layout/:uiSelection/:routerSelection/index", + "ui-libraries/crud-components/:uiSelection/:routerSelection/index", + "ui-libraries/refactoring/:uiSelection/:routerSelection/index", + ], + }, + { + title: "Next Steps", + id: "next-steps", + items: [ + "next-steps/intro/index", + "next-steps/inferencer/:uiSelection/index", + "next-steps/devtools/:routerSelection/index", + "next-steps/cli/:routerSelection/index", + "next-steps/summary/index", + ], + }, + ], + }, +}; From 8a9e886cba1a18955923f33ec4a4d5349b4b54b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Wed, 17 Jan 2024 10:15:30 +0300 Subject: [PATCH 02/35] docs(tutorial): add content percentage prop --- documentation/src/refine-theme/tutorial-sandpack.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/documentation/src/refine-theme/tutorial-sandpack.tsx b/documentation/src/refine-theme/tutorial-sandpack.tsx index cf93992e5121..a770482e3bfa 100644 --- a/documentation/src/refine-theme/tutorial-sandpack.tsx +++ b/documentation/src/refine-theme/tutorial-sandpack.tsx @@ -41,16 +41,20 @@ type SandpackProps = React.ComponentProps & { parentResizing?: boolean; }; -type Props = React.PropsWithChildren; +type Props = React.PropsWithChildren< + SandpackProps & { contentOnly?: boolean; contentPercentage?: number } +>; const maxPercentage = 70; export const TutorialSandpack = ({ children, contentOnly, + contentPercentage = 45, ...sandpackProps }: Props) => { - const [viewPercentage, setViewPercentage] = React.useState(45); + const [viewPercentage, setViewPercentage] = + React.useState(contentPercentage); const [resizing, setResizing] = React.useState(false); const containerRef = React.useRef(null); @@ -175,7 +179,7 @@ export const TutorialSandpack = ({ "max-h-full", "h-full", "overflow-scroll", - "px-4 tutorial-sm:px-6", + "px-6 tutorial-sm:px-8", "pb-6", )} > @@ -543,7 +547,7 @@ const SandpackRightSide = ({ }} > {showFiles ? ( - + ) : null} Date: Wed, 17 Jan 2024 10:15:48 +0300 Subject: [PATCH 03/35] docs(tutorial): add custom admonitions --- documentation/docusaurus.config.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/documentation/docusaurus.config.js b/documentation/docusaurus.config.js index 13e1ad5f0129..91520b07af6f 100644 --- a/documentation/docusaurus.config.js +++ b/documentation/docusaurus.config.js @@ -154,6 +154,21 @@ const siteConfig = { docLayoutComponent: "@theme/TutorialPage", docItemComponent: "@theme/TutorialItem", include: ["**/index.md"], + admonitions: { + tag: ":::", + keywords: [ + "additional", + "note", + "tip", + "info-tip", + "info", + "caution", + "danger", + "sourcecode", + "create-example", + "simple", + ], + }, }, ], ], From d3e7d80263028900f133fe626c64d67eadda4862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Wed, 17 Jan 2024 10:16:07 +0300 Subject: [PATCH 04/35] docs(tutorial): add tutorial glob to tailwind contents --- documentation/tailwind.config.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/documentation/tailwind.config.js b/documentation/tailwind.config.js index e2131b6edbff..7b7a6122ab3b 100644 --- a/documentation/tailwind.config.js +++ b/documentation/tailwind.config.js @@ -4,7 +4,11 @@ const plugin = require("tailwindcss/plugin"); /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ["class", '[data-theme="dark"]'], - content: ["./src/**/*.{js,jsx,ts,tsx}", "./docs/**/*.{md,mdx,tsx}"], + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + "./docs/**/*.{md,mdx,tsx}", + "./tutorial/**/*.{md,mdx,tsx}", + ], jit: true, theme: { extend: { From 35c7efbef490f6961a8c2a097c35e603562389d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Wed, 17 Jan 2024 11:43:27 +0300 Subject: [PATCH 05/35] docs(tutorial): add missing links in tutorial footer --- .../src/refine-theme/tutorial-footer.tsx | 88 +++++++++++++------ 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/documentation/src/refine-theme/tutorial-footer.tsx b/documentation/src/refine-theme/tutorial-footer.tsx index b05ceebb4cb8..c576f2b328a2 100644 --- a/documentation/src/refine-theme/tutorial-footer.tsx +++ b/documentation/src/refine-theme/tutorial-footer.tsx @@ -1,6 +1,7 @@ import clsx from "clsx"; import React from "react"; import { socialLinks } from "./footer-data"; +import Link from "@docusaurus/Link"; export const TutorialFooter = () => { return ( @@ -33,44 +34,77 @@ export const TutorialFooter = () => {
-
- Join us on -
+ Refine Home + + + Documentation +
- {socialLinks.map(({ href, icon: Icon }, i) => { - return ( - - + Join us on +
+
From 4930865b4aa598fd6ac98b039a7a616d8574b50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Wed, 17 Jan 2024 12:07:51 +0300 Subject: [PATCH 06/35] docs(tutorial): fix layout issues --- documentation/src/refine-theme/tutorial-header.tsx | 2 +- documentation/src/refine-theme/tutorial-sandpack.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/refine-theme/tutorial-header.tsx b/documentation/src/refine-theme/tutorial-header.tsx index 940d35ee7f16..d37f048d66ab 100644 --- a/documentation/src/refine-theme/tutorial-header.tsx +++ b/documentation/src/refine-theme/tutorial-header.tsx @@ -342,7 +342,7 @@ export const TutorialHeader = React.memo(function TutorialHeaderComponent() { "z-10", "sticky", "top-0", - "py-4 pb-[15px] tutorial-sm:py-3 px-4 tutorial-md:px-6", + "py-4 pb-[15px] tutorial-sm:pt-[15px] tutorial-sm:pb-[16px] px-4 tutorial-md:px-6 tutorial-md:pt-[11px] tutorial-md:pb-[10px]", "bg-gray-0 dark:bg-gray-800", "border-b border-gray-300 dark:border-gray-700", )} diff --git a/documentation/src/refine-theme/tutorial-sandpack.tsx b/documentation/src/refine-theme/tutorial-sandpack.tsx index a770482e3bfa..7f0a8a083e46 100644 --- a/documentation/src/refine-theme/tutorial-sandpack.tsx +++ b/documentation/src/refine-theme/tutorial-sandpack.tsx @@ -145,7 +145,7 @@ export const TutorialSandpack = ({
Date: Wed, 17 Jan 2024 12:08:02 +0300 Subject: [PATCH 07/35] docs(tutorial): update navigation dropdown height --- .../src/refine-theme/tutorial-navigation.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/documentation/src/refine-theme/tutorial-navigation.tsx b/documentation/src/refine-theme/tutorial-navigation.tsx index 513cd6be8028..4d54f11f8acd 100644 --- a/documentation/src/refine-theme/tutorial-navigation.tsx +++ b/documentation/src/refine-theme/tutorial-navigation.tsx @@ -203,27 +203,22 @@ const NavigationDropdown = () => {
-
+
Date: Wed, 17 Jan 2024 16:47:35 +0300 Subject: [PATCH 08/35] docs(tutorial): update header alignments --- documentation/src/refine-theme/tutorial-header.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/src/refine-theme/tutorial-header.tsx b/documentation/src/refine-theme/tutorial-header.tsx index d37f048d66ab..e50ca95c7bd1 100644 --- a/documentation/src/refine-theme/tutorial-header.tsx +++ b/documentation/src/refine-theme/tutorial-header.tsx @@ -235,6 +235,7 @@ const Header = () => { "items-center", "gap-6", "tutorial-md:gap-12", + "max-w-[656px]", )} > @@ -245,6 +246,7 @@ const Header = () => {
Date: Wed, 17 Jan 2024 16:51:32 +0300 Subject: [PATCH 09/35] docs(tutorial): update tutorial header logo --- .../src/refine-theme/tutorial-refine-logo.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/documentation/src/refine-theme/tutorial-refine-logo.tsx b/documentation/src/refine-theme/tutorial-refine-logo.tsx index 6258782697f2..982519184fce 100644 --- a/documentation/src/refine-theme/tutorial-refine-logo.tsx +++ b/documentation/src/refine-theme/tutorial-refine-logo.tsx @@ -11,7 +11,6 @@ export const TutorialRefineLogo = ({ className }: Props) => { return (
{ )} > - + { "font-semibold", )} > - Refine Tutorial + Refine - {/* { "bg-gray-300 dark:bg-gray-600", )} /> - + - Documentation + Tutorial - */} +
); }; From 21e2e3d9e091f50326f7ee7e787459bf3193d11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Thu, 18 Jan 2024 17:07:28 +0300 Subject: [PATCH 10/35] docs(tutorial): fix navigation dropdown --- documentation/src/refine-theme/tutorial-navigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/refine-theme/tutorial-navigation.tsx b/documentation/src/refine-theme/tutorial-navigation.tsx index 4d54f11f8acd..0292e878d38e 100644 --- a/documentation/src/refine-theme/tutorial-navigation.tsx +++ b/documentation/src/refine-theme/tutorial-navigation.tsx @@ -218,7 +218,7 @@ const NavigationDropdown = () => { "rounded-[20px]", )} > -
+
Date: Tue, 23 Jan 2024 16:28:23 +0300 Subject: [PATCH 11/35] docs(new-tutorial): add essentials/unit-0 (#5524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(tutorial): split data fetching into chapters * docs(tutorial): add essentials/setup * docs(tutorial): add data feching intro chapter * docs(tutorial): add fetching data chapter * docs(tutorial): add updating data chapter * docs(tutorial): add listing data chapter * docs(tutorial-setup): fix broken link * docs(listing-data): update filter condition * docs(intro-data-fetching): add multiple data provider tip * docs(tutorial): fix buttons in light mode * docs(tutorial-listing-data): use multiple sorters and filters * docs(listing-data): use 50 percent content * docs(tutorial): update listing data highlights * docs(tutorial-setup): separate setups with tabs * docs(tutorial): add forms tutorial * docs(tutorial): update get list methods * docs(tutorial-sandpack): fix line numbers * docs(tutorial): add tables chapter * docs(tutorial): update buttons * docs(tutorial): add solve * docs(tutorial): add final states * docs(tutorial): open links in tutorial in new tab * docs(tutorial): use a better narrative Co-authored-by: Necati Özmen * docs(tutorial): change `chapter` to `step` in tutorial --------- Co-authored-by: Necati Özmen --- documentation/src/refine-theme/css/custom.css | 4 + .../tutorial-create-file-button.tsx | 98 ++ .../tutorial-file-explorer/directory.tsx | 59 + .../tutorial-file-explorer/file.tsx | 129 +++ .../tutorial-file-explorer/index.tsx | 144 +++ .../tutorial-file-explorer/module-list.tsx | 65 ++ .../tutorial-file-explorer/utils.ts | 49 + .../src/refine-theme/tutorial-sandpack.tsx | 165 ++- .../tutorial-update-file-button.tsx | 100 ++ documentation/src/theme/MDXComponents/A.js | 16 +- .../data-fetching/fetching-data/index.md | 106 ++ .../data-fetching/fetching-data/sandpack.tsx | 186 +++ .../essentials/data-fetching/index.md | 11 - .../essentials/data-fetching/intro/index.md | 46 + .../data-fetching/intro/sandpack.tsx | 113 ++ .../data-fetching/listing-data/index.md | 375 ++++++ .../data-fetching/listing-data/sandpack.tsx | 660 +++++++++++ .../essentials/data-fetching/sandpack.tsx | 20 - .../data-fetching/updating-data/index.md | 146 +++ .../data-fetching/updating-data/sandpack.tsx | 258 +++++ .../tutorial/essentials/forms/index.md | 407 ++++++- .../tutorial/essentials/forms/sandpack.tsx | 704 +++++++++++- .../tutorial/essentials/intro/index.md | 2 +- .../tutorial/essentials/setup/index.md | 151 ++- .../tutorial/essentials/setup/sandpack.tsx | 30 +- .../tutorial/essentials/tables/index.md | 513 ++++++++- .../tutorial/essentials/tables/sandpack.tsx | 1005 ++++++++++++++++- documentation/tutorials.js | 5 +- 28 files changed, 5504 insertions(+), 63 deletions(-) create mode 100644 documentation/src/refine-theme/tutorial-create-file-button.tsx create mode 100644 documentation/src/refine-theme/tutorial-file-explorer/directory.tsx create mode 100644 documentation/src/refine-theme/tutorial-file-explorer/file.tsx create mode 100644 documentation/src/refine-theme/tutorial-file-explorer/index.tsx create mode 100644 documentation/src/refine-theme/tutorial-file-explorer/module-list.tsx create mode 100644 documentation/src/refine-theme/tutorial-file-explorer/utils.ts create mode 100644 documentation/src/refine-theme/tutorial-update-file-button.tsx create mode 100644 documentation/tutorial/essentials/data-fetching/fetching-data/index.md create mode 100644 documentation/tutorial/essentials/data-fetching/fetching-data/sandpack.tsx delete mode 100644 documentation/tutorial/essentials/data-fetching/index.md create mode 100644 documentation/tutorial/essentials/data-fetching/intro/index.md create mode 100644 documentation/tutorial/essentials/data-fetching/intro/sandpack.tsx create mode 100644 documentation/tutorial/essentials/data-fetching/listing-data/index.md create mode 100644 documentation/tutorial/essentials/data-fetching/listing-data/sandpack.tsx delete mode 100644 documentation/tutorial/essentials/data-fetching/sandpack.tsx create mode 100644 documentation/tutorial/essentials/data-fetching/updating-data/index.md create mode 100644 documentation/tutorial/essentials/data-fetching/updating-data/sandpack.tsx diff --git a/documentation/src/refine-theme/css/custom.css b/documentation/src/refine-theme/css/custom.css index 7759ca517b57..1399136e2542 100644 --- a/documentation/src/refine-theme/css/custom.css +++ b/documentation/src/refine-theme/css/custom.css @@ -551,4 +551,8 @@ h4 del code { } .sp-console-list .sp-console-item { @apply !px-2 !py-px text-gray-600 dark:text-gray-200; +} + +.cm-gutter.cm-lineNumbers { + @apply !text-xs !leading-5; } \ No newline at end of file diff --git a/documentation/src/refine-theme/tutorial-create-file-button.tsx b/documentation/src/refine-theme/tutorial-create-file-button.tsx new file mode 100644 index 000000000000..f93294d24100 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-create-file-button.tsx @@ -0,0 +1,98 @@ +import clsx from "clsx"; +import React from "react"; + +type Props = { + name: string; + onClick: () => void; +}; + +export const TutorialCreateFileButton = ({ name, onClick }: Props) => { + return ( + + ); +}; + +const CreateIcon = (props: React.SVGProps) => ( + + + +); diff --git a/documentation/src/refine-theme/tutorial-file-explorer/directory.tsx b/documentation/src/refine-theme/tutorial-file-explorer/directory.tsx new file mode 100644 index 000000000000..fa247892dcab --- /dev/null +++ b/documentation/src/refine-theme/tutorial-file-explorer/directory.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; + +import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client"; +import type { SandpackOptions } from "@codesandbox/sandpack-react"; + +import { File } from "./file"; +import { ModuleList } from "./module-list"; + +import type { SandpackFileExplorerProp } from "."; + +export interface Props extends SandpackFileExplorerProp { + prefixedPath: string; + files: SandpackBundlerFiles; + selectFile: (path: string) => void; + activeFile: NonNullable; + depth: number; + visibleFiles: NonNullable; +} + +export const Directory: React.FC = ({ + prefixedPath, + files, + selectFile, + activeFile, + depth, + autoHiddenFiles, + visibleFiles, + initialCollapsedFolder, +}) => { + const [open, setOpen] = React.useState( + !initialCollapsedFolder?.includes(prefixedPath), + ); + + const toggle = (): void => setOpen((prev) => !prev); + + return ( +
+ + + {open && ( + + )} +
+ ); +}; diff --git a/documentation/src/refine-theme/tutorial-file-explorer/file.tsx b/documentation/src/refine-theme/tutorial-file-explorer/file.tsx new file mode 100644 index 000000000000..d766b1f9a959 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-file-explorer/file.tsx @@ -0,0 +1,129 @@ +import * as React from "react"; + +import { useClassNames } from "@codesandbox/sandpack-react"; + +import clsx from "clsx"; + +export interface Props { + path: string; + selectFile?: (path: string) => void; + active?: boolean; + onClick?: (e: React.MouseEvent) => void; + depth: number; + isDirOpen?: boolean; +} + +export const File: React.FC = ({ + selectFile, + path, + active, + onClick, + depth, + isDirOpen, +}) => { + const classNames = useClassNames(); + const onClickButton = ( + event: React.MouseEvent, + ): void => { + if (selectFile) { + selectFile(path); + } + + onClick?.(event); + }; + + const fileName = path.split("/").filter(Boolean).pop(); + + const getIcon = (): JSX.Element => { + if (selectFile) return ; + + return isDirOpen ? : ; + }; + + return ( + + ); +}; + +const SVG: React.FC> = (props) => ( + +); + +const DirectoryIconOpen = (): React.ReactElement => ( + + Directory + + + +); + +const DirectoryIconClosed = (): React.ReactElement => ( + + Directory + + +); + +const FileIcon = (): React.ReactElement => ( + + File + + +); diff --git a/documentation/src/refine-theme/tutorial-file-explorer/index.tsx b/documentation/src/refine-theme/tutorial-file-explorer/index.tsx new file mode 100644 index 000000000000..2dcde8977206 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-file-explorer/index.tsx @@ -0,0 +1,144 @@ +import * as React from "react"; +import clsx from "clsx"; +import { + useSandpack, + useClassNames, + stackClassName, +} from "@codesandbox/sandpack-react"; + +import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client"; + +import { ModuleList } from "./module-list"; + +export interface SandpackFileExplorerProp { + /** + * enable auto hidden file in file explorer + * + * @description set with hidden property in files property + * @default false + */ + autoHiddenFiles?: boolean; + + initialCollapsedFolder?: string[]; + + hasSolve?: boolean; +} + +export const TutorialFileExplorer = ({ + className, + autoHiddenFiles = false, + initialCollapsedFolder = [], + hasSolve, + ...props +}: SandpackFileExplorerProp & + React.HTMLAttributes): JSX.Element | null => { + const { + sandpack: { + status, + updateFile, + deleteFile, + activeFile, + files, + openFile, + visibleFilesFromProps, + }, + listen, + } = useSandpack(); + const classNames = useClassNames(); + + const [visibleFiles, setVisibleFiles] = React.useState( + visibleFilesFromProps, + ); + + const [defaultHiddenFiles] = React.useState( + Object.keys(files).filter( + (path) => !visibleFilesFromProps.includes(path), + ), + ); + + React.useEffect(() => { + const visible = Object.keys(files).filter((path) => { + const file = files[path]; + const isHidden = file.hidden || defaultHiddenFiles.includes(path); + + return !isHidden; + }); + + setVisibleFiles(visible); + }, [files, defaultHiddenFiles]); + + React.useEffect( + function watchFSFilesChanges() { + if (status !== "running") return; + + const unsubscribe = listen((message) => { + if (message.type === "fs/change") { + updateFile(message.path, message.content, false); + } + + if (message.type === "fs/remove") { + deleteFile(message.path, false); + } + }); + + return unsubscribe; + }, + [status], + ); + + const orderedFiles = Object.keys(files) + .sort() + .reduce((obj, key) => { + obj[key] = files[key]; + return obj; + }, {}); + + return ( +
+
+ +
+
+ ); +}; + +export const ExplorerToggleIcon = (props: React.SVGProps) => ( + + + +); diff --git a/documentation/src/refine-theme/tutorial-file-explorer/module-list.tsx b/documentation/src/refine-theme/tutorial-file-explorer/module-list.tsx new file mode 100644 index 000000000000..71455f296193 --- /dev/null +++ b/documentation/src/refine-theme/tutorial-file-explorer/module-list.tsx @@ -0,0 +1,65 @@ +import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client"; +import * as React from "react"; + +import type { SandpackOptions } from "@codesandbox/sandpack-react"; + +import { Directory } from "./directory"; +import { File } from "./file"; +import { fromPropsToModules } from "./utils"; + +import type { SandpackFileExplorerProp } from "."; + +export interface ModuleListProps extends SandpackFileExplorerProp { + prefixedPath: string; + files: SandpackBundlerFiles; + selectFile: (path: string) => void; + activeFile: NonNullable; + depth?: number; + visibleFiles: NonNullable; +} + +export const ModuleList: React.FC = ({ + depth = 0, + activeFile, + selectFile, + prefixedPath, + files, + autoHiddenFiles, + visibleFiles, + initialCollapsedFolder, +}) => { + const { directories, modules } = fromPropsToModules({ + visibleFiles, + autoHiddenFiles, + prefixedPath, + files, + }); + + return ( +
+ {directories.map((dir) => ( + + ))} + + {modules.map((file) => ( + + ))} +
+ ); +}; diff --git a/documentation/src/refine-theme/tutorial-file-explorer/utils.ts b/documentation/src/refine-theme/tutorial-file-explorer/utils.ts new file mode 100644 index 000000000000..130ef7a2794b --- /dev/null +++ b/documentation/src/refine-theme/tutorial-file-explorer/utils.ts @@ -0,0 +1,49 @@ +import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client"; + +export const fromPropsToModules = ({ + autoHiddenFiles, + visibleFiles, + files, + prefixedPath, +}: { + prefixedPath: string; + files: SandpackBundlerFiles; + autoHiddenFiles?: boolean; + visibleFiles: string[]; +}): { directories: string[]; modules: string[] } => { + const hasVisibleFilesOption = visibleFiles.length > 0; + + /** + * When visibleFiles or activeFile are set, the hidden and active flags on the files prop are ignored. + */ + const filterByHiddenProperty = autoHiddenFiles && !hasVisibleFilesOption; + const filterByVisibleFilesOption = + autoHiddenFiles && !!hasVisibleFilesOption; + + const fileListWithoutPrefix = Object.keys(files) + .filter((filePath) => { + const isValidatedPath = filePath.startsWith(prefixedPath); + if (filterByVisibleFilesOption) { + return isValidatedPath && visibleFiles.includes(filePath); + } + + if (filterByHiddenProperty) { + return isValidatedPath && !files[filePath]?.hidden; + } + + return isValidatedPath; + }) + .map((file) => file.substring(prefixedPath.length)); + + const directories = new Set( + fileListWithoutPrefix + .filter((file) => file.includes("/")) + .map((file) => `${prefixedPath}${file.split("/")[0]}/`), + ); + + const modules = fileListWithoutPrefix + .filter((file) => !file.includes("/")) + .map((file) => `${prefixedPath}${file}`); + + return { directories: Array.from(directories), modules }; +}; diff --git a/documentation/src/refine-theme/tutorial-sandpack.tsx b/documentation/src/refine-theme/tutorial-sandpack.tsx index 7f0a8a083e46..813b4265b7a0 100644 --- a/documentation/src/refine-theme/tutorial-sandpack.tsx +++ b/documentation/src/refine-theme/tutorial-sandpack.tsx @@ -15,12 +15,13 @@ import { nightOwl, aquaBlue } from "@codesandbox/sandpack-themes"; import { SandpackCodeEditor, - SandpackFileExplorer, SandpackPreview, SandpackProvider, + useSandpack, } from "@codesandbox/sandpack-react"; import { useColorMode } from "@docusaurus/theme-common"; +import { TutorialFileExplorer } from "./tutorial-file-explorer"; type SandpackProps = React.ComponentProps & { startRoute?: string; @@ -42,7 +43,11 @@ type SandpackProps = React.ComponentProps & { }; type Props = React.PropsWithChildren< - SandpackProps & { contentOnly?: boolean; contentPercentage?: number } + SandpackProps & { + contentOnly?: boolean; + contentPercentage?: number; + finalFiles?: SandpackFiles; + } >; const maxPercentage = 70; @@ -51,6 +56,7 @@ export const TutorialSandpack = ({ children, contentOnly, contentPercentage = 45, + finalFiles, ...sandpackProps }: Props) => { const [viewPercentage, setViewPercentage] = @@ -234,6 +240,7 @@ export const TutorialSandpack = ({ startRoute={sandpackProps.startRoute} parentResizing={resizing} codeEditorOptions={codeEditorOptions} + finalFiles={finalFiles} />
@@ -324,21 +331,21 @@ const TutorialSandpackBase = ({ showTabs: true, initMode: "lazy", classes: { - "sp-file-explorer": - "!h-full !w-[200px] border-r border-r-gray-300 dark:border-r-gray-700", "sp-layout": "!rounded-lg !border-gray-300 dark:!border-gray-700", "sp-close-button": "!visible", "sp-editor": - "!h-full !gap-0 border-r !border-r-gray-300 dark:!border-r-gray-700", + "!h-full !gap-0 border-r !border-r-gray-300 dark:!border-r-gray-700 overflow-hidden", "sp-stack": "!h-full", "sp-tabs": "!border-b-gray-300 dark:!border-b-gray-700 !bg-gray-0 dark:!bg-gray-800", - "sp-tabs-scrollable-container": "!min-h-[32px]", + "sp-tabs-scrollable-container": "!min-h-[32px] scrollbar-hidden", "sp-input": "!text-gray-800 dark:!text-gray-100", "sp-cm": clsx( "p-0 bg-transparent", "[&>.cm-editor]:!bg-refine-react-light-code", "[&>.cm-editor]:dark:!bg-refine-react-dark-code", + "[&_.cm-gutters]:!bg-refine-react-light-code", + "[&_.cm-gutters]:dark:!bg-refine-react-dark-code", "[&_.cm-activeLine]:!bg-gray-100 [&_.cm-activeLine]:dark:!bg-gray-800", ), "sp-icon-standalone": @@ -434,11 +441,13 @@ const SandpackRightSide = ({ startRoute, parentResizing, sandpackProps, + finalFiles, }: { parentResizing: boolean; startRoute: string; codeEditorOptions: CodeEditorProps; sandpackProps: SandpackProps; + finalFiles?: SandpackFiles; }) => { const [viewPercentage, setViewPercentage] = React.useState(50); const [resizing, setResizing] = React.useState(false); @@ -539,6 +548,7 @@ const SandpackRightSide = ({ "rounded-[4px]", "border border-gray-300 dark:border-gray-700", "flex", + "relative", )} style={{ height: hidePreview @@ -547,12 +557,18 @@ const SandpackRightSide = ({ }} > {showFiles ? ( - + + ) : null} + {finalFiles ? ( + ) : null} { + const { sandpack } = useSandpack(); + const [solved, setSolved] = React.useState(false); + + const onClick = () => { + if (solved) { + sandpack?.resetAllFiles(); + } else { + sandpack?.updateFile(finalFiles); + } + setSolved((p) => !p); + }; + + return ( +
+ +
+ ); +}; + const ResizeHandleIcon = (props: React.SVGProps) => ( ) => ( ); + +const ChevronRightIcon = (props: React.SVGProps) => ( + + + + +); + +const ResetIcon = (props: React.SVGProps) => ( + + + +); diff --git a/documentation/src/refine-theme/tutorial-update-file-button.tsx b/documentation/src/refine-theme/tutorial-update-file-button.tsx new file mode 100644 index 000000000000..b24ffca0e2af --- /dev/null +++ b/documentation/src/refine-theme/tutorial-update-file-button.tsx @@ -0,0 +1,100 @@ +import clsx from "clsx"; +import React from "react"; + +type Props = { + onClick: () => void; +}; + +export const TutorialUpdateFileButton = ({ onClick }: Props) => { + return ( + + ); +}; + +const FileIcon = (props: React.SVGProps) => ( + + + +); diff --git a/documentation/src/theme/MDXComponents/A.js b/documentation/src/theme/MDXComponents/A.js index a679e32c9d3e..7ad711f7fd3b 100644 --- a/documentation/src/theme/MDXComponents/A.js +++ b/documentation/src/theme/MDXComponents/A.js @@ -1,9 +1,23 @@ import React from "react"; import Link from "@docusaurus/Link"; import { getLinkRel } from "@site/src/utils/link-rel"; +import { useLocation } from "@docusaurus/router"; export default function MDXA(props) { const rel = getLinkRel(props?.href); - return ; + const location = useLocation(); + + return ( + + ); } diff --git a/documentation/tutorial/essentials/data-fetching/fetching-data/index.md b/documentation/tutorial/essentials/data-fetching/fetching-data/index.md new file mode 100644 index 000000000000..5289792167a4 --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/fetching-data/index.md @@ -0,0 +1,106 @@ +--- +title: Fetching a Record +--- + +import { Sandpack, AddGetOneMethod, CreateShowProductFile, AddUseOneToShowProduct, AddShowProductToAppTsx } from "./sandpack.tsx"; + + + +In this step, we'll be learning about the Refine's `useOne` hook to fetch a single record from our API and implement the `getOne` method in our data provider. + +## Implementing the `getOne` Method + +To fetch a record using Refine's hooks, first we need to implement the [`getOne`](/docs/data/data-provider/#getone-) method in our data provider. This method will be called when we use the [`useOne`](/docs/data/hooks/use-one) hook or its extensions in our components. + +The `getOne` method accepts `resource`, `id` and `meta` properties. + +- `resource` refers to the entity we're fetching. +- `id` is the ID of the record we're fetching. +- `meta` is an object containing any additional data passed to the hook. + +Our fake API has `products` entity and expects us to fetch a single record using the `/products/:id` endpoint. So, we'll be using the `resource` and `id` properties to make our request. + +Try to add the following lines to your `src/data-provider.ts` file: + +```ts title="src/data-provider.ts" +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + // highlight-start + getOne: async ({ resource, id, meta }) => { + const response = await fetch(`${API_URL}/${resource}/${id}`); + const data = await response.json(); + + return { data }; + }, + // highlight-end + update: () => { + throw new Error("Not implemented"); + }, + getList: () => { + throw new Error("Not implemented"); + }, + /* ... */ +}; +``` + + + +## Using the `useOne` Hook + +After implementing the `getOne` method, we'll be able to call `useOne` hook and fetch a single record from our API. Let's create a component called `ShowProduct` and mount it inside our `` component. + + + +Then, we'll import `useOne` hook and use it inside our `ShowProduct` component to fetch a single record of `products` entity from our API. + +Try to add the following lines to your `src/show-product.tsx` file: + +```tsx title="src/show-product.tsx" +// highlight-next-line +import { useOne } from "@refinedev/core"; + +export const ShowProduct = () => { + // highlight-next-line + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + + if (isLoading) { + return
Loading...
; + } + + return
Product name: {data?.data.name}
; +}; +``` + + + +Finally, we'll mount the `ShowProduct` component inside our `` component. + +Try to add the following lines to your `src/App.tsx` file: + +```tsx title="src/App.tsx" +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; +// highlight-next-line +import { ShowProduct } from "./show-product"; + +export default function App(): JSX.Element { + return ( + + {/* highlight-next-line */} + + + ); +} +``` + + + +Now, we should be able to see the product name on our screen. + +In the next step, we'll be learning about the Refine's `useUpdate` hook to update a single record from our API and implement the `update` method in our data provider. + +
diff --git a/documentation/tutorial/essentials/data-fetching/fetching-data/sandpack.tsx b/documentation/tutorial/essentials/data-fetching/fetching-data/sandpack.tsx new file mode 100644 index 000000000000..29ab89413989 --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/fetching-data/sandpack.tsx @@ -0,0 +1,186 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; +import { useSandpack } from "@codesandbox/sandpack-react"; +import { TutorialUpdateFileButton } from "@site/src/refine-theme/tutorial-update-file-button"; +import { TutorialCreateFileButton } from "@site/src/refine-theme/tutorial-create-file-button"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +const AppTsxCode = /* tsx */ ` +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +export default function App(): JSX.Element { + return ; +} +`.trim(); + +const DataProviderTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getOne: () => { throw new Error("Not implemented"); }, + update: () => { throw new Error("Not implemented"); }, + getList: () => { throw new Error("Not implemented"); }, + create: () => { throw new Error("Not implemented"); }, + deleteOne: () => { throw new Error("Not implemented"); }, + getApiUrl: () => API_URL, + // Optional methods: + // getMany: () => { /* ... */ }, + // createMany: () => { /* ... */ }, + // deleteMany: () => { /* ... */ }, + // updateMany: () => { /* ... */ }, + // custom: () => { /* ... */ }, +} +`.trim(); + +const DataProviderWithGetOneMethodTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: () => { throw new Error("Not implemented"); }, + getList: () => { throw new Error("Not implemented"); }, + create: () => { throw new Error("Not implemented"); }, + deleteOne: () => { throw new Error("Not implemented"); }, + /* ... */ +}; +`.trim(); + +const BaseShowProductTsxCode = /* tsx */ ` +export const ShowProduct = () => { + return

Hello world!

; +}; +`.trim(); + +const ShowProductWithUseOneTsxCode = /* tsx */ ` +import { useOne } from "@refinedev/core"; + +export const ShowProduct = () => { + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + + if (isLoading) { + return
Loading...
; + } + + return
Product name: {data?.data.name}
; +}; +`.trim(); + +const AppTsxWithShowProductCode = /* tsx */ ` +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; +import { ShowProduct } from "./show-product"; + +export default function App(): JSX.Element { + return ( + + + + ); +} +`.trim(); + +export const AddGetOneMethod = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/data-provider.ts", + DataProviderWithGetOneMethodTsCode, + ); + sandpack.setActiveFile("/data-provider.ts"); + }} + /> + ); +}; + +export const CreateShowProductFile = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.addFile({ + "/show-product.tsx": { + code: BaseShowProductTsxCode, + }, + }); + sandpack.openFile("/show-product.tsx"); + sandpack.setActiveFile("/show-product.tsx"); + }} + name="show-product.tsx" + /> + ); +}; + +export const AddUseOneToShowProduct = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/show-product.tsx", + ShowProductWithUseOneTsxCode, + ); + sandpack.setActiveFile("/show-product.tsx"); + }} + /> + ); +}; + +export const AddShowProductToAppTsx = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile("/App.tsx", AppTsxWithShowProductCode); + sandpack.setActiveFile("/App.tsx"); + }} + /> + ); +}; diff --git a/documentation/tutorial/essentials/data-fetching/index.md b/documentation/tutorial/essentials/data-fetching/index.md deleted file mode 100644 index a92cc0c4012d..000000000000 --- a/documentation/tutorial/essentials/data-fetching/index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Data Fetching ---- - -import { Sandpack } from "./sandpack.tsx"; - - - -Your content here. - - diff --git a/documentation/tutorial/essentials/data-fetching/intro/index.md b/documentation/tutorial/essentials/data-fetching/intro/index.md new file mode 100644 index 000000000000..06fec13b63e4 --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/intro/index.md @@ -0,0 +1,46 @@ +--- +title: Data Fetching +--- + +import { Sandpack, FocusOnDataProviderFile, AddDataProviderToRefine } from "./sandpack.tsx"; + + + +In this step, we'll be learning about the basics of data fetching in Refine. `` component accepts a [`dataProvider`](/docs/core/refine-component/#dataprovider-) prop which is used to handle all the data fetching and mutation operations with a simple interface. While Refine supports many data providers out of the box, for sake of this tutorial, we'll be creating our own data provider and connecting it to a [fake REST API](https://api.fake-rest.refine.dev/). + +To learn more about the supported data providers, refer to the [Supported Data Providers](/docs/guides-concepts/data-fetching/#supported-data-providers) section in the Data Fetching guide + +## Creating a Data Provider + +We'll be implementing each method one-by-one, ensuring thorough coverage of all details. We'll use `fetch` for API requests, but you're free to choose any library. + +First, we'll create a `src/data-provider.ts` file in our project, which will contain all the methods we need to implement for our data provider. + +To see an empty data provider, check out the `data-provider.ts` in the right panel. + +Then, we'll pass our data provider to `` component in `src/App.tsx` file with the `dataProvider` prop. + +Try to add the following code to your `src/App.tsx` file: + +```tsx +// We're also removing the `` component from the file. +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +export default function App(): JSX.Element { + return ; +} +``` + + + +:::tip + +It's also possible to use multiple data providers with Refine. You can learn more about it in the [Multiple Data Providers](/docs/guides-concepts/data-fetching/#multiple-data-providers) section of the Data Fetching guide. + +::: + +In the next step, we'll be learning about the fetching a record using Refine's `useOne` hook, and also about implementing the `getOne` method in our data provider. + + diff --git a/documentation/tutorial/essentials/data-fetching/intro/sandpack.tsx b/documentation/tutorial/essentials/data-fetching/intro/sandpack.tsx new file mode 100644 index 000000000000..ac82544919ed --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/intro/sandpack.tsx @@ -0,0 +1,113 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; +import { useSandpack } from "@codesandbox/sandpack-react"; +import clsx from "clsx"; +import { TutorialUpdateFileButton } from "@site/src/refine-theme/tutorial-update-file-button"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +const AppTsxCode = /* tsx */ ` +import { Refine, WelcomePage } from "@refinedev/core"; + +export default function App(): JSX.Element { + return ( + + + + ); +}; +`.trim(); + +const UpdatedAppTsxCode = /* tsx */ ` +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +export default function App(): JSX.Element { + return ; +} +`.trim(); + +const DataProviderTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getOne: () => { throw new Error("Not implemented"); }, + update: () => { throw new Error("Not implemented"); }, + getList: () => { throw new Error("Not implemented"); }, + create: () => { throw new Error("Not implemented"); }, + deleteOne: () => { throw new Error("Not implemented"); }, + getApiUrl: () => API_URL, + // Optional methods: + // getMany: () => { /* ... */ }, + // createMany: () => { /* ... */ }, + // deleteMany: () => { /* ... */ }, + // updateMany: () => { /* ... */ }, + // custom: () => { /* ... */ }, +} +`.trim(); + +export const FocusOnDataProviderFile = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.openFile("/data-provider.ts"); + }} + className={clsx( + "cursor-pointer", + "text-refine-link-light dark:text-refine-link-dark", + "[&>code]:!text-refine-link-light dark:[&>code]:!text-refine-link-dark", + "hover:underline", + )} + > + {children} + + ); +}; + +export const AddDataProviderToRefine = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile("App.tsx", UpdatedAppTsxCode); + sandpack.setActiveFile("/App.tsx"); + }} + /> + ); +}; diff --git a/documentation/tutorial/essentials/data-fetching/listing-data/index.md b/documentation/tutorial/essentials/data-fetching/listing-data/index.md new file mode 100644 index 000000000000..81c57d2ba2c1 --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/listing-data/index.md @@ -0,0 +1,375 @@ +--- +title: Listing Records +--- + +import { Sandpack, AddGetListMethod, CreateListProductsFile, AddUseListToListProducts, AddListProductsToAppTsx, AddPaginationToGetList, AddPaginationToListProducts, AddSortingToGetList, AddSortingToListProducts, AddFiltersToGetList, AddFiltersToListProducts } from "./sandpack.tsx"; + + + +In this step, we'll be learning about the Refine's `useList` hook to fetch a list of records from our API. We'll also learn about pagination, sorting and filtering through the `useList` hook. + +## Implementing the `getList` Method + +To list records using Refine's hooks, first we need to implement the [`getList`](/docs/data/data-provider/#getlist-) method in our data provider. This method will be called when we use the [`useList`](/docs/data/hooks/use-list) hook or its extensions in our components. + +The `getList` method accepts `resource`, `pagination`, `sorters`, `filters` and `meta` properties. + +- `resource` refers to the entity we're fetching. +- `pagination` is an object containing the `current` and `pageSize` properties. +- `sorters` is an array containing the sorters we're using. +- `filters` is an array containing the filters we're using. +- `meta` is an object containing any additional data passed to the hook. + +Our fake API has `products` entity and expects us to list records using the `/products` endpoint. So, we'll be using the `resource`, `pagination`, `sorters` and `filters` properties to make our request. + +To make the implementation process easier, we'll start by implementing the `getList` method without pagination, sorting, or filtering, and then gradually add these features to our implementation. + +Try to add the following lines to your `src/data-provider.ts` file: + +```ts title="src/data-provider.ts" +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + // highlight-start + getList: async ({ resource, pagination, filters, sorters, meta }) => { + const response = await fetch(`${API_URL}/${resource}`); + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + // highlight-end + /* ... */ +}; +``` + + + +## Using the `useList` Hook + +After implementing the `getList` method, we'll be able to call `useList` hook and fetch a list of records from our API. Let's create a component called `ListProducts` and mount it inside our `` component. + + + +Then, we'll use the `useList` hook inside our `ListProducts` to fetch a list of records of `products` entity from our API. + +Try to add the following lines to your `src/list-products.tsx` file: + +```tsx title="src/list-products.tsx" +// highlight-next-line +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + // highlight-start + const { data, isLoading } = useList({ resource: "products" }); + // highlight-end + + if (isLoading) { + return
Loading...
; + } + + return ( +
+

Products

+
    + {/* highlight-next-line */} + {data?.data?.map((product) => ( +
  • +

    + {product.name} +
    + Price: {product.price} +
    + Material: {product.material} +

    +
  • + ))} +
+
+ ); +}; +``` + + + +Finally, we'll mount our `ListProducts` component inside our `` component. + +Try to add the following lines to your `src/App.tsx` file: + +```tsx title="src/App.tsx" +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +import { ShowProduct } from "./show-product"; +import { EditProduct } from "./edit-product"; +// highlight-next-line +import { ListProducts } from "./list-products"; + +export default function App(): JSX.Element { + return ( + + {/* */} + {/* */} + {/* highlight-next-line */} + + + ); +} +``` + + + +We should be able to see the list of products on our screen now. + +## Adding Pagination + +At this point, we've listed all the products in our API, but we're not able to paginate the list. Let's add pagination logic to our `getList` method. + +Our fake API supports pagination through the `_start` and `_end` query parameters. `_start` is the index of the first record we want to fetch and `_end` is the index of the last record we want to fetch. So, we'll be using the `pagination` property to calculate the `_start` and `_end` query parameters. + +Try to add the following lines to your `src/data-provider.ts` file: + +```ts title="src/data-provider.ts" +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getList: async ({ resource, pagination, filters, sorters, meta }) => { + // highlight-start + const params = new URLSearchParams(); + + if (pagination) { + params.append("_start", (pagination.current - 1) * pagination.pageSize); + params.append("_end", pagination.current * pagination.pageSize); + } + + const response = await fetch(`${API_URL}/${resource}?${params.toString()}`); + // highlight-end + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + /* ... */ +}; +``` + + + +Now, we'll be able to paginate the list of products. Let's add pagination to our `ListProducts` component. + +Try to add the following lines to your `src/list-products.tsx` file: + +```tsx title="src/list-products.tsx" +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + const { data, isLoading } = useList({ + resource: "products", + // highlight-next-line + pagination: { current: 1, pageSize: 10 }, + }); + + if (isLoading) { + return
Loading...
; + } + + return
{/* ... */}
; +}; +``` + + + +We should be able to see the first 10 products on our screen now. + +## Adding Sorting + +We've added pagination to our `getList` method, but we're not able to sort the list. Let's add sorting logic to our `getList` method. + +Our fake API supports sorting through the `_sort` and `_order` query parameters. `_sort` is the name of the field we want to sort and `_order` is the order we want to sort. So, we'll be using the `sorters` property to calculate the `_sort` and `_order` query parameters. + +:::simple Implementation Details + +Refine supports multiple sorters to be passed to the `useList` hook. Fortunately, our fake API also supports multiple sorters. But, if your API doesn't support multiple sorters, you can simply use the first sorter in the `sorters` array. + +Our fake API requires multiple sorters and orders to be passed with a comma separated string. So, we'll be mapping the `sorters` array to a comma separated string. + +::: + +Try to add the following lines to your `src/data-provider.ts` file: + +```ts title="src/data-provider.ts" +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getList: async ({ resource, pagination, filters, sorters, meta }) => { + const params = new URLSearchParams(); + + if (pagination) { + params.append("_start", (pagination.current - 1) * pagination.pageSize); + params.append("_end", pagination.current * pagination.pageSize); + } + + // highlight-start + if (sorters && sorters.length > 0) { + params.append("_sort", sorters.map((sorter) => sorter.field).join(",")); + params.append("_order", sorters.map((sorter) => sorter.order).join(",")); + } + // highlight-end + + const response = await fetch(`${API_URL}/${resource}?${params.toString()}`); + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + /* ... */ +}; +``` + + + +Now, we'll be able to sort the list of products. Let's add sorting to our `ListProducts` component. + +Try to add the following lines to your `src/list-products.tsx` file: + +```tsx title="src/list-products.tsx" +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + const { data, isLoading } = useList({ + resource: "products", + pagination: { current: 1, pageSize: 10 }, + // highlight-next-line + sorters: [{ field: "name", order: "asc" }], + }); + + if (isLoading) { + return
Loading...
; + } + + return
{/* ... */}
; +}; +``` + + + +We should be able to see the first 10 products sorted by name on our screen now. + +## Adding Filtering + +We've added sorting to our `getList` method. But, we're not able to filter the list. Let's add filtering logic to our `getList` method. + +`useList`'s `filters` property implements the [`CrudFilters`](#) interface which accepts various operators for fields. To learn more about the operators, you can check the [Filters](#) section of the Data Fetching guide. + +:::simple Implementation Details + +- Refine supports multiple filters to be passed to the `useList` hook. Fortunately, our fake API also supports multiple filters. But, if your API doesn't support multiple filters, you can simply use the first filter in the `filters` array. + +- Our fake API supports filtering with various operators but for sake of simplicity, we'll be implementing filtering through with `"eq"` operator. + +- `URLSearchParams`'s `append` method accepts duplicate keys and appends them to the query string. So, we'll be mapping through the `filters` array and appending the field name and value to the query string. + +- Refine also supports conditional filtering operators `"and"` and `"or"`. But, our fake API doesn't support these operators. So, we'll be ignoring these operators in our implementation. + +::: + +Try to add the following lines to your `src/data-provider.ts` file: + +```ts title="src/data-provider.ts" +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getList: async ({ resource, pagination, filters, sorters, meta }) => { + const params = new URLSearchParams(); + + if (pagination) { + params.append("_start", (pagination.current - 1) * pagination.pageSize); + params.append("_end", pagination.current * pagination.pageSize); + } + + if (sorters && sorters.length > 0) { + params.append("_sort", sorters.map((sorter) => sorter.field).join(",")); + params.append("_order", sorters.map((sorter) => sorter.order).join(",")); + } + + // highlight-start + if (filters && filters.length > 0) { + filters.forEach((filter) => { + if ("field" in filter && filter.operator === "eq") { + // Our fake API supports "eq" operator by simply appending the field name and value to the query string. + params.append(filter.field, filter.value); + } + }); + } + // highlight-end + + const response = await fetch(`${API_URL}/${resource}?${params.toString()}`); + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + /* ... */ +}; +``` + + + +Now, we'll be able to filter the list of products. Let's add filtering to our `ListProducts` component. + +Try to add the following lines to your `src/list-products.tsx` file: + +```tsx title="src/list-products.tsx" +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + const { data, isLoading } = useList({ + resource: "products", + pagination: { current: 1, pageSize: 10 }, + sorters: [{ field: "name", order: "asc" }], + // highlight-next-line + filters: [{ field: "material", operator: "eq", value: "Aluminum" }], + }); + + if (isLoading) { + return
Loading...
; + } + + return
{/* ... */}
; +}; +``` + + + +## Summary + +In this step, we've learned about the Refine's `useList` hook to fetch a list of records from our API. We've also learned about pagination, sorting and filtering through the `useList` hook. + +Refine also offers `useInfiniteList` hook to fetch a list of records with infinite scrolling and `useTable` hook to fetch a list of records with additional features on top of `useList` hook. You can check the [Hooks](#) section of the Data Fetching guide and [Tables](#) guide to learn more about these hooks and their usages. + +:::simple Implementation Tips + +To see the full implementation of a REST data provider, please check the [source code of `@refinedev/simple-rest`](https://github.com/refinedev/refine/tree/master/packages/simple-rest). + +::: + +In the next steps, we'll learn about how to handle forms and tables with Refine. + +
diff --git a/documentation/tutorial/essentials/data-fetching/listing-data/sandpack.tsx b/documentation/tutorial/essentials/data-fetching/listing-data/sandpack.tsx new file mode 100644 index 000000000000..1990bcb3251f --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/listing-data/sandpack.tsx @@ -0,0 +1,660 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; +import { useSandpack } from "@codesandbox/sandpack-react"; +import { TutorialUpdateFileButton } from "@site/src/refine-theme/tutorial-update-file-button"; +import { TutorialCreateFileButton } from "@site/src/refine-theme/tutorial-create-file-button"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +const AppTsxCode = /* tsx */ ` +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; +import { ShowProduct } from "./show-product"; +import { EditProduct } from "./edit-product"; + +export default function App(): JSX.Element { + return ( + + {/* */} + + + ); +} +`.trim(); + +const DataProviderTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: async ({ resource, id, variables }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`, { + method: "PATCH", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + getList: () => { throw new Error("Not implemented"); }, + create: () => { throw new Error("Not implemented"); }, + deleteOne: () => { throw new Error("Not implemented"); }, + /* ... */ +}; +`.trim(); + +const ShowProductTsxCode = /* tsx */ ` +import { useOne } from "@refinedev/core"; + +export const ShowProduct = () => { + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + + if (isLoading) { + return
Loading...
; + } + + return
Product name: {data?.data.name}
; +}; +`.trim(); + +const EditProductTsxCode = /* tsx */ ` +import { useOne, useUpdate } from "@refinedev/core"; + +export const EditProduct = () => { + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + const { mutate, isLoading: isUpdating } = useUpdate(); + + if (isLoading) { + return
Loading...
; + } + + const updatePrice = async () => { + await mutate({ + resource: "products", + id: 123, + values: { + price: Math.floor(Math.random() * 100), + }, + }); + }; + + return ( +
+
Product name: {data?.data.name}
+
Product price: \${data?.data.price}
+ +
+ ); +}; +`.trim(); + +const DataProviderWithGetListMethodTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getList: async ({ resource, pagination, filters, sorters, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}\`); + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: async ({ resource, id, variables }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`, { + method: "PATCH", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + /* ... */ +}; +`.trim(); + +const DataProviderWithPaginationTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getList: async ({ resource, pagination, filters, sorters, meta }) => { + const params = new URLSearchParams(); + + if (pagination) { + params.append("_start", (pagination.current - 1) * pagination.pageSize); + params.append("_end", pagination.current * pagination.pageSize); + } + + const response = await fetch(\`\${API_URL}/\${resource}?\${params.toString()}\`); + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: async ({ resource, id, variables }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`, { + method: "PATCH", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + /* ... */ +}; +`.trim(); + +const DataProviderWithSortingTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getList: async ({ resource, pagination, filters, sorters, meta }) => { + const params = new URLSearchParams(); + + if (pagination) { + params.append("_start", (pagination.current - 1) * pagination.pageSize); + params.append("_end", pagination.current * pagination.pageSize); + } + + if (sorters && sorters.length > 0) { + params.append("_sort", sorters.map((sorter) => sorter.field).join(",")); + params.append("_order", sorters.map((sorter) => sorter.order).join(",")); + } + + const response = await fetch(\`\${API_URL}/\${resource}?\${params.toString()}\`); + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: async ({ resource, id, variables }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`, { + method: "PATCH", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + /* ... */ +}; +`.trim(); + +const DataProviderWithFilteringTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getList: async ({ resource, pagination, filters, sorters, meta }) => { + const params = new URLSearchParams(); + + if (pagination) { + params.append("_start", (pagination.current - 1) * pagination.pageSize); + params.append("_end", pagination.current * pagination.pageSize); + } + + if (sorters && sorters.length > 0) { + params.append("_sort", sorters.map((sorter) => sorter.field).join(",")); + params.append("_order", sorters.map((sorter) => sorter.order).join(",")); + } + + if (filters && filters.length > 0) { + filters.forEach((filter) => { + if ("field" in filter && filter.operator === "eq") { + // Our fake API supports "eq" operator by simply appending the field name and value to the query string. + params.append(filter.field, filter.value); + } + }); + } + + const response = await fetch(\`\${API_URL}/\${resource}?\${params.toString()}\`); + const data = await response.json(); + + return { + data, + total: 0, // We'll cover this in the next steps. + }; + }, + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: async ({ resource, id, variables }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`, { + method: "PATCH", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + /* ... */ +}; +`.trim(); + +const BaseListProductsTsxCode = /* tsx */ ` +export const ListProducts = () => { + return ( +
+

Products

+
+ ); +}; +`.trim(); + +const ListProductsWithUseListTsxCode = /* tsx */ ` +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + const { data, isLoading } = useList({ resource: "products" }); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+

Products

+
    + {data?.data?.map((product) => ( +
  • +

    + {product.name} +
    + Price: {product.price} +
    + Material: {product.material} +

    +
  • + ))} +
+
+ ); +}; +`.trim(); + +const ListProductsWithPaginationTsxCode = /* tsx */ ` +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + const { data, isLoading } = useList({ + resource: "products", + pagination: { current: 1, pageSize: 10 }, + }); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+

Products

+
    + {data?.data?.map((product) => ( +
  • +

    + {product.name} +
    + Price: {product.price} +
    + Material: {product.material} +

    +
  • + ))} +
+
+ ); +}; +`.trim(); + +const ListProductsWithSortingTsxCode = /* tsx */ ` +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + const { data, isLoading } = useList({ + resource: "products", + pagination: { current: 1, pageSize: 10 }, + sorters: [{ field: "name", order: "asc" }], + }); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+

Products

+
    + {data?.data?.map((product) => ( +
  • +

    + {product.name} +
    + Price: {product.price} +
    + Material: {product.material} +

    +
  • + ))} +
+
+ ); +}; +`.trim(); + +const ListProductsWithFilteringTsxCode = /* tsx */ ` +import { useList } from "@refinedev/core"; + +export const ListProducts = () => { + const { data, isLoading } = useList({ + resource: "products", + pagination: { current: 1, pageSize: 10 }, + sorters: [{ field: "name", order: "asc" }], + filters: [{ field: "material", operator: "eq", value: "Aluminum" }], + }); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+

Products

+
    + {data?.data?.map((product) => ( +
  • +

    + {product.name} +
    + Price: {product.price} +
    + Material: {product.material} +

    +
  • + ))} +
+
+ ); +}; +`.trim(); + +const AppTsxWithListProductsCode = /* tsx */ ` +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +import { ShowProduct } from "./show-product"; +import { EditProduct } from "./edit-product"; +import { ListProducts } from "./list-products"; + +export default function App(): JSX.Element { + return ( + + {/* */} + {/* */} + + + ); +} +`.trim(); + +export const AddGetListMethod = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/data-provider.ts", + DataProviderWithGetListMethodTsCode, + ); + sandpack.setActiveFile("/data-provider.ts"); + }} + /> + ); +}; + +export const CreateListProductsFile = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.addFile({ + "/list-products.tsx": { + code: BaseListProductsTsxCode, + }, + }); + sandpack.openFile("/list-products.tsx"); + sandpack.setActiveFile("/list-products.tsx"); + }} + name="list-products.tsx" + /> + ); +}; + +export const AddUseListToListProducts = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/list-products.tsx", + ListProductsWithUseListTsxCode, + ); + sandpack.setActiveFile("/list-products.tsx"); + }} + /> + ); +}; + +export const AddListProductsToAppTsx = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile("/App.tsx", AppTsxWithListProductsCode); + sandpack.setActiveFile("/App.tsx"); + }} + /> + ); +}; + +export const AddPaginationToGetList = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/data-provider.ts", + DataProviderWithPaginationTsCode, + ); + sandpack.setActiveFile("/data-provider.ts"); + }} + /> + ); +}; + +export const AddPaginationToListProducts = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/list-products.tsx", + ListProductsWithPaginationTsxCode, + ); + sandpack.setActiveFile("/list-products.tsx"); + }} + /> + ); +}; + +export const AddSortingToGetList = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/data-provider.ts", + DataProviderWithSortingTsCode, + ); + sandpack.setActiveFile("/data-provider.ts"); + }} + /> + ); +}; + +export const AddSortingToListProducts = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/list-products.tsx", + ListProductsWithSortingTsxCode, + ); + sandpack.setActiveFile("/list-products.tsx"); + }} + /> + ); +}; + +export const AddFiltersToGetList = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/data-provider.ts", + DataProviderWithFilteringTsCode, + ); + sandpack.setActiveFile("/data-provider.ts"); + }} + /> + ); +}; + +export const AddFiltersToListProducts = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/list-products.tsx", + ListProductsWithFilteringTsxCode, + ); + sandpack.setActiveFile("/list-products.tsx"); + }} + /> + ); +}; diff --git a/documentation/tutorial/essentials/data-fetching/sandpack.tsx b/documentation/tutorial/essentials/data-fetching/sandpack.tsx deleted file mode 100644 index db3bde6111b1..000000000000 --- a/documentation/tutorial/essentials/data-fetching/sandpack.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; -import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; - -export const Sandpack = ({ children }: { children: React.ReactNode }) => { - return ( - Hello world! -} - `.trim(), - }, - }} - > - {children} - - ); -}; diff --git a/documentation/tutorial/essentials/data-fetching/updating-data/index.md b/documentation/tutorial/essentials/data-fetching/updating-data/index.md new file mode 100644 index 000000000000..52f6dfebd4e1 --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/updating-data/index.md @@ -0,0 +1,146 @@ +--- +title: Updating a Record +--- + +import { Sandpack, AddUpdateMethod, CreateEditProductFile, AddUseUpdateToEditProduct, AddEditProductToAppTsx } from "./sandpack.tsx"; + + + +In this step, we'll be learning about the Refine's `useUpdate` hook to update a record from our API and implement the `update` method in our data provider. + +## Implementing the `update` Method + +To update a record using Refine's hooks, first we need to implement the [`update`](/docs/data/data-provider/#update-) method in our data provider. This method will be called when we use the [`useUpdate`](/docs/data/hooks/use-update) hook or its extensions in our components. + +The `update` method accepts `resource`, `id`, `variables` and `meta` properties. + +- `resource` refers to the entity we're updating +- `id` is the ID of the record we're updating +- `variables` is an object containing the data we're sending to the API. +- `meta` is an object containing any additional data passed to the hook. + +`products` entity of our fake API expects us to update a record using the `/products/:id` endpoint with a `PATCH` request. So, we'll be using the `resource`, `id` and `variables` properties to make our request. + +Try to add the following lines to your `src/data-provider.ts` file: + +```ts title="src/data-provider.ts" +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getOne: async ({ resource, id, meta }) => { + const response = await fetch(`${API_URL}/${resource}/${id}`); + const data = await response.json(); + + return { data }; + }, + // highlight-start + update: async ({ resource, id, variables }) => { + const response = await fetch(`${API_URL}/${resource}/${id}`, { + method: "PATCH", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + // highlight-end + getList: () => { + throw new Error("Not implemented"); + }, + /* ... */ +}; +``` + + + +## Using the `useUpdate` Hook + +After implementing the `update` method, we'll be able to call `useUpdate` hook and update a single record from our API. Let's create a component called `EditProduct` and mount it inside our `` component. + + + +Initially, we'll include a `useOne` hook call in our `EditProduct` component to fetch the record we want to update. + +Then, we'll use the `useUpdate` hook inside our `EditProduct` to update a single record of `products` entity from our API. + +Try to add the following lines to your `src/edit-product.tsx` file: + +```tsx title="src/edit-product.tsx" +// highlight-next-line +import { useOne, useUpdate } from "@refinedev/core"; + +export const EditProduct = () => { + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + // highlight-next-line + const { mutate, isLoading: isUpdating } = useUpdate(); + + if (isLoading) { + return
Loading...
; + } + + const updatePrice = async () => { + // highlight-start + await mutate({ + resource: "products", + id: 123, + values: { + price: Math.floor(Math.random() * 100), + }, + }); + // highlight-end + }; + + return ( +
+
Product name: {data?.data.name}
+
Product price: ${data?.data.price}
+ +
+ ); +}; +``` + + + +Finally, we'll mount our `EditProduct` component inside our `` component. + +Try to add the following lines to your `src/App.tsx` file: + +```tsx title="src/App.tsx" +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +import { ShowProduct } from "./show-product"; +// highlight-next-line +import { EditProduct } from "./edit-product"; + +export default function App(): JSX.Element { + return ( + + {/* */} + {/* highlight-next-line */} + + + ); +} +``` + + + +Now, we should be able to view both the product name and price on our screen. Once we click the `Update Price` button, the product's price will be updated. + +:::tip Smart Invalidations + +Notice that when we update the price using `useUpdate`, the `useOne` hook we called before is automatically invalidated. This is because Refine will invalidate all the queries that are using the same resource and id when we update a record. This will ensure that we'll always see the latest data on our screen and we won't need to manually invalidate the queries. + +::: + +In the next step, we'll be learning about the Refine's `useList` hook to fetch a list of records from our API and implement the `getList` method in our data provider. + +
diff --git a/documentation/tutorial/essentials/data-fetching/updating-data/sandpack.tsx b/documentation/tutorial/essentials/data-fetching/updating-data/sandpack.tsx new file mode 100644 index 000000000000..0cd662b66485 --- /dev/null +++ b/documentation/tutorial/essentials/data-fetching/updating-data/sandpack.tsx @@ -0,0 +1,258 @@ +import React from "react"; +import { TutorialSandpack } from "@site/src/refine-theme/tutorial-sandpack"; +import { useSandpack } from "@codesandbox/sandpack-react"; +import { TutorialUpdateFileButton } from "@site/src/refine-theme/tutorial-update-file-button"; +import { TutorialCreateFileButton } from "@site/src/refine-theme/tutorial-create-file-button"; + +export const Sandpack = ({ children }: { children: React.ReactNode }) => { + return ( + + {children} + + ); +}; + +const AppTsxCode = /* tsx */ ` +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; +import { ShowProduct } from "./show-product"; + +export default function App(): JSX.Element { + return ( + + + + ); +} +`.trim(); + +const DataProviderTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: () => { throw new Error("Not implemented"); }, + getList: () => { throw new Error("Not implemented"); }, + create: () => { throw new Error("Not implemented"); }, + deleteOne: () => { throw new Error("Not implemented"); }, + /* ... */ +}; +`.trim(); + +const ShowProductTsxCode = /* tsx */ ` +import { useOne } from "@refinedev/core"; + +export const ShowProduct = () => { + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + + if (isLoading) { + return
Loading...
; + } + + return
Product name: {data?.data.name}
; +}; +`.trim(); + +const DataProviderWithUpdateMethodTsCode = /* ts */ ` +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + getOne: async ({ resource, id, meta }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`); + const data = await response.json(); + + return { data }; + }, + update: async ({ resource, id, variables }) => { + const response = await fetch(\`\${API_URL}/\${resource}/\${id}\`, { + method: "PATCH", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + getList: () => { throw new Error("Not implemented"); }, + create: () => { throw new Error("Not implemented"); }, + deleteOne: () => { throw new Error("Not implemented"); }, + /* ... */ +}; +`.trim(); + +const BaseEditProductTsxCode = /* tsx */ ` +import { useOne, useUpdate } from "@refinedev/core"; + +export const EditProduct = () => { + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+
Product name: {data?.data.name}
+
Product price: \${data?.data.price}
+
+ ); +}; +`.trim(); + +const EditProductWithUseUpdateTsxCode = /* tsx */ ` +import { useOne, useUpdate } from "@refinedev/core"; + +export const EditProduct = () => { + const { data, isLoading } = useOne({ resource: "products", id: 123 }); + const { mutate, isLoading: isUpdating } = useUpdate(); + + if (isLoading) { + return
Loading...
; + } + + const updatePrice = async () => { + await mutate({ + resource: "products", + id: 123, + values: { + price: Math.floor(Math.random() * 100), + }, + }); + }; + + return ( +
+
Product name: {data?.data.name}
+
Product price: \${data?.data.price}
+ +
+ ); +}; +`.trim(); + +const AppTsxWithEditProductCode = /* tsx */ ` +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +import { ShowProduct } from "./show-product"; +// highlight-next-line +import { EditProduct } from "./edit-product"; + +export default function App(): JSX.Element { + return ( + + {/* */} + {/* highlight-next-line */} + + + ); +} +`.trim(); + +export const AddUpdateMethod = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/data-provider.ts", + DataProviderWithUpdateMethodTsCode, + ); + sandpack.setActiveFile("/data-provider.ts"); + }} + /> + ); +}; + +export const CreateEditProductFile = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.addFile({ + "/edit-product.tsx": { + code: BaseEditProductTsxCode, + }, + }); + sandpack.openFile("/edit-product.tsx"); + sandpack.setActiveFile("/edit-product.tsx"); + }} + name="edit-product.tsx" + /> + ); +}; + +export const AddUseUpdateToEditProduct = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile( + "/edit-product.tsx", + EditProductWithUseUpdateTsxCode, + ); + sandpack.setActiveFile("/edit-product.tsx"); + }} + /> + ); +}; + +export const AddEditProductToAppTsx = () => { + const { sandpack } = useSandpack(); + + return ( + { + sandpack.updateFile("/App.tsx", AppTsxWithEditProductCode); + sandpack.setActiveFile("/App.tsx"); + }} + /> + ); +}; diff --git a/documentation/tutorial/essentials/forms/index.md b/documentation/tutorial/essentials/forms/index.md index e77fadbeab95..25885aff0100 100644 --- a/documentation/tutorial/essentials/forms/index.md +++ b/documentation/tutorial/essentials/forms/index.md @@ -2,10 +2,413 @@ title: Forms --- -import { Sandpack } from "./sandpack.tsx"; +import { Sandpack, AddCreateMethod, CreateCreateProductFile, AddUseFormToCreateProduct, AddCreateProductToAppTsx, AddPriceUpdateToCreateProduct, AddCategoryRelationToCreateProduct, MountEditProductInAppTsx, RefactorToUseFormInEditProduct } from "./sandpack.tsx"; -Your content here. +In this step, we'll be learning about the Refine's `useForm` hook to manage forms for creating and updating records. + +:::simple Implementation Tips + +Refine's `useForm` has extended versions with more features and compatibility with other libraries. To learn more about the `useForm` hook, please refer to the [Forms](/docs/guides-concepts/forms) guide. + +::: + +`useForm` hook can be used for 3 different actions; + +- `create`: To create a new record for a resource using the data provider's `create` method. +- `edit`: To update an existing record for a resource using the data provider's `update` method. +- `clone`: To create a new record using an existing record's data as a template using the data provider's `create` method. + +In this step, we'll be covering the `create` and `edit` actions. Check out the [Clone](/docs/guides-concepts/forms/#clone) section of the Forms guide for information about the `clone` action. + +## Implementing the `create` Method + +To create a record using Refine's `useForm` and `useCreate` hooks, first we need to implement the `create` method in our data provider. This method will be called when we use the `useForm` with `create` action. + +The `create` method will receive `resource`, `variables` and `meta` properties. `resource` will be the name of the entity we're creating. `variables` will be an object containing the data we're sending to the API. `meta` will be an object containing any additional data we're passing to the hook. + +`products` entity of our fake API expects us to create a record using the `/products` endpoint with a `POST` request. So, we'll be using the `resource` and `variables` properties to make our request. + +Try to add the following lines to your `src/data-provider.ts` file: + +```ts title="src/data-provider.ts" +import type { DataProvider } from "@refinedev/core"; + +const API_URL = "https://api.fake-rest.refine.dev"; + +export const dataProvider: DataProvider = { + // highlight-start + create: async ({ resource, variables }) => { + const response = await fetch(`${API_URL}/${resource}`, { + method: "POST", + body: JSON.stringify(variables), + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + + return { data }; + }, + // highlight-end + update: async ({ resource, id, variables }) => { + /* ... */ + }, + getList: async ({ resource, pagination, filters, sorters }) => { + /* ... */ + }, + getOne: async ({ resource, id }) => { + /* ... */ + }, + /* ... */ +}; +``` + + + +## Using the `useForm` Hook + +After implementing the `create` method, we'll be able to call `useForm` hook and create a single record from our API. Let's create a component called `CreateProduct` and mount it inside our `` component. Then, we'll use the `useForm` hook inside our `CreateProduct` to create a record of `products` entity from our API. + + + +Now, we'll mount our `CreateProduct` component inside our `` component. Let's add the following lines to our `src/App.tsx` file: + +```tsx title="src/App.tsx" +import { Refine } from "@refinedev/core"; + +import { dataProvider } from "./data-provider"; + +import { ShowProduct } from "./show-product"; +import { EditProduct } from "./edit-product"; +import { ListProducts } from "./list-products"; +// highlight-next-line +import { CreateProduct } from "./create-product"; + +export default function App(): JSX.Element { + return ( + + {/* */} + {/* */} + {/* */} + {/* highlight-next-line */} + + + ); +} +``` + + + +We'll be using the `useForm` hook and have form fields for `name`, `description`, `price`, `material` and `category`. + +Let's add the following lines to our `src/create-product.tsx` file: + +```tsx title="src/create-product.tsx" +import { useForm } from "@refinedev/core"; + +export const CreateProduct = () => { + const { onFinish, mutationResult } = useForm({ + action: "create", + resource: "products", + }); + + const onSubmit = (event: React.FormEvent) => { + event.preventDefault(); + // Using FormData to get the form values and convert it to an object. + const data = Object.fromEntries(new FormData(event.target).entries()); + // Calling onFinish to submit with the data we've collected from the form. + onFinish(data); + }; + + return ( +
+ + + + +