diff --git a/.gitignore b/.gitignore index 05517d4..1f11769 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ coverage *.njsproj *.sln *.sw? + +# tanstack/router **/routeTree.gen.ts +src/route-tree.ts diff --git a/package.json b/package.json index 8590591..7f5f2f9 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@testing-library/jest-dom": "6.4.5", "@testing-library/react": "^15.0.6", "@types/jest": "^29.5.12", + "@types/node": "^20.14.1", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.0", "@types/react-test-renderer": "^18.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b52c7d..2ecb077 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,16 +53,19 @@ importers: version: 1.31.20(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/router-vite-plugin': specifier: ^1.26.21 - version: 1.31.18(vite@5.2.12(@types/node@20.12.8)) + version: 1.31.18(vite@5.2.12(@types/node@20.14.1)) '@testing-library/jest-dom': specifier: 6.4.5 - version: 6.4.5(@types/jest@29.5.12)(vitest@1.6.0(@types/node@20.12.8)(jsdom@24.0.0)) + version: 6.4.5(@types/jest@29.5.12)(vitest@1.6.0(@types/node@20.14.1)(jsdom@24.0.0)) '@testing-library/react': specifier: ^15.0.6 version: 15.0.6(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/jest': specifier: ^29.5.12 version: 29.5.12 + '@types/node': + specifier: ^20.14.1 + version: 20.14.1 '@types/react': specifier: ^18.3.1 version: 18.3.3 @@ -80,10 +83,10 @@ importers: version: 7.8.0(eslint@8.57.0)(typescript@5.4.5) '@vitejs/plugin-react-swc': specifier: ^3.5.0 - version: 3.6.0(@swc/helpers@0.5.11)(vite@5.2.12(@types/node@20.12.8)) + version: 3.6.0(@swc/helpers@0.5.11)(vite@5.2.12(@types/node@20.14.1)) '@vitest/coverage-v8': specifier: ^1.5.3 - version: 1.6.0(vitest@1.6.0(@types/node@20.12.8)(jsdom@24.0.0)) + version: 1.6.0(vitest@1.6.0(@types/node@20.14.1)(jsdom@24.0.0)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) @@ -116,10 +119,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.11 - version: 5.2.12(@types/node@20.12.8) + version: 5.2.12(@types/node@20.14.1) vitest: specifier: ^1.5.3 - version: 1.6.0(@types/node@20.12.8)(jsdom@24.0.0) + version: 1.6.0(@types/node@20.14.1)(jsdom@24.0.0) packages: @@ -2060,8 +2063,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@20.12.8': - resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==} + '@types/node@20.14.1': + resolution: {integrity: sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -5851,7 +5854,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.8 + '@types/node': 20.14.1 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -7199,7 +7202,7 @@ snapshots: prettier: 3.2.5 zod: 3.23.6 - '@tanstack/router-vite-plugin@1.31.18(vite@5.2.12(@types/node@20.12.8))': + '@tanstack/router-vite-plugin@1.31.18(vite@5.2.12(@types/node@20.14.1))': dependencies: '@babel/core': 7.24.5 '@babel/generator': 7.24.5 @@ -7215,7 +7218,7 @@ snapshots: '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 - '@vitejs/plugin-react': 4.2.1(vite@5.2.12(@types/node@20.12.8)) + '@vitejs/plugin-react': 4.2.1(vite@5.2.12(@types/node@20.14.1)) zod: 3.23.6 transitivePeerDependencies: - supports-color @@ -7234,7 +7237,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.5(@types/jest@29.5.12)(vitest@1.6.0(@types/node@20.12.8)(jsdom@24.0.0))': + '@testing-library/jest-dom@6.4.5(@types/jest@29.5.12)(vitest@1.6.0(@types/node@20.14.1)(jsdom@24.0.0))': dependencies: '@adobe/css-tools': 4.3.3 '@babel/runtime': 7.24.5 @@ -7246,7 +7249,7 @@ snapshots: redent: 3.0.0 optionalDependencies: '@types/jest': 29.5.12 - vitest: 1.6.0(@types/node@20.12.8)(jsdom@24.0.0) + vitest: 1.6.0(@types/node@20.14.1)(jsdom@24.0.0) '@testing-library/react@15.0.6(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -7326,7 +7329,7 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@20.12.8': + '@types/node@20.14.1': dependencies: undici-types: 5.26.5 @@ -7627,25 +7630,25 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react-swc@3.6.0(@swc/helpers@0.5.11)(vite@5.2.12(@types/node@20.12.8))': + '@vitejs/plugin-react-swc@3.6.0(@swc/helpers@0.5.11)(vite@5.2.12(@types/node@20.14.1))': dependencies: '@swc/core': 1.4.17(@swc/helpers@0.5.11) - vite: 5.2.12(@types/node@20.12.8) + vite: 5.2.12(@types/node@20.14.1) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.2.1(vite@5.2.12(@types/node@20.12.8))': + '@vitejs/plugin-react@4.2.1(vite@5.2.12(@types/node@20.14.1))': dependencies: '@babel/core': 7.24.5 '@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.24.5) '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.2.12(@types/node@20.12.8) + vite: 5.2.12(@types/node@20.14.1) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.12.8)(jsdom@24.0.0))': + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.1)(jsdom@24.0.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -7660,7 +7663,7 @@ snapshots: std-env: 3.7.0 strip-literal: 2.1.0 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@20.12.8)(jsdom@24.0.0) + vitest: 1.6.0(@types/node@20.14.1)(jsdom@24.0.0) transitivePeerDependencies: - supports-color @@ -9125,7 +9128,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 20.14.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -10563,13 +10566,13 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-node@1.6.0(@types/node@20.12.8): + vite-node@1.6.0(@types/node@20.14.1): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.12(@types/node@20.12.8) + vite: 5.2.12(@types/node@20.14.1) transitivePeerDependencies: - '@types/node' - less @@ -10580,16 +10583,16 @@ snapshots: - supports-color - terser - vite@5.2.12(@types/node@20.12.8): + vite@5.2.12(@types/node@20.14.1): dependencies: esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.18.0 optionalDependencies: - '@types/node': 20.12.8 + '@types/node': 20.14.1 fsevents: 2.3.3 - vitest@1.6.0(@types/node@20.12.8)(jsdom@24.0.0): + vitest@1.6.0(@types/node@20.14.1)(jsdom@24.0.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -10608,11 +10611,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.2.12(@types/node@20.12.8) - vite-node: 1.6.0(@types/node@20.12.8) + vite: 5.2.12(@types/node@20.14.1) + vite-node: 1.6.0(@types/node@20.14.1) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.12.8 + '@types/node': 20.14.1 jsdom: 24.0.0 transitivePeerDependencies: - less diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..589ff94 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,34 @@ +import { StrictMode } from 'react'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; +import { createRouter, RouterProvider } from '@tanstack/react-router'; + +import { MarigoldProvider } from '@marigold/components'; +import theme from '@marigold/theme-core'; + +import { routeTree } from './route-tree'; + +// Router +// --------------- +const router = createRouter({ routeTree }); + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +// Query +// --------------- +const queryClient = new QueryClient(); + +// App +// --------------- +export const App = () => ( + + + + + + + +); diff --git a/src/components/App.test.tsx b/src/components/App.test.tsx deleted file mode 100644 index dfc033c..0000000 --- a/src/components/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { expect } from 'vitest'; -import { render } from '@testing-library/react'; -import App from './App'; - -test('renders correctly', () => { - const { container } = render(); - - expect(container).toMatchSnapshot(); -}); diff --git a/src/components/App.tsx b/src/components/App.tsx deleted file mode 100644 index 6f6fee0..0000000 --- a/src/components/App.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import '@code-hike/mdx/styles'; -import '../css/custom-ch.css'; -import WelcomeContent from './index.mdx'; - -const App = () => ( -
- -
-); - -export default App; diff --git a/src/components/DemoLink.tsx b/src/components/DemoLink.tsx index 8864ea9..8dc3556 100644 --- a/src/components/DemoLink.tsx +++ b/src/components/DemoLink.tsx @@ -1,19 +1,17 @@ import { Link } from '@tanstack/react-router'; import { ExternalLink } from '@marigold/icons'; -import { ReactNode } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; -const DemoLink = ({ - destination, - children, -}: { - destination: string; - children: ReactNode; -}) => { +export interface DemoLinkProps extends ComponentProps { + children?: ReactNode; +} + +const DemoLink = ({ children, ...props }: DemoLinkProps) => { return (
diff --git a/src/components/Devtools.tsx b/src/components/Devtools.tsx new file mode 100644 index 0000000..71a9017 --- /dev/null +++ b/src/components/Devtools.tsx @@ -0,0 +1,18 @@ +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { Suspense, lazy } from 'react'; + +const TanStackRouterDevtools = + process.env.NODE_ENV === 'production' + ? () => null + : lazy(() => + import('@tanstack/router-devtools').then(res => ({ + default: res.TanStackRouterDevtools, + })) + ); + +export const Devtools = () => ( + + + + +); diff --git a/src/components/Header.test.tsx b/src/components/Header.test.tsx deleted file mode 100644 index 219ecd1..0000000 --- a/src/components/Header.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { expect } from 'vitest'; -import Header from './Header'; -import { - createRootRoute, - createRouter, - RouterProvider, -} from '@tanstack/react-router'; -import { act, render } from '@testing-library/react'; - -test('renders
correctly', async () => { - const rootRoute = createRootRoute({ - component: () =>
, - }); - - const router = createRouter({ - routeTree: rootRoute, - }); - - const { container } = render(); - - await act(async () => { - await new Promise(resolve => setTimeout(resolve, 1000)); - }); - - expect(container).toMatchSnapshot(); -}); diff --git a/src/components/Logo.test.tsx b/src/components/Logo.test.tsx deleted file mode 100644 index 770b139..0000000 --- a/src/components/Logo.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { expect } from 'vitest'; -import { render } from '@testing-library/react'; -import { Logo } from './Logo'; - -test('renders correctly', () => { - const { container } = render(); - - expect(container).toMatchSnapshot(); -}); diff --git a/src/components/Preview.tsx b/src/components/Preview.tsx deleted file mode 100644 index 423dfdf..0000000 --- a/src/components/Preview.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import BreadcrumbsExample from './compoundComponents/Breadcrumbs/BreadcrumbsExample'; -import TabsExample from './compoundComponents/Tabs/TabsExample'; -import ServerStateExample from './state-management/ServerState/ServerStateExample'; -import { Route } from '../routes/$component.preview.$example'; - -const components = { - BreadcrumbsExample, - TabsExample, - ServerStateExample, -}; - -const Preview = () => { - const { example } = Route.useParams(); - const PreviewComponent = components[example as keyof typeof components]; - - return ( -
- -
- ); -}; - -export default Preview; diff --git a/src/components/SideNavigation.test.tsx b/src/components/SideNavigation.test.tsx deleted file mode 100644 index 2f30e45..0000000 --- a/src/components/SideNavigation.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { act, render, screen } from '@testing-library/react'; -import { SideNavigation } from './SideNavigation'; -import { - createRootRoute, - createRouter, - RouterProvider, -} from '@tanstack/react-router'; -import { expect } from 'vitest'; - -test('renders navigation links', async () => { - const rootRoute = createRootRoute({ - component: () => , - }); - - const router = createRouter({ - routeTree: rootRoute, - }); - - render(); - - await act(async () => { - await new Promise(resolve => setTimeout(resolve, 1000)); - }); - - const links = screen.getAllByRole('link'); - - expect(screen.getByText('Welcome')).toBeInTheDocument(); - expect(links[0]).toHaveAttribute('href', '/'); - expect(screen.getByText('Compound Component')).toBeInTheDocument(); - expect(links[1]).toHaveAttribute('href', '/compoundComponent'); -}); diff --git a/src/components/SideNavigation.tsx b/src/components/SideNavigation.tsx index 2a7574d..21748db 100644 --- a/src/components/SideNavigation.tsx +++ b/src/components/SideNavigation.tsx @@ -25,12 +25,12 @@ export const SideNavigation = () => { group: 'Patterns', items: [ { - linkTo: '/compoundComponent', + linkTo: '/compound-component', name: 'Compound Component', }, { linkTo: '/state-management', - name: 'state-management', + name: 'State Management', }, ], }, diff --git a/src/components/__snapshots__/App.test.tsx.snap b/src/components/__snapshots__/App.test.tsx.snap deleted file mode 100644 index a654770..0000000 --- a/src/components/__snapshots__/App.test.tsx.snap +++ /dev/null @@ -1,136 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`renders correctly 1`] = ` -
-
-

- Welcome to the React Reference App -

- - -

- The React Reference App is a resource designed to provide you with practical examples of various coding patterns and practices, mainly in - - ReactJS - - . -It serves as a reference point for you to understand, learn, and implement these patterns in your own projects. -

- - -

- Patterns are a way of organizing code in a way that makes it easier to understand, maintain, and extend. They are a set of best practices that have been proven to work well in a variety of situations. -In the end, patterns are like blueprints that can solve recurring problems and challenges. -

- - -

- The main purpose of this app is to provide an overview of the most common patterns and practices in ReactJS using simple examples. -Specific issues or challenges, such as managing state, handling forms will be addressed. These are also shown in conjunction with the - - Marigold Design System - - in appropriate places. -

- - -
- - -

- How to use the React Reference App? -

- - -

- Using the React Reference App is straightforward. Each pattern or practice is presented with a detailed explanation and code examples. -You can copy these code examples directly into your own projects. To do this, simply click the copy button ( - - ) in the code snippet. -Clicking this button will copy the code to your clipboard, ready to be pasted into your own code editor. -

- - -
- - -

- Which patterns will be addressed? -

- - -

- The patterns are selected from our survey we made. So in future we will try to add all of them. -

- - - Survey - - -

- If you have suggestions for patterns that should be added to the React Reference App, or if you think some of your own code could -be transformed into a useful pattern here, please let us - - know - - . -

- - -
- - -

- Feedback -

- - -

- Constructive feedback, ideas and suggestions for improvement are welcome. -

- - -

- Please use one of our - - channels - - to contact us. -

-
-
-`; diff --git a/src/components/__snapshots__/Header.test.tsx.snap b/src/components/__snapshots__/Header.test.tsx.snap deleted file mode 100644 index 5ec3290..0000000 --- a/src/components/__snapshots__/Header.test.tsx.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`renders
correctly 1`] = ` - -`; diff --git a/src/components/__snapshots__/Logo.test.tsx.snap b/src/components/__snapshots__/Logo.test.tsx.snap deleted file mode 100644 index 5bea305..0000000 --- a/src/components/__snapshots__/Logo.test.tsx.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`renders correctly 1`] = ` -
- - - - -
-`; diff --git a/src/components/compoundComponents/Breadcrumbs/BreadcrumbsExample.test.tsx b/src/components/compoundComponents/Breadcrumbs/BreadcrumbsExample.test.tsx deleted file mode 100644 index b8c3bae..0000000 --- a/src/components/compoundComponents/Breadcrumbs/BreadcrumbsExample.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { expect, test } from 'vitest'; -import BreadcrumbsExample from './BreadcrumbsExample'; -import { render } from '@testing-library/react'; - -test('Breadcrumbs are correctly rendered', () => { - const { container } = render(); - - expect(container).toMatchSnapshot(); -}); diff --git a/src/components/compoundComponents/Breadcrumbs/__snapshots__/BreadcrumbsExample.test.tsx.snap b/src/components/compoundComponents/Breadcrumbs/__snapshots__/BreadcrumbsExample.test.tsx.snap deleted file mode 100644 index 7cd424b..0000000 --- a/src/components/compoundComponents/Breadcrumbs/__snapshots__/BreadcrumbsExample.test.tsx.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Breadcrumbs are correctly rendered 1`] = ` - -`; diff --git a/src/components/compoundComponents/CompoundApp.test.tsx b/src/components/compoundComponents/CompoundApp.test.tsx deleted file mode 100644 index 5af16b4..0000000 --- a/src/components/compoundComponents/CompoundApp.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { expect } from 'vitest'; -import { act, render } from '@testing-library/react'; -import CompoundApp from './CompoundApp'; -import { - createRootRoute, - createRouter, - RouterProvider, -} from '@tanstack/react-router'; - -test('renders correctly', async () => { - const rootRoute = createRootRoute({ - component: () => , - }); - - const router = createRouter({ - routeTree: rootRoute, - }); - - const { container } = render(); - - await act(async () => { - await new Promise(resolve => setTimeout(resolve, 1000)); - }); - - expect(container).toMatchSnapshot(); -}); diff --git a/src/components/compoundComponents/CompoundApp.tsx b/src/components/compoundComponents/CompoundApp.tsx deleted file mode 100644 index 7cd18ac..0000000 --- a/src/components/compoundComponents/CompoundApp.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import '@code-hike/mdx/styles'; -import '../../css/custom-ch.css'; -import CompoundComponentsContent from './index.mdx'; - -function CompoundApp() { - return ( -
-
- -
-
- ); -} - -export default CompoundApp; diff --git a/src/components/compoundComponents/__snapshots__/CompoundApp.test.tsx.snap b/src/components/compoundComponents/__snapshots__/CompoundApp.test.tsx.snap deleted file mode 100644 index d856105..0000000 --- a/src/components/compoundComponents/__snapshots__/CompoundApp.test.tsx.snap +++ /dev/null @@ -1,1492 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`renders correctly 1`] = ` -
-
-
- - - - - -

- Compound components in React -

- - -

- This document provides an explanation and examples of using compound components in React. Compound components are a design pattern -where related child components are encapsulated within a parent component to create a reusable and flexible component suite. -

- - -

- This pattern enhances customization and simplifies the internal composition of components. -

- - -

- Importance of the Pattern -

- - -

- Compound components are important for several reasons: -

- - -
    - - -
  • - - -

    - - Encapsulation and Reusability: - - They allow encapsulating related components, making the parent component more reusable and modular. -

    - - -
  • - - -
  • - - -

    - - Customizability: - - Each child component can be individually customized without exposing numerous customization props through the parent component. -

    - - -
  • - - -
  • - - -

    - - Simplicity in State Management: - - A well-designed compound component should require minimal state management, making the component easier to understand and maintain. -

    - - -
  • - - -
- - -

- Use-Cases and Examples -

- - -

- Example 1: - - <select> - - and - - <option> - - in HTML -

- - -

- A similiar example in HTML are the - - <select> - - and - - <option> - - tags: -

- - -
-
-
-
-
-
-
-
-
-
-
-
- - select.html -
-
-
- -
-
-
- -
-
- - _ - 10 - -
- - <select> - -
-
-
- - _ - 10 - -
- - <option value="pizza">Pizza</option> - -
-
-
- - _ - 10 - -
- - <option value="sushi">Sushi</option> - -
-
-
- - _ - 10 - -
- - <option value="soup">Soup</option> - -
-
-
- - _ - 10 - -
- - </select> - -
-
-
-
-
-
-
-
- - -

- The select tag works together with the option tag which is used for a drop-down menu to select items in HTML. Here the - - <select> - - manages the state of the UI, then the - - <option> - - elements are configured on how the - - <select> - - should work. -

- - -

- Example 2: Building a compound component -

- - - - - - - - -

- E-commerce websites often have “breadcrumbs” to help the user navigate to parent/grand-parent pages. For example: -

- - -

- - Electronics / Cell Phones / Cases - -

- - -
- - - - - -
-
-
-
-

- Let's imagine we have the following code for these breadcrumbs (see BreadcrumbsExample.tsx). -

-

- How we can built now a compound component? -

-

- Looking at the markup, it seems like we'll need two components: -

-
    - - -
  • - - -

    - A - - Breadcrumbs - - parent wrapper to contain the group -

    - - -
  • - - -
  • - - -

    - A - - Crumb - - component for each link in the group -

    - - -
  • - - -
-
-
-

- Copy the chunk for the parent wrapper ( - - Breadcrumbs - - ) and made it generic. -

-
-
-

- Copy the chunk for each link in the group ( - - Crumb - - ) and made it generic. -

-
-
-

- As final step we need to connect these components. -

-

- We can call it weird or awesome, but JS functions can have properties. -Functions are like objects in this way. We can "hang" data on them, like clothes hung on a clothesline. -

-
-
-

- Finally we can use this compound component in our BreadcrumbsExample.tsx. -

-
-
-

- Note that we only need to import the - - Breadcrumbs - - component. -

-
-
-

- Because we connect - - Crumb - - to - - Breadcrumbs - - we can access it as a property. -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - BreadcrumbsExample.tsx -
-
-
- -
-
-
- -
-
- - _ - 26 - -
- -
-
-
- - _ - 26 - -
- - import React from 'react'; - -
-
-
- - _ - 26 - -
- -
-
-
- - _ - 26 - -
- - import Breadcrumbs from './Breadcrumbs'; - -
-
-
- - _ - 26 - -
- -
-
-
- - _ - 26 - -
- - function App() { - -
-
-
- - _ - 26 - -
- - return ( - -
-
-
- - _ - 26 - -
- - <nav aria-label="Breadcrumbs"> - -
-
-
- - _ - 26 - -
- - <ol> - -
-
-
- - _ - 26 - -
- - <li> - -
-
-
- - _ - 26 - -
- - <a href="/">Electronics</a> - -
-
-
- - _ - 26 - -
- - </li> - -
-
-
- - _ - 26 - -
- - <li> - -
-
-
- - _ - 26 - -
- - <a href="/cellphones">Cell Phones</a> - -
-
-
- - _ - 26 - -
- - </li> - -
-
-
- - _ - 26 - -
- - <li> - -
-
-
- - _ - 26 - -
- - <a href="/cellphones/cases" aria-current="page"> - -
-
-
- - _ - 26 - -
- - Cases - -
-
-
- - _ - 26 - -
- - </a> - -
-
-
- - _ - 26 - -
- - </li> - -
-
-
- - _ - 26 - -
- - </ol> - -
-
-
- - _ - 26 - -
- - </nav> - -
-
-
- - _ - 26 - -
- - ); - -
-
-
- - _ - 26 - -
- - } - -
-
-
- - _ - 26 - -
- -
-
-
- - _ - 26 - -
- - export default App; - -
-
-
-
-
-
-
-
-
-
-
- - -
- - -

- Example 3: Compound component with Context -

- - - - - - - - -

- By leveraging React context, we can provide the necessary states and functions to child components without exposing them through props, resulting in a cleaner and more maintainable codebase. -

- - - - - -
-
-
-
-

- If we want to pass props to different components and avoid prop drilling, we will need to set up a - - React context - - object for providing states managed at the parent level to all children components. -

-
-
-

- This component is essentially a wrapper that exposes the active tab state and the function that allow us to change the active tab. The context provider acts as the wrapper. So now all the children inside this container can access the activeTab and the setter function using the useContext hook. -

-
-
-

- Then let’s add the tab component. This is the button that’ll help us to switch between sections. -On clicking this button, it triggers the - - changeTab - - from the context changing the \`activeTab state. Any other component that’s accessing this state will be re-rendered. -

-
-
-

- And finally, the actual tab section. Based on the activeTab from the context, we display the specific tab section. -

-
-
-

- Now we can default export the tabs component and attach the other sub-components to this component. -

-
-
-

- Inside the TabsExample file, let’s import all our tab components and use them as we please. -

-

- Note: We can add any other elements we want like the - - div - - . We can also use a completely different arrangement of the elements. -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Tabs.tsx -
-
-
- -
-
-
- -
-
- - _ - 19 - -
- - import { createContext, ReactNode, useState } from 'react'; - -
-
-
- - _ - 19 - -
- - import Tab from './Tab'; - -
-
-
- - _ - 19 - -
- - import TabPanel from './TabPanel'; - -
-
-
- - _ - 19 - -
- -
-
-
- - _ - 19 - -
- - export const TabsContext = createContext({} as any); - -
-
-
- - _ - 19 - -
- -
-
-
- - _ - 19 - -
- - const Tabs = ({ children }: { children: ReactNode }) => { - -
-
-
- - _ - 19 - -
- - const [activeTab, setActiveTab] = useState<number>(0); - -
-
-
- - _ - 19 - -
- - const changeTab = (tab: number) => setActiveTab(tab); - -
-
-
- - _ - 19 - -
- - return ( - -
-
-
- - _ - 19 - -
- - <TabsContext.Provider value={{ activeTab, changeTab }}> - -
-
-
- - _ - 19 - -
- - <div className="w-[600px] rounded shadow-xl">{children}</div> - -
-
-
- - _ - 19 - -
- - </TabsContext.Provider> - -
-
-
- - _ - 19 - -
- - ); - -
-
-
- - _ - 19 - -
- - }; - -
-
-
- - _ - 19 - -
- -
-
-
- - _ - 19 - -
- - Tabs.Tab = Tab; - -
-
-
- - _ - 19 - -
- - Tabs.TabPanel = TabPanel; - -
-
-
- - _ - 19 - -
- - export default Tabs; - -
-
-
-
-
-
-
-
-
-
-
- - -
-
-
-
-`; diff --git a/src/components/state-management/StateManagementApp.test.tsx b/src/components/state-management/StateManagementApp.test.tsx deleted file mode 100644 index 4d12e7c..0000000 --- a/src/components/state-management/StateManagementApp.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { expect } from 'vitest'; -import { act, render } from '@testing-library/react'; -import StateManagementApp from './StateManagementApp'; -import { - createRootRoute, - createRouter, - RouterProvider, -} from '@tanstack/react-router'; - -test('renders correctly', async () => { - const rootRoute = createRootRoute({ - component: () => , - }); - - const router = createRouter({ - routeTree: rootRoute, - }); - const { container } = render(); - - await act(async () => { - await new Promise(resolve => setTimeout(resolve, 1000)); - }); - - expect(container).toMatchSnapshot(); -}); diff --git a/src/components/state-management/StateManagementApp.tsx b/src/components/state-management/StateManagementApp.tsx deleted file mode 100644 index 53c7fa5..0000000 --- a/src/components/state-management/StateManagementApp.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import '@code-hike/mdx/styles'; -import '../../css/custom-ch.css'; -import StateManagementContent from './index.mdx'; - -function StateManagementApp() { - return ( -
-
- -
-
- ); -} - -export default StateManagementApp; diff --git a/src/components/state-management/__snapshots__/StateManagementApp.test.tsx.snap b/src/components/state-management/__snapshots__/StateManagementApp.test.tsx.snap deleted file mode 100644 index 0985c82..0000000 --- a/src/components/state-management/__snapshots__/StateManagementApp.test.tsx.snap +++ /dev/null @@ -1,2043 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`renders correctly 1`] = ` -
-
-
-

- State Management in React -

- - -

- What is State Management ? -

- - -

- - "State" is any data that describes the current behavior of an application. - - This could include values like "a list of objects fetched from the server", "which item is currently selected", "name of the currently logged-in user", and "is this modal open?". -

- - -

- - State - - allows you to create interactive and dynamic user interfaces. By managing the state of component, you can update the UI in response to user interactions, API calls, or other events. -

- - -

- Here are different types for state management that will be covered in our example: -

- - -
    - - -
  • - - -

    - - Local State: - - Data we manage in one or another component e.g handling inputs data in from. -

    - - -
  • - - -
  • - - -

    - - Server State: - - Data are fetched from the server via an API and cached on the client, there are a couple of great libraries that make data fetching in React a breeze such as: - - - React Query - - - . -

    - - -
  • - - -
- - -

- Let's break down the state management code example: -

- - - - - - - - - - -
-
-
-
-

- Initializing local state for filters using useState hook -

-

- Here, we use the - - useState - - hook to manage the local state of our filters. This state will store the user's input for the movie title and category. -

-
-
-

- Handle User Input: SearchField & Select -

-

- We use the - - SearchField - - component to allow the user to input a search query. The - - onChange - - handler updates the local state with the new search term, which triggers a re-fetch of the data. -

-

- The - - Select - - component allows the user to choose a category. The - - onChange - - handler updates the local state with the selected category, which also triggers a re-fetch of the data. -

-
-
-

- Fetch date from server -

-

- The - - fetchData - - function builds the API URL with query parameters and fetches data from the server. -

-
-
-

- Use React Query to Manage Server State -

-

- The - - useQuery - - hook from React Query fetches data based on the current filters and manages the server state, including loading and error states. -

-
-
-

- Error Handling -

-

- If an error occurs during data fetching, we display an error message. -

-
-
-

- Loading State -

-

- While data is being fetched, we display a loading indicator. -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - ServerStateExample.tsx -
-
-
- -
-
-
- -
-
- - _ - 116 - -
- - import { - -
-
-
- - _ - 116 - -
- - Card, - -
-
-
- - _ - 116 - -
- - Image, - -
-
-
- - _ - 116 - -
- - Inline, - -
-
-
- - _ - 116 - -
- - SearchField, - -
-
-
- - _ - 116 - -
- - Select, - -
-
-
- - _ - 116 - -
- - Stack, - -
-
-
- - _ - 116 - -
- - Text, - -
-
-
- - _ - 116 - -
- - } from '@marigold/components'; - -
-
-
- - _ - 116 - -
- - import { useQuery } from '@tanstack/react-query'; - -
-
-
- - _ - 116 - -
- - import { useState } from 'react'; - -
-
-
- - _ - 116 - -
- -
-
-
- - _ - 116 - -
- - interface IMovie { - -
-
-
- - _ - 116 - -
- - href: string; - -
-
-
- - _ - 116 - -
- - year: number; - -
-
-
- - _ - 116 - -
- - title: string; - -
-
-
- - _ - 116 - -
- - category: string; - -
-
-
- - _ - 116 - -
- - thumbnail: string; - -
-
-
- - _ - 116 - -
- - thumbnail_width: number; - -
-
-
- - _ - 116 - -
- - thumbnail_height: number; - -
-
-
- - _ - 116 - -
- - extract: string; - -
-
-
- - _ - 116 - -
- - } - -
-
-
- - _ - 116 - -
- - function ServerStateExample() { - -
-
-
- - _ - 116 - -
- - const [filters, setFilters] = useState<{ title: string; category: string }>({ - -
-
-
- - _ - 116 - -
- - title: '', - -
-
-
- - _ - 116 - -
- - category: '', - -
-
-
- - _ - 116 - -
- - }); - -
-
-
- - _ - 116 - -
- -
-
-
- - _ - 116 - -
- - const fetchData = async ( - -
-
-
- - _ - 116 - -
- - url: string, - -
-
-
- - _ - 116 - -
- - queryParams: Record<string, string> - -
-
-
- - _ - 116 - -
- - ) => { - -
-
-
- - _ - 116 - -
- - const queryString = new URLSearchParams(queryParams).toString(); - -
-
-
- - _ - 116 - -
- - const apiURL = \`\${url}\${queryString ? \`?\${queryString}\` : ''}\`; - -
-
-
- - _ - 116 - -
- - const data = await fetch(apiURL); - -
-
-
- - _ - 116 - -
- - return await data.json(); - -
-
-
- - _ - 116 - -
- - }; - -
-
-
- - _ - 116 - -
- -
-
-
- - _ - 116 - -
- - const { - -
-
-
- - _ - 116 - -
- - data: movies, - -
-
-
- - _ - 116 - -
- - isError, - -
-
-
- - _ - 116 - -
- - isLoading, - -
-
-
- - _ - 116 - -
- - error, - -
-
-
- - _ - 116 - -
- - } = useQuery<Array<IMovie>>({ - -
-
-
- - _ - 116 - -
- - queryKey: ['users', filters], - -
-
-
- - _ - 116 - -
- - queryFn: async () => - -
-
-
- - _ - 116 - -
- - await fetchData( - -
-
-
- - _ - 116 - -
- - 'https://6630d183c92f351c03db2e12.mockapi.io/movies', - -
-
-
- - _ - 116 - -
- - filters - -
-
-
- - _ - 116 - -
- - ), - -
-
-
- - _ - 116 - -
- - }); - -
-
-
- - _ - 116 - -
- -
-
-
- - _ - 116 - -
- - if (isError) { - -
-
-
- - _ - 116 - -
- - return <span>Error: {error.message}</span>; - -
-
-
- - _ - 116 - -
- - } - -
-
-
- - _ - 116 - -
- -
-
-
- - _ - 116 - -
- - return ( - -
-
-
- - _ - 116 - -
- - <> - -
-
-
- - _ - 116 - -
- - <Stack space={4}> - -
-
-
- - _ - 116 - -
- - <Inline space={4}> - -
-
-
- - _ - 116 - -
- - <SearchField - -
-
-
- - _ - 116 - -
- - value={filters?.title} - -
-
-
- - _ - 116 - -
- - onChange={value => setFilters(prev => ({ ...prev, title: value }))} - -
-
-
- - _ - 116 - -
- - label="search" - -
-
-
- - _ - 116 - -
- - width={'1/2'} - -
-
-
- - _ - 116 - -
- - /> - -
-
-
- - _ - 116 - -
- - <Select - -
-
-
- - _ - 116 - -
- - label="Category" - -
-
-
- - _ - 116 - -
- - placeholder="Select your character" - -
-
-
- - _ - 116 - -
- - width={'1/5'} - -
-
-
- - _ - 116 - -
- - onChange={key => { - -
-
-
- - _ - 116 - -
- - setFilters(prev => ({ ...prev, category: key as string })); - -
-
-
- - _ - 116 - -
- - }} - -
-
-
- - _ - 116 - -
- - selectedKey={filters.category} - -
-
-
- - _ - 116 - -
- - > - -
-
-
- - _ - 116 - -
- - <Select.Option id={''}>all</Select.Option> - -
-
-
- - _ - 116 - -
- - <Select.Option id={'Drama'}>Drama</Select.Option> - -
-
-
- - _ - 116 - -
- - <Select.Option id={'Silent'}>Silent</Select.Option> - -
-
-
- - _ - 116 - -
- - <Select.Option id={'Comedy'}>Comedy</Select.Option> - -
-
-
- - _ - 116 - -
- - <Select.Option id={'Western'}>Western</Select.Option> - -
-
-
- - _ - 116 - -
- - <Select.Option id={'Historical'}>Historical</Select.Option> - -
-
-
- - _ - 116 - -
- - <Select.Option id={'Fantasy'}>Fantasy</Select.Option> - -
-
-
- - _ - 116 - -
- - </Select> - -
-
-
- - _ - 116 - -
- - </Inline> - -
-
-
- - _ - 116 - -
- -
-
-
- - _ - 116 - -
- - {isLoading && <span>Loading...</span>} - -
-
-
- - _ - 116 - -
- - <Inline space={4}> - -
-
-
- - _ - 116 - -
- - { - -
-
-
- - _ - 116 - -
- - // check if search returns nothing(movies='not found') - -
-
-
- - _ - 116 - -
- - typeof movies === 'string' ? ( - -
-
-
- - _ - 116 - -
- - <Text fontSize="xl" color="text-error"> - -
-
-
- - _ - 116 - -
- - {movies} - -
-
-
- - _ - 116 - -
- - </Text> - -
-
-
- - _ - 116 - -
- - ) : ( - -
-
-
- - _ - 116 - -
- - movies?.map(movie => ( - -
-
-
- - _ - 116 - -
- - <Card variant="hovering" key={movie.href}> - -
-
-
- - _ - 116 - -
- - <Text fontSize="xl" weight="bold"> - -
-
-
- - _ - 116 - -
- - {movie.title} - -
-
-
- - _ - 116 - -
- - </Text> - -
-
-
- - _ - 116 - -
- - <Image - -
-
-
- - _ - 116 - -
- - src={movie.thumbnail} - -
-
-
- - _ - 116 - -
- - alt={movie.title} - -
-
-
- - _ - 116 - -
- - width={300} - -
-
-
- - _ - 116 - -
- - height={300} - -
-
-
- - _ - 116 - -
- - /> - -
-
-
- - _ - 116 - -
- - </Card> - -
-
-
- - _ - 116 - -
- - )) - -
-
-
- - _ - 116 - -
- - ) - -
-
-
- - _ - 116 - -
- - } - -
-
-
- - _ - 116 - -
- - </Inline> - -
-
-
- - _ - 116 - -
- - </Stack> - -
-
-
- - _ - 116 - -
- - </> - -
-
-
- - _ - 116 - -
- - ); - -
-
-
- - _ - 116 - -
- - } - -
-
-
- - _ - 116 - -
- -
-
-
- - _ - 116 - -
- - export default ServerStateExample; - -
-
-
-
-
-
-
-
-
-
-
- - -
-
-
-
-`; diff --git a/src/css/custom-ch.css b/src/css/custom-ch.css deleted file mode 100644 index 4049663..0000000 --- a/src/css/custom-ch.css +++ /dev/null @@ -1,45 +0,0 @@ -body { - --ch-scrollycoding-sticker-width: 600px; - --ch-scrollycoding-code-min-height: 62vh; -} - -.ch-scrollycoding { - gap: 0; -} - -.ch-scrollycoding-step-content { - border-radius: 0; - padding: 24px; - border: 0; - border-left: 2px solid transparent; - box-shadow: inset 0 1px #e3e8ee; - margin: 0 0 0 -0.5rem; -} - -.ch-scrollycoding-step-content[data-selected] { - @apply border-border-brand bg-gray-50; -} - -.ch-code-multiline-mark { - background: #1a2652 !important; -} - -.ch-code-multiline-mark-border { - background: none !important; -} - -.ch-scrollycoding-sticker { - flex-flow: column-reverse; - gap: 0 !important; - @apply bg-blue-50; - border-radius: 4px; -} - -.ch-scrollycoding-preview { - @apply bg-gray-100 shadow-sm; - border-radius: 0; -} - -.ch-frame-buttons { - display: none; -} diff --git a/src/index.css b/src/index.css index b4c455d..0013a63 100644 --- a/src/index.css +++ b/src/index.css @@ -25,10 +25,6 @@ --page-side-nav-padding-xl: calc(var(--page-padding-xl) - var(--xx)); } -.article-content { - @apply pl-[--page-side-nav-width] pt-8 md:pl-[--page-side-nav-width-md] xl:md:pl-[--page-side-nav-width-xl]; -} - article { padding: 0 7px; } @@ -148,3 +144,53 @@ h4 { @tailwind base; @tailwind components; @tailwind utilities; + +/************************/ +/* CUSTOM CODE HIKE */ +/************************/ + +body { + --ch-scrollycoding-sticker-width: 600px; + --ch-scrollycoding-code-min-height: 62vh; +} + +.ch-scrollycoding { + gap: 0; +} + +.ch-scrollycoding-step-content { + border-radius: 0; + padding: 24px; + border: 0; + border-left: 2px solid transparent; + box-shadow: inset 0 1px #e3e8ee; + margin: 0 0 0 -0.5rem; +} + +.ch-scrollycoding-step-content[data-selected] { + @apply border-border-brand bg-gray-50; +} + +.ch-code-multiline-mark { + background: #1a2652 !important; +} + +.ch-code-multiline-mark-border { + background: none !important; +} + +.ch-scrollycoding-sticker { + flex-flow: column-reverse; + gap: 0 !important; + @apply bg-blue-50; + border-radius: 4px; +} + +.ch-scrollycoding-preview { + @apply bg-gray-100 shadow-sm; + border-radius: 0; +} + +.ch-frame-buttons { + display: none; +} diff --git a/src/index.tsx b/src/index.tsx index 67ac226..9d2670a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,32 +1,8 @@ -import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; -import { routeTree } from './routeTree.gen'; -import { createRouter, RouterProvider } from '@tanstack/react-router'; -import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; -import { MarigoldProvider } from '@marigold/components'; -import theme from '@marigold/theme-core'; - -const router = createRouter({ routeTree }); - -declare module '@tanstack/react-router' { - interface Register { - router: typeof router; - } -} +import { App } from './App'; -const queryClient = new QueryClient(); - -const rootElement = document.getElementById('app')!; +import '@code-hike/mdx/styles'; +import './index.css'; -if (!rootElement.innerHTML) { - ReactDOM.createRoot(rootElement).render( - - - - - - - - ); -} +const root = document.getElementById('app')!; +ReactDOM.createRoot(root).render(); diff --git a/src/routes/$component.preview.$example.tsx b/src/routes/$component.preview.$example.tsx deleted file mode 100644 index 04f63eb..0000000 --- a/src/routes/$component.preview.$example.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; -import Preview from '../components/Preview'; - -export const Route = createFileRoute('/$component/preview/$example')({ - component: () => , -}); diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 382c512..05a7b9b 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,17 +1,7 @@ import { createRootRoute, Outlet } from '@tanstack/react-router'; -import React, { Suspense } from 'react'; -import { SideNavigation } from '../components/SideNavigation'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import Header from '../components/Header'; - -const TanStackRouterDevtools = - process.env.NODE_ENV === 'production' - ? () => null - : React.lazy(() => - import('@tanstack/router-devtools').then(res => ({ - default: res.TanStackRouterDevtools, - })) - ); +import { SideNavigation } from '@/components/SideNavigation'; +import Header from '@/components/Header'; +import { Devtools } from '@/components/Devtools'; export const Route = createRootRoute({ component: () => ( @@ -19,11 +9,10 @@ export const Route = createRootRoute({
- - - - - +
+ +
+
), diff --git a/src/components/compoundComponents/Breadcrumbs/Breadcrumbs.tsx b/src/routes/compound-component/_components/Breadcrumbs.tsx similarity index 94% rename from src/components/compoundComponents/Breadcrumbs/Breadcrumbs.tsx rename to src/routes/compound-component/_components/Breadcrumbs.tsx index d37da73..d212687 100644 --- a/src/components/compoundComponents/Breadcrumbs/Breadcrumbs.tsx +++ b/src/routes/compound-component/_components/Breadcrumbs.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from 'react'; +import type { ReactNode } from 'react'; function Breadcrumbs({ children }: { children: ReactNode }) { return ( diff --git a/src/components/compoundComponents/Breadcrumbs/BreadcrumbsExample.tsx b/src/routes/compound-component/_components/BreadcrumbsExample.tsx similarity index 100% rename from src/components/compoundComponents/Breadcrumbs/BreadcrumbsExample.tsx rename to src/routes/compound-component/_components/BreadcrumbsExample.tsx diff --git a/src/components/compoundComponents/Tabs/Tab.tsx b/src/routes/compound-component/_components/Tab.tsx similarity index 100% rename from src/components/compoundComponents/Tabs/Tab.tsx rename to src/routes/compound-component/_components/Tab.tsx diff --git a/src/components/compoundComponents/Tabs/TabPanel.tsx b/src/routes/compound-component/_components/TabPanel.tsx similarity index 100% rename from src/components/compoundComponents/Tabs/TabPanel.tsx rename to src/routes/compound-component/_components/TabPanel.tsx diff --git a/src/components/compoundComponents/Tabs/Tabs.tsx b/src/routes/compound-component/_components/Tabs.tsx similarity index 100% rename from src/components/compoundComponents/Tabs/Tabs.tsx rename to src/routes/compound-component/_components/Tabs.tsx diff --git a/src/components/compoundComponents/Tabs/TabsExample.tsx b/src/routes/compound-component/_components/TabsExample.tsx similarity index 100% rename from src/components/compoundComponents/Tabs/TabsExample.tsx rename to src/routes/compound-component/_components/TabsExample.tsx diff --git a/src/components/compoundComponents/Breadcrumbs/breadcrumbs.mdx b/src/routes/compound-component/breadcrumbs.mdx similarity index 88% rename from src/components/compoundComponents/Breadcrumbs/breadcrumbs.mdx rename to src/routes/compound-component/breadcrumbs.mdx index f96a39f..996b090 100644 --- a/src/components/compoundComponents/Breadcrumbs/breadcrumbs.mdx +++ b/src/routes/compound-component/breadcrumbs.mdx @@ -45,7 +45,7 @@ Copy the chunk for the parent wrapper (`Breadcrumbs`) and made it generic. ```tsx Breadcrumbs.tsx focus=3:9 - // from ./Breadcrumbs.tsx + // from ./_components/Breadcrumbs.tsx ``` --- @@ -53,7 +53,7 @@ Copy the chunk for each link in the group (`Crumb`) and made it generic. ```tsx Breadcrumbs.tsx focus=11:31 - // from ./Breadcrumbs.tsx + // from ./_components/Breadcrumbs.tsx ``` --- @@ -61,7 +61,7 @@ As final step we need to connect these components. ```tsx Breadcrumbs.tsx focus=34 - // from ./Breadcrumbs.tsx + // from ./_components/Breadcrumbs.tsx ``` We can call it weird or awesome, but JS functions can have properties. @@ -72,7 +72,7 @@ Finally we can use this compound component in our BreadcrumbsExample.tsx. ```tsx BreadcrumbsExample.tsx - // from ./BreadcrumbsExample.tsx + // from ./_components/BreadcrumbsExample.tsx ``` --- @@ -80,7 +80,7 @@ Note that we only need to import the `Breadcrumbs` component. ```tsx BreadcrumbsExample.tsx focus=1,6,12 - // from ./BreadcrumbsExample.tsx + // from ./_components/BreadcrumbsExample.tsx ``` --- @@ -88,7 +88,7 @@ Because we connect `Crumb` to `Breadcrumbs` we can access it as a property. ```tsx BreadcrumbsExample.tsx focus=6:12 - // from ./BreadcrumbsExample.tsx + // from ./_components/BreadcrumbsExample.tsx ``` diff --git a/src/routes/compound-component/index.lazy.tsx b/src/routes/compound-component/index.lazy.tsx new file mode 100644 index 0000000..f9e4b23 --- /dev/null +++ b/src/routes/compound-component/index.lazy.tsx @@ -0,0 +1,6 @@ +import { createLazyFileRoute } from '@tanstack/react-router'; +import Content from './index.mdx'; + +export const Route = createLazyFileRoute('/compound-component/')({ + component: Content, +}); diff --git a/src/components/compoundComponents/index.mdx b/src/routes/compound-component/index.mdx similarity index 81% rename from src/components/compoundComponents/index.mdx rename to src/routes/compound-component/index.mdx index 0ae5747..032b5fa 100644 --- a/src/components/compoundComponents/index.mdx +++ b/src/routes/compound-component/index.mdx @@ -1,6 +1,7 @@ -import BreadcrumbDoc from './Breadcrumbs/breadcrumbs.mdx'; -import TabsDoc from './Tabs/tabs.mdx'; -import DemoLink from '../DemoLink'; +import DemoLink from '@/components/DemoLink'; + +import BreadcrumbDoc from './breadcrumbs.mdx'; +import TabsDoc from './tabs.mdx'; # Compound components in React @@ -37,13 +38,7 @@ The select tag works together with the option tag which is used for a drop-down ### Example 2: Building a compound component - - View Demo - - - - View Demo - +View Demo E-commerce websites often have “breadcrumbs” to help the user navigate to parent/grand-parent pages. For example: @@ -56,13 +51,7 @@ E-commerce websites often have “breadcrumbs” to help the user navigate to pa ### Example 3: Compound component with Context - - View Demo - - - - View Demo - +View Demo By leveraging React context, we can provide the necessary states and functions to child components without exposing them through props, resulting in a cleaner and more maintainable codebase. diff --git a/src/routes/compound-component/preview.breadcrumbs.lazy.tsx b/src/routes/compound-component/preview.breadcrumbs.lazy.tsx new file mode 100644 index 0000000..a648a97 --- /dev/null +++ b/src/routes/compound-component/preview.breadcrumbs.lazy.tsx @@ -0,0 +1,8 @@ +import { createLazyFileRoute } from '@tanstack/react-router'; +import BreadcrumbsExample from './_components/BreadcrumbsExample'; + +export const Route = createLazyFileRoute( + '/compound-component/preview/breadcrumbs' +)({ + component: BreadcrumbsExample, +}); diff --git a/src/routes/compound-component/preview.tabs.lazy.tsx b/src/routes/compound-component/preview.tabs.lazy.tsx new file mode 100644 index 0000000..74a6ddc --- /dev/null +++ b/src/routes/compound-component/preview.tabs.lazy.tsx @@ -0,0 +1,6 @@ +import { createLazyFileRoute } from '@tanstack/react-router'; +import TabsExample from './_components/TabsExample'; + +export const Route = createLazyFileRoute('/compound-component/preview/tabs')({ + component: TabsExample, +}); diff --git a/src/components/compoundComponents/Tabs/tabs.mdx b/src/routes/compound-component/tabs.mdx similarity index 87% rename from src/components/compoundComponents/Tabs/tabs.mdx rename to src/routes/compound-component/tabs.mdx index 84f7130..bb91a18 100644 --- a/src/components/compoundComponents/Tabs/tabs.mdx +++ b/src/routes/compound-component/tabs.mdx @@ -1,7 +1,7 @@ ```tsx Tabs.tsx focus=5 - // from ./Tabs.tsx + // from ./_components/Tabs.tsx ```` If we want to pass props to different components and avoid prop drilling, we will need to set up a [React context](https://react.dev/reference/react/hooks#context-hooks) object for providing states managed at the parent level to all children components. @@ -9,7 +9,7 @@ --- ```tsx Tabs.tsx focus=7:15 - // from ./Tabs.tsx + // from ./_components/Tabs.tsx ```` This component is essentially a wrapper that exposes the active tab state and the function that allow us to change the active tab. The context provider acts as the wrapper. So now all the children inside this container can access the activeTab and the setter function using the useContext hook. @@ -17,7 +17,7 @@ --- ```tsx Tab.tsx - // from ./Tab.tsx + // from ./_components/Tab.tsx ```` Then let’s add the tab component. This is the button that’ll help us to switch between sections. @@ -26,7 +26,7 @@ --- ```tsx TabPanel.tsx - // from ./TabPanel.tsx + // from ./_components/TabPanel.tsx ```` And finally, the actual tab section. Based on the activeTab from the context, we display the specific tab section. @@ -34,7 +34,7 @@ --- ```tsx Tabs.tsx focus=2:3,17:19 - // from ./Tabs.tsx + // from ./_components/Tabs.tsx ```` Now we can default export the tabs component and attach the other sub-components to this component. @@ -42,7 +42,7 @@ --- ```tsx TabsExample.tsx - // from ./TabsExample.tsx + // from ./_components/TabsExample.tsx ```` Inside the TabsExample file, let’s import all our tab components and use them as we please. diff --git a/src/routes/compoundComponent.index.lazy.tsx b/src/routes/compoundComponent.index.lazy.tsx deleted file mode 100644 index 248bccb..0000000 --- a/src/routes/compoundComponent.index.lazy.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createLazyFileRoute } from '@tanstack/react-router'; -import CompoundApp from '../components/compoundComponents/CompoundApp'; - -export const Route = createLazyFileRoute('/compoundComponent/')({ - component: CompoundApp, -}); diff --git a/src/routes/index.lazy.tsx b/src/routes/index.lazy.tsx index b597799..4392986 100644 --- a/src/routes/index.lazy.tsx +++ b/src/routes/index.lazy.tsx @@ -1,6 +1,6 @@ import { createLazyFileRoute } from '@tanstack/react-router'; -import App from '../components/App'; +import Content from './index.mdx'; export const Route = createLazyFileRoute('/')({ - component: App, + component: Content, }); diff --git a/src/components/index.mdx b/src/routes/index.mdx similarity index 100% rename from src/components/index.mdx rename to src/routes/index.mdx diff --git a/src/components/state-management/ServerState/ServerStateExample.tsx b/src/routes/state-management/_components/ServerStateExample.tsx similarity index 100% rename from src/components/state-management/ServerState/ServerStateExample.tsx rename to src/routes/state-management/_components/ServerStateExample.tsx diff --git a/src/routes/state-management/example.lazy.tsx b/src/routes/state-management/example.lazy.tsx deleted file mode 100644 index 0531449..0000000 --- a/src/routes/state-management/example.lazy.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createLazyFileRoute } from '@tanstack/react-router'; -import ServerStateExample from '../../components/state-management/ServerState/ServerStateExample'; - -export const Route = createLazyFileRoute('/state-management/example')({ - component: ServerStateExample, -}); diff --git a/src/routes/state-management/index.lazy.tsx b/src/routes/state-management/index.lazy.tsx index b5f1619..e1f0df0 100644 --- a/src/routes/state-management/index.lazy.tsx +++ b/src/routes/state-management/index.lazy.tsx @@ -1,6 +1,6 @@ import { createLazyFileRoute } from '@tanstack/react-router'; -import StateManagementApp from '../../components/state-management/StateManagementApp'; +import Content from './index.mdx'; export const Route = createLazyFileRoute('/state-management/')({ - component: StateManagementApp, + component: Content, }); diff --git a/src/components/state-management/index.mdx b/src/routes/state-management/index.mdx similarity index 84% rename from src/components/state-management/index.mdx rename to src/routes/state-management/index.mdx index f9f2ede..fa4903c 100644 --- a/src/components/state-management/index.mdx +++ b/src/routes/state-management/index.mdx @@ -1,5 +1,5 @@ -import ServerStateDoc from './ServerState/server-state.mdx'; -import DemoLink from '../DemoLink'; +import DemoLink from '@/components/DemoLink'; +import ServerStateDoc from './server-state.mdx'; # State Management in React @@ -17,9 +17,7 @@ Here are different types for state management that will be covered in our exampl ### Let's break down the state management code example: - - View Demo - +View Demo diff --git a/src/routes/state-management/preview.lazy.tsx b/src/routes/state-management/preview.lazy.tsx new file mode 100644 index 0000000..d2398f1 --- /dev/null +++ b/src/routes/state-management/preview.lazy.tsx @@ -0,0 +1,6 @@ +import { createLazyFileRoute } from '@tanstack/react-router'; +import ServerStateExample from './_components/ServerStateExample'; + +export const Route = createLazyFileRoute('/state-management/preview')({ + component: ServerStateExample, +}); diff --git a/src/components/state-management/ServerState/server-state.mdx b/src/routes/state-management/server-state.mdx similarity index 81% rename from src/components/state-management/ServerState/server-state.mdx rename to src/routes/state-management/server-state.mdx index fdcb0b0..2adcb3f 100644 --- a/src/components/state-management/ServerState/server-state.mdx +++ b/src/routes/state-management/server-state.mdx @@ -1,9 +1,9 @@ -import ServerStateExample from './ServerStateExample'; +import ServerStateExample from './_components/ServerStateExample'; ```tsx ServerStateExample.tsx focus=24:27 - // from ./ServerStateExample.tsx + // from ./_components/ServerStateExample.tsx ```` #### Initializing local state for filters using useState hook @@ -12,7 +12,7 @@ import ServerStateExample from './ServerStateExample'; --- ```tsx ServerStateExample.tsx focus=63,71:73 - // from ./ServerStateExample.tsx + // from ./_components/ServerStateExample.tsx ```` #### Handle User Input: SearchField & Select @@ -23,7 +23,7 @@ import ServerStateExample from './ServerStateExample'; --- ```tsx ServerStateExample.tsx focus=29:37 - // from ./ServerStateExample.tsx + // from ./_components/ServerStateExample.tsx ```` #### Fetch date from server @@ -32,7 +32,7 @@ import ServerStateExample from './ServerStateExample'; --- ```tsx ServerStateExample.tsx focus=39:51 - // from ./ServerStateExample.tsx + // from ./_components/ServerStateExample.tsx ```` #### Use React Query to Manage Server State @@ -43,7 +43,7 @@ import ServerStateExample from './ServerStateExample'; ```tsx ServerStateExample.tsx focus=53:55 - // from ./ServerStateExample.tsx + // from ./_components/ServerStateExample.tsx ``` #### Error Handling @@ -55,7 +55,7 @@ import ServerStateExample from './ServerStateExample'; ```tsx ServerStateExample.tsx focus=86 - // from ./ServerStateExample.tsx + // from ./_components/ServerStateExample.tsx ``` #### Loading State diff --git a/tests/smoke.test.tsx b/tests/smoke.test.tsx new file mode 100644 index 0000000..ffbd670 --- /dev/null +++ b/tests/smoke.test.tsx @@ -0,0 +1,11 @@ +import { render, screen } from '@testing-library/react'; +import { expect } from 'vitest'; + +test('smoketest', async () => { + const Component = () =>
this is just a test
; + render(); + + const cmp = screen.getByTestId('test'); + + expect(cmp).toBeInTheDocument(); +}); diff --git a/tsconfig.json b/tsconfig.json index cd6ef96..6862fda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,8 +16,11 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "types": ["@testing-library/jest-dom"] + "types": ["@testing-library/jest-dom", "node"], + "paths": { + "@/*": ["./src/*"] + } }, - "include": ["src", "types/mdx.d.ts"], + "include": ["src", "types/mdx.d.ts", "tests"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/tsr.config.json b/tsr.config.json new file mode 100644 index 0000000..cf0f596 --- /dev/null +++ b/tsr.config.json @@ -0,0 +1,5 @@ +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/route-tree.ts", + "routeFileIgnorePrefix": "_components" +} diff --git a/vite.config.ts b/vite.config.ts index f4b565d..e989eb3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,15 +1,21 @@ -import { defineConfig } from 'vite'; +import { UserConfig, defineConfig } from 'vite'; +import path from 'node:path'; + import react from '@vitejs/plugin-react-swc'; import { remarkCodeHike } from '@code-hike/mdx'; import { TanStackRouterVite } from '@tanstack/router-vite-plugin'; -// @ts-ignore export default defineConfig(async () => { const mdx = await import('@mdx-js/rollup'); return { optimizeDeps: { include: ['react/jsx-runtime'], }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, plugins: [ { enforce: 'pre', @@ -22,6 +28,7 @@ export default defineConfig(async () => { react(), TanStackRouterVite(), ], + // @ts-expect-error (extending vite's config with vitest's configuration) test: { globals: true, environment: 'jsdom', @@ -39,5 +46,5 @@ export default defineConfig(async () => { ], }, }, - }; + } satisfies UserConfig; });