From a1b413984995edfdf060a84341663d419a4472f2 Mon Sep 17 00:00:00 2001 From: Eike Foken Date: Fri, 6 Dec 2024 08:10:11 +0100 Subject: [PATCH] Setup docs and add sandbox --- app/package.json | 10 +- app/tsconfig.json | 4 +- biome.json | 1 + docs/.velite/docs.json | 48 + docs/.velite/index.d.ts | 8 + docs/.velite/index.js | 3 + docs/app/docs/[...slug]/page.tsx | 78 + docs/app/docs/layout.tsx | 20 + docs/app/docs/sidebar.tsx | 26 + docs/app/globals.css | 9 + docs/app/layout.tsx | 6 +- docs/app/page.module.css | 9 - docs/app/page.tsx | 20 +- docs/app/styles-provider.tsx | 14 - docs/components/Card.tsx | 14 +- docs/components/MDXContent.tsx | 53 + docs/components/PageHeader.tsx | 45 + docs/components/Toc.tsx | 90 + docs/components/mdx/Code.tsx | 22 + docs/components/mdx/Pre.tsx | 38 + docs/content/getting-started/installation.mdx | 87 + docs/docs.config.ts | 7 + docs/lib/flattenToc.ts | 19 + docs/lib/scrollIntoView.ts | 22 + docs/lib/useScrollSpy.ts | 42 + docs/next.config.mjs | 10 + docs/package.json | 27 +- docs/tsconfig.json | 17 +- docs/velite.config.ts | 82 + package.json | 19 +- packages/components/package.json | 4 +- packages/components/src/Popover/Popover.tsx | 1 + packages/components/src/Show/Show.tsx | 22 + packages/components/src/Show/index.ts | 2 + packages/components/src/index.ts | 1 + packages/core/package.json | 6 +- packages/core/src/sxConfig/defaultSxConfig.ts | 3 +- packages/core/src/sxConfig/typography.ts | 2 + packages/core/src/theme/defaultTheme.ts | 2 + packages/elements/package.json | 2 +- packages/next/package.json | 52 + packages/next/src/AppRouterProvider.tsx | 118 + packages/next/src/index.ts | 1 + packages/svg/package.json | 2 +- sandbox/next-app/README.md | 36 + sandbox/next-app/app/favicon.ico | Bin 0 -> 25931 bytes sandbox/next-app/app/globals.css | 64 + sandbox/next-app/app/layout.tsx | 39 + sandbox/next-app/app/page.tsx | 207 ++ sandbox/next-app/components/Card.tsx | 79 + sandbox/next-app/components/Image.tsx | 26 + sandbox/next-app/next-env.d.ts | 5 + sandbox/next-app/next.config.mjs | 28 + sandbox/next-app/package.json | 25 + sandbox/next-app/public/next.svg | 1 + sandbox/next-app/public/vercel.svg | 1 + sandbox/next-app/tsconfig.json | 25 + tsconfig.json | 4 +- yarn.lock | 2434 +++++++++++++++-- 59 files changed, 3677 insertions(+), 365 deletions(-) create mode 100644 docs/.velite/docs.json create mode 100644 docs/.velite/index.d.ts create mode 100644 docs/.velite/index.js create mode 100644 docs/app/docs/[...slug]/page.tsx create mode 100644 docs/app/docs/layout.tsx create mode 100644 docs/app/docs/sidebar.tsx delete mode 100644 docs/app/page.module.css delete mode 100644 docs/app/styles-provider.tsx create mode 100644 docs/components/MDXContent.tsx create mode 100644 docs/components/PageHeader.tsx create mode 100644 docs/components/Toc.tsx create mode 100644 docs/components/mdx/Code.tsx create mode 100644 docs/components/mdx/Pre.tsx create mode 100644 docs/content/getting-started/installation.mdx create mode 100644 docs/docs.config.ts create mode 100644 docs/lib/flattenToc.ts create mode 100644 docs/lib/scrollIntoView.ts create mode 100644 docs/lib/useScrollSpy.ts create mode 100644 docs/velite.config.ts create mode 100644 packages/components/src/Show/Show.tsx create mode 100644 packages/components/src/Show/index.ts create mode 100644 packages/next/package.json create mode 100644 packages/next/src/AppRouterProvider.tsx create mode 100644 packages/next/src/index.ts create mode 100644 sandbox/next-app/README.md create mode 100644 sandbox/next-app/app/favicon.ico create mode 100644 sandbox/next-app/app/globals.css create mode 100644 sandbox/next-app/app/layout.tsx create mode 100644 sandbox/next-app/app/page.tsx create mode 100644 sandbox/next-app/components/Card.tsx create mode 100644 sandbox/next-app/components/Image.tsx create mode 100644 sandbox/next-app/next-env.d.ts create mode 100644 sandbox/next-app/next.config.mjs create mode 100644 sandbox/next-app/package.json create mode 100644 sandbox/next-app/public/next.svg create mode 100644 sandbox/next-app/public/vercel.svg create mode 100644 sandbox/next-app/tsconfig.json diff --git a/app/package.json b/app/package.json index 7774ae7..8e93bfb 100644 --- a/app/package.json +++ b/app/package.json @@ -7,6 +7,7 @@ "android": "expo run:android", "ios": "expo run:ios", "start": "expo start", + "typecheck": "tsc", "web": "expo start --web" }, "dependencies": { @@ -14,10 +15,10 @@ "@react-universal/components": "workspace:*", "@react-universal/core": "workspace:*", "@react-universal/svg": "workspace:*", - "expo": "^52.0.14", + "expo": "^52.0.17", "expo-dev-client": "~5.0.5", - "expo-screen-orientation": "~8.0.0", - "expo-splash-screen": "~0.29.13", + "expo-screen-orientation": "~8.0.1", + "expo-splash-screen": "~0.29.16", "expo-status-bar": "~2.0.0", "react": "18.3.1", "react-native": "0.76.3", @@ -29,6 +30,7 @@ "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@expo/metro-runtime": "~4.0.0", - "@types/babel__core": "^7.20.5" + "@types/babel__core": "^7.20.5", + "typescript": "~5.7.2" } } diff --git a/app/tsconfig.json b/app/tsconfig.json index af15871..34af871 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -2,7 +2,9 @@ "extends": "expo/tsconfig.base", "compilerOptions": { "customConditions": ["source"], + "jsx": "react-jsx", "moduleResolution": "Bundler", "strict": true - } + }, + "include": ["**/*.ts", "**/*.tsx", "../@types/*.d.ts"] } diff --git a/biome.json b/biome.json index f1cff3d..3ed30ab 100644 --- a/biome.json +++ b/biome.json @@ -39,6 +39,7 @@ "correctness": { "noNewSymbol": "error", "noUndeclaredVariables": "error", + "noUnusedImports": "error", "noUnusedPrivateClassMembers": "error", "noUnusedVariables": "error", "useArrayLiterals": "error", diff --git a/docs/.velite/docs.json b/docs/.velite/docs.json new file mode 100644 index 0000000..75b650a --- /dev/null +++ b/docs/.velite/docs.json @@ -0,0 +1,48 @@ +[ + { + "title": "Installation", + "slug": "getting-started/installation", + "description": "How to install and set up React Universal in your project", + "metadata": { + "readingTime": 1, + "wordCount": 193 + }, + "content": "

Framework Guide

\n

React Universal works in your favorite framework. We've put together step-by-step guides for these\nframeworks.

\n\n Easily add React Universal with Next.js app\n\n\n Use React Universal with Vite\n\n
\n

The minimum Node.js version required is 18.x

\n
\n

Installation

\n

To manually set up React Universal in your project, follow the steps below.

\n

Install @react-universal/core

\n
yarn add @react-universal/core\n
\n

Setup provider

\n

Wrap your application with the Provider component generated in the components/ui/provider\ncomponent at the root of your application.

\n

This provider composes the following:

\n\n
import { Provider } from \"@/components/ui/provider\"\n\nfunction App({ Component, pageProps }) {\n  return (\n    <Provider>\n      <Component {...pageProps} />\n    </Provider>\n  )\n}\n
\n

Update tsconfig

\n

If you're using TypeScript, you need to update the compilerOptions in the tsconfig file to include\nthe following options:

\n
{\n  \"compilerOptions\": {\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n
\n

Enjoy!

\n

With the power of the snippets and the primitive components from React Universal, you can build your\nUI faster.

\n
import { Button, Stack } from \"@react-universal/core\"\n\nfunction Demo() {\n  return (\n    <Stack direction=\"row\">\n      <Button>Click me</Button>\n      <Button>Click me</Button>\n    </Stack>\n  )\n}\n
", + "toc": [ + { + "title": "Framework Guide", + "url": "#framework-guide", + "items": [] + }, + { + "title": "Installation", + "url": "#installation", + "items": [ + { + "title": "Install @react-universal/core", + "url": "#install-react-universalcore", + "items": [] + }, + { + "title": "Setup provider", + "url": "#setup-provider", + "items": [] + }, + { + "title": "Update tsconfig", + "url": "#update-tsconfig", + "items": [] + }, + { + "title": "Enjoy!", + "url": "#enjoy", + "items": [] + } + ] + } + ], + "code": "const{Fragment:e,jsx:l,jsxs:n}=arguments[0];function _createMdxContent(r){const s={blockquote:\"blockquote\",code:\"code\",h2:\"h2\",h3:\"h3\",li:\"li\",p:\"p\",pre:\"pre\",span:\"span\",ul:\"ul\",...r.components},{Card:c}=s;return c||function(e,l){throw new Error(\"Expected \"+(l?\"component\":\"object\")+\" `\"+e+\"` to be defined: you likely forgot to import, pass, or provide it.\")}(\"Card\",!0),n(e,{children:[l(s.h2,{id:\"framework-guide\",children:\"Framework Guide\"}),\"\\n\",l(s.p,{children:\"React Universal works in your favorite framework. We've put together step-by-step guides for these\\nframeworks.\"}),\"\\n\",l(c,{title:\"Next.js\",href:\"/getting-started/frameworks/next-app\",children:l(s.p,{children:\"Easily add React Universal with Next.js app\"})}),\"\\n\",l(c,{title:\"Vite\",href:\"/getting-started/frameworks/vite\",children:l(s.p,{children:\"Use React Universal with Vite\"})}),\"\\n\",n(s.blockquote,{children:[\"\\n\",l(s.p,{children:\"The minimum Node.js version required is 18.x\"}),\"\\n\"]}),\"\\n\",l(s.h2,{id:\"installation\",children:\"Installation\"}),\"\\n\",l(s.p,{children:\"To manually set up React Universal in your project, follow the steps below.\"}),\"\\n\",n(s.h3,{id:\"install-react-universalcore\",children:[\"Install \",l(s.code,{children:\"@react-universal/core\"})]}),\"\\n\",l(s.pre,{className:\"shiki dark-plus\",style:{backgroundColor:\"#1E1E1E\",color:\"#D4D4D4\"},tabIndex:\"0\",children:l(s.code,{children:n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#DCDCAA\"},children:\"yarn\"}),l(s.span,{style:{color:\"#CE9178\"},children:\" add\"}),l(s.span,{style:{color:\"#CE9178\"},children:\" @react-universal/core\"})]})})}),\"\\n\",l(s.h3,{id:\"setup-provider\",children:\"Setup provider\"}),\"\\n\",n(s.p,{children:[\"Wrap your application with the \",l(s.code,{children:\"Provider\"}),\" component generated in the \",l(s.code,{children:\"components/ui/provider\"}),\"\\ncomponent at the root of your application.\"]}),\"\\n\",l(s.p,{children:\"This provider composes the following:\"}),\"\\n\",n(s.ul,{children:[\"\\n\",n(s.li,{children:[l(s.code,{children:\"UniversalProvider\"}),\" from \",l(s.code,{children:\"@react-universal/core\"}),\" for the styling system\"]}),\"\\n\",n(s.li,{children:[l(s.code,{children:\"ThemeProvider\"}),\" from \",l(s.code,{children:\"next-themes\"}),\" for color mode\"]}),\"\\n\"]}),\"\\n\",l(s.pre,{className:\"shiki dark-plus\",style:{backgroundColor:\"#1E1E1E\",color:\"#D4D4D4\"},tabIndex:\"0\",children:n(s.code,{children:[n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#C586C0\"},children:\"import\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\" { \"}),l(s.span,{style:{color:\"#9CDCFE\"},children:\"Provider\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\" } \"}),l(s.span,{style:{color:\"#C586C0\"},children:\"from\"}),l(s.span,{style:{color:\"#CE9178\"},children:' \"@/components/ui/provider\"'})]}),\"\\n\",l(s.span,{className:\"line\"}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#569CD6\"},children:\"function\"}),l(s.span,{style:{color:\"#DCDCAA\"},children:\" App\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\"({ \"}),l(s.span,{style:{color:\"#9CDCFE\"},children:\"Component\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\", \"}),l(s.span,{style:{color:\"#9CDCFE\"},children:\"pageProps\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\" }) {\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#C586C0\"},children:\" return\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\" (\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#808080\"},children:\" <\"}),l(s.span,{style:{color:\"#4EC9B0\"},children:\"Provider\"}),l(s.span,{style:{color:\"#808080\"},children:\">\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#808080\"},children:\" <\"}),l(s.span,{style:{color:\"#4EC9B0\"},children:\"Component\"}),l(s.span,{style:{color:\"#569CD6\"},children:\" {\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\"...\"}),l(s.span,{style:{color:\"#9CDCFE\"},children:\"pageProps\"}),l(s.span,{style:{color:\"#569CD6\"},children:\"}\"}),l(s.span,{style:{color:\"#808080\"},children:\" />\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#808080\"},children:\" \"})]}),\"\\n\",l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\" )\"})}),\"\\n\",l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\"}\"})})]})}),\"\\n\",l(s.h3,{id:\"update-tsconfig\",children:\"Update tsconfig\"}),\"\\n\",l(s.p,{children:\"If you're using TypeScript, you need to update the compilerOptions in the tsconfig file to include\\nthe following options:\"}),\"\\n\",l(s.pre,{className:\"shiki dark-plus\",style:{backgroundColor:\"#1E1E1E\",color:\"#D4D4D4\"},tabIndex:\"0\",children:n(s.code,{children:[l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\"{\"})}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#9CDCFE\"},children:' \"compilerOptions\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\": {\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#9CDCFE\"},children:' \"module\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\": \"}),l(s.span,{style:{color:\"#CE9178\"},children:'\"ESNext\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\",\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#9CDCFE\"},children:' \"moduleResolution\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\": \"}),l(s.span,{style:{color:\"#CE9178\"},children:'\"Bundler\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\",\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#9CDCFE\"},children:' \"skipLibCheck\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\": \"}),l(s.span,{style:{color:\"#569CD6\"},children:\"true\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\",\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#9CDCFE\"},children:' \"paths\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\": {\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#9CDCFE\"},children:' \"@/*\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\": [\"}),l(s.span,{style:{color:\"#CE9178\"},children:'\"./src/*\"'}),l(s.span,{style:{color:\"#D4D4D4\"},children:\"]\"})]}),\"\\n\",l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\" }\"})}),\"\\n\",l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\" }\"})}),\"\\n\",l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\"}\"})})]})}),\"\\n\",l(s.h3,{id:\"enjoy\",children:\"Enjoy!\"}),\"\\n\",l(s.p,{children:\"With the power of the snippets and the primitive components from React Universal, you can build your\\nUI faster.\"}),\"\\n\",l(s.pre,{className:\"shiki dark-plus\",style:{backgroundColor:\"#1E1E1E\",color:\"#D4D4D4\"},tabIndex:\"0\",children:n(s.code,{children:[n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#C586C0\"},children:\"import\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\" { \"}),l(s.span,{style:{color:\"#9CDCFE\"},children:\"Button\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\", \"}),l(s.span,{style:{color:\"#9CDCFE\"},children:\"Stack\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\" } \"}),l(s.span,{style:{color:\"#C586C0\"},children:\"from\"}),l(s.span,{style:{color:\"#CE9178\"},children:' \"@react-universal/core\"'})]}),\"\\n\",l(s.span,{className:\"line\"}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#569CD6\"},children:\"function\"}),l(s.span,{style:{color:\"#DCDCAA\"},children:\" Demo\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\"() {\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#C586C0\"},children:\" return\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\" (\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#808080\"},children:\" <\"}),l(s.span,{style:{color:\"#4EC9B0\"},children:\"Stack\"}),l(s.span,{style:{color:\"#9CDCFE\"},children:\" direction\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\"=\"}),l(s.span,{style:{color:\"#CE9178\"},children:'\"row\"'}),l(s.span,{style:{color:\"#808080\"},children:\">\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#808080\"},children:\" <\"}),l(s.span,{style:{color:\"#4EC9B0\"},children:\"Button\"}),l(s.span,{style:{color:\"#808080\"},children:\">\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\"Click me\"}),l(s.span,{style:{color:\"#808080\"},children:\"\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#808080\"},children:\" <\"}),l(s.span,{style:{color:\"#4EC9B0\"},children:\"Button\"}),l(s.span,{style:{color:\"#808080\"},children:\">\"}),l(s.span,{style:{color:\"#D4D4D4\"},children:\"Click me\"}),l(s.span,{style:{color:\"#808080\"},children:\"\"})]}),\"\\n\",n(s.span,{className:\"line\",children:[l(s.span,{style:{color:\"#808080\"},children:\" \"})]}),\"\\n\",l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\" )\"})}),\"\\n\",l(s.span,{className:\"line\",children:l(s.span,{style:{color:\"#D4D4D4\"},children:\"}\"})})]})})]})}return{default:function(e={}){const{wrapper:n}=e.components||{};return n?l(n,{...e,children:l(_createMdxContent,{...e})}):_createMdxContent(e)}};", + "links": {}, + "category": "getting-started" + } +] diff --git a/docs/.velite/index.d.ts b/docs/.velite/index.d.ts new file mode 100644 index 0000000..963ba68 --- /dev/null +++ b/docs/.velite/index.d.ts @@ -0,0 +1,8 @@ +// This file is generated by Velite + +import type __vc from '../velite.config.ts'; + +type Collections = typeof __vc.collections; + +export type Docs = Collections['docs']['schema']['_output']; +export declare const docs: Docs[]; diff --git a/docs/.velite/index.js b/docs/.velite/index.js new file mode 100644 index 0000000..f31bcf7 --- /dev/null +++ b/docs/.velite/index.js @@ -0,0 +1,3 @@ +// This file is generated by Velite + +export { default as docs } from './docs.json'; diff --git a/docs/app/docs/[...slug]/page.tsx b/docs/app/docs/[...slug]/page.tsx new file mode 100644 index 0000000..503dbc0 --- /dev/null +++ b/docs/app/docs/[...slug]/page.tsx @@ -0,0 +1,78 @@ +import { Show, Stack } from '@react-universal/components'; +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import { docs } from '#/.velite'; +import { MDXContent } from '#/components/MDXContent'; +import { PageHeader } from '#/components/PageHeader'; +import { Toc } from '#/components/Toc'; +import { flattenToc } from '#/lib/flattenToc'; +import { SidebarEnd } from '../sidebar'; + +interface PageProps { + params: Promise<{ slug: string[] }>; +} + +export default async function Page({ params }: PageProps) { + const { slug } = await params; + const page = docs.find((doc) => doc.slug === slug.join('/')); + + if (!page) { + return notFound(); + } + + return ( + <> + + + + + + + + + + + ); +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + const page = docs.find((doc) => doc.slug === slug.join('/')); + + const category = page?.slug + .split('/') + .slice(0, -1) + .join(' > ') + .replace('-', ' ') + .replace(/\b\w/g, (l) => l.toUpperCase()); + + return { + title: page?.title, + description: page?.description, + openGraph: { + images: `/og?title=${page?.title}&category=${category}`, + }, + }; +} + +export function generateStaticParams() { + return docs.map((item) => ({ + slug: item.slug.split('/').slice(1), + })); +} diff --git a/docs/app/docs/layout.tsx b/docs/app/docs/layout.tsx new file mode 100644 index 0000000..3ded9ea --- /dev/null +++ b/docs/app/docs/layout.tsx @@ -0,0 +1,20 @@ +import { Container } from '@react-universal/components'; + +interface LayoutProps { + children: React.ReactNode; +} + +export default function Layout({ children }: LayoutProps) { + return ( + <> + {/*
*/} +
+ + {/* */} + {/* */} + {children} + +
+ + ); +} diff --git a/docs/app/docs/sidebar.tsx b/docs/app/docs/sidebar.tsx new file mode 100644 index 0000000..048e97c --- /dev/null +++ b/docs/app/docs/sidebar.tsx @@ -0,0 +1,26 @@ +import type { BoxProps } from '@react-universal/components'; +import { Stack } from '@react-universal/components'; +import { Aside } from '@react-universal/elements'; + +export const SidebarEnd: React.FC = ({ children, sx, ...props }) => ( + +); diff --git a/docs/app/globals.css b/docs/app/globals.css index 0cc6fdd..fad7944 100644 --- a/docs/app/globals.css +++ b/docs/app/globals.css @@ -1,4 +1,7 @@ :root { + --header-height: 64px; + --content-height: calc(100dvh - var(--header-height)); + --max-width: 1164px; --border-radius: 12px; --font-mono: @@ -26,6 +29,12 @@ --card-border-rgb: 131, 134, 135; } +@media (min-width: 600px) { + :root { + --header-height: 104px; + } +} + @media (prefers-color-scheme: dark) { :root { --foreground-rgb: 255, 255, 255; diff --git a/docs/app/layout.tsx b/docs/app/layout.tsx index 1917c45..660f665 100644 --- a/docs/app/layout.tsx +++ b/docs/app/layout.tsx @@ -2,7 +2,7 @@ import { createTheme } from '@react-universal/core'; import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; -import { StylesProvider } from './styles-provider'; +import { AppRouterProdivder } from '@react-universal/next'; const inter = Inter({ subsets: ['latin'] }); @@ -19,7 +19,7 @@ export default function RootLayout({ return ( - {children} - + ); diff --git a/docs/app/page.module.css b/docs/app/page.module.css deleted file mode 100644 index 41d90a1..0000000 --- a/docs/app/page.module.css +++ /dev/null @@ -1,9 +0,0 @@ -@media (prefers-color-scheme: dark) { - .vercelLogo { - filter: invert(1); - } - - .logo { - filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); - } -} diff --git a/docs/app/page.tsx b/docs/app/page.tsx index 4dbd639..fc3a649 100644 --- a/docs/app/page.tsx +++ b/docs/app/page.tsx @@ -184,23 +184,27 @@ export default function Home() { + > + Find in-depth information about Next.js features and API. + + > + Learn about Next.js in an interactive course with quizzes! + + > + Explore starter templates for Next.js. + + > + Instantly deploy your Next.js site to a shareable URL with Vercel. + ); diff --git a/docs/app/styles-provider.tsx b/docs/app/styles-provider.tsx deleted file mode 100644 index 08271f6..0000000 --- a/docs/app/styles-provider.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client'; - -import type { Theme } from '@react-universal/core'; -import { ThemeProvider } from '@react-universal/core'; - -export function StylesProvider({ - children, - theme, -}: Readonly<{ - children: React.ReactNode; - theme?: Theme; -}>) { - return {children}; -} diff --git a/docs/components/Card.tsx b/docs/components/Card.tsx index c0699cc..c62f0aa 100644 --- a/docs/components/Card.tsx +++ b/docs/components/Card.tsx @@ -2,17 +2,17 @@ import type { ButtonMethods } from '@react-universal/components'; import { Button } from '@react-universal/components'; -import { H2, P, Span } from '@react-universal/elements'; +import { Div, H2, Span } from '@react-universal/elements'; import { forwardRef, useCallback, useState } from 'react'; -export interface CardProps { - description?: string; +interface CardProps { + children?: string; href?: string; title?: string; } export const Card = forwardRef( - ({ description, href, title }, ref) => { + ({ children, href, title }, ref) => { const [hovered, setHovered] = useState(false); const handleHoverIn = useCallback(() => setHovered(true), []); @@ -62,7 +62,7 @@ export const Card = forwardRef( -> -

( textWrap: 'balance', }} > - {description} -

+ {children} + ); }, diff --git a/docs/components/MDXContent.tsx b/docs/components/MDXContent.tsx new file mode 100644 index 0000000..e22553e --- /dev/null +++ b/docs/components/MDXContent.tsx @@ -0,0 +1,53 @@ +import { A, H1, H2, H3, H4, Img, P, Strong } from '@react-universal/elements'; +import type { AnyObject } from '@react-universal/utils'; +import * as runtime from 'react/jsx-runtime'; +import { Card } from './Card'; +import { Code } from './mdx/Code'; +import { Pre } from './mdx/Pre'; + +const sharedComponents = { + a: A, + // blockquote: Blockquote, + img: Img, + p: P, + strong: Strong, + h1: H1, + h2: H2, + h3: H3, + h4: H4, + // kbd: Kbd, + pre: Pre, + code: Code, + // ol: Ol, + // ul: Ul, + // li: Li, + // table: Table, + // steps: Steps, + // callout: Callout, + // "code-group": CodeGroup, + // card: Card, + // "card-group": CardGroup, + Card, + // hr: Hr, + // "code-block": CodeBlock, +}; + +function useMDXComponent(code: string) { + const fn = new Function(code); + return fn({ ...runtime }).default; +} + +interface MDXProps { + code: string; + components?: AnyObject; +} + +export const MDXContent: React.FC = ({ code, components = {} }) => { + const Component = useMDXComponent(code); + + return ( +
+ +
+ ); +}; diff --git a/docs/components/PageHeader.tsx b/docs/components/PageHeader.tsx new file mode 100644 index 0000000..174d3f1 --- /dev/null +++ b/docs/components/PageHeader.tsx @@ -0,0 +1,45 @@ +import { Stack, Text } from '@react-universal/components'; +import { A, H1, Span } from '@react-universal/elements'; +import { LuArrowUpRight } from 'react-icons/lu'; +import { titleCase } from 'scule'; + +interface PageHeaderProps { + title: string; + description: string; + links?: { + source?: string; + storybook?: string; + recipe?: string; + ark?: string; + }; +} + +export const PageHeader: React.FC = ({ title, description, links }) => ( + +

{title}

+ {description} + {links && ( + + {Object.entries(links).map(([title, url]) => ( + + {/* */} + {titleCase(title)} + + + + + ))} + + )} +
+); diff --git a/docs/components/Toc.tsx b/docs/components/Toc.tsx new file mode 100644 index 0000000..21030ab --- /dev/null +++ b/docs/components/Toc.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { Stack, Text } from '@react-universal/components'; +import { css, styled } from '@react-universal/core'; +import type { SxProps } from '@react-universal/core'; +import { Nav } from '@react-universal/elements'; +import Link from 'next/link'; +import type { LinkProps } from 'next/link'; +import { useEffect } from 'react'; +import { scrollIntoView } from '../lib/scrollIntoView'; +import { useScrollSpy } from '../lib/useScrollSpy'; + +interface TocItem { + title: React.ReactNode; + url: string; + depth: number; +} + +interface TocProps { + items: TocItem[]; +} + +const TocLink = styled( + ({ + className, + style, + ...props + }: { + children?: React.ReactNode; + className?: string; + id?: string; + style?: React.CSSProperties; + sx?: SxProps; + } & LinkProps) => , + { + name: 'Toc', + slot: 'Link', + }, +)(({ theme }) => ({ + color: theme.colors.text.muted, + fontSize: '0.875rem', + marginInlineStart: 'calc(1rem * var(--toc-depth))', + '&[aria-current="page"]': { + color: theme.colors.text.default, + fontWeight: 700, + }, + '&:hover': { + color: theme.colors.text.default, + }, +})); + +export const Toc: React.FC = ({ items }) => { + const activeItem = useScrollSpy(items.map((entry) => entry.url)); + + // biome-ignore lint/correctness/useExhaustiveDependencies: + useEffect(() => { + const activeLink = document.querySelector('[data-toc][aria-current="page"]'); + const toc = document.getElementById('toc'); + if (toc && activeLink) { + scrollIntoView(toc, activeLink, 120); + } + }, [activeItem]); + + if (items.length === 0) { + return
; + } + + return ( + + ); +}; diff --git a/docs/components/mdx/Code.tsx b/docs/components/mdx/Code.tsx new file mode 100644 index 0000000..0f1f38d --- /dev/null +++ b/docs/components/mdx/Code.tsx @@ -0,0 +1,22 @@ +import type { TextProps } from '@react-universal/components'; +import { Code as CodeRoot } from '@react-universal/elements'; + +export const Code: React.FC = ({ sx, ...props }) => { + return ( + + ); +}; diff --git a/docs/components/mdx/Pre.tsx b/docs/components/mdx/Pre.tsx new file mode 100644 index 0000000..0805f1b --- /dev/null +++ b/docs/components/mdx/Pre.tsx @@ -0,0 +1,38 @@ +import { Box } from '@react-universal/components'; +import type { BoxProps } from '@react-universal/components'; +import type { Code } from '@react-universal/elements'; +import { cloneElement, isValidElement } from 'react'; + +export const Pre: React.FC = ({ children, sx, ...props }) => { + return ( + + {isValidElement>(children) + ? cloneElement(children, { + sx: { + bgColor: 'transparent', + borderWidth: 'inherit' as any, + fontSize: 'inherit', + letterSpacing: 'inherit' as any, + p: 0, + }, + }) + : children} + + ); +}; diff --git a/docs/content/getting-started/installation.mdx b/docs/content/getting-started/installation.mdx new file mode 100644 index 0000000..04e1818 --- /dev/null +++ b/docs/content/getting-started/installation.mdx @@ -0,0 +1,87 @@ +--- +title: Installation +description: How to install and set up React Universal in your project +--- + +## Framework Guide + +React Universal works in your favorite framework. We've put together step-by-step guides for these +frameworks. + + + Easily add React Universal with Next.js app + + + + Use React Universal with Vite + + +> The minimum Node.js version required is 18.x + +## Installation + +To manually set up React Universal in your project, follow the steps below. + +### Install `@react-universal/core` + +```bash +yarn add @react-universal/core +``` + +### Setup provider + +Wrap your application with the `Provider` component generated in the `components/ui/provider` +component at the root of your application. + +This provider composes the following: + +- `UniversalProvider` from `@react-universal/core` for the styling system +- `ThemeProvider` from `next-themes` for color mode + +```tsx +import { Provider } from "@/components/ui/provider" + +function App({ Component, pageProps }) { + return ( + + + + ) +} +``` + +### Update tsconfig + +If you're using TypeScript, you need to update the compilerOptions in the tsconfig file to include +the following options: + +```json +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +### Enjoy! + +With the power of the snippets and the primitive components from React Universal, you can build your +UI faster. + +```tsx +import { Button, Stack } from "@react-universal/core" + +function Demo() { + return ( + + + + + ) +} +``` diff --git a/docs/docs.config.ts b/docs/docs.config.ts new file mode 100644 index 0000000..78a9254 --- /dev/null +++ b/docs/docs.config.ts @@ -0,0 +1,7 @@ +const config = { + repoBranch: 'main', + repoUrl: 'https://github.com/efoken/react-universal', + storybookUrl: '', +}; + +export default config; diff --git a/docs/lib/flattenToc.ts b/docs/lib/flattenToc.ts new file mode 100644 index 0000000..98f70f7 --- /dev/null +++ b/docs/lib/flattenToc.ts @@ -0,0 +1,19 @@ +interface TocEntry { + title: string; + url: string; + items?: TocEntry[]; +} + +interface FlattenedTocEntry { + title: string; + url: string; + depth: number; +} + +export function flattenToc(entries: TocEntry[] = [], depth = 0): FlattenedTocEntry[] { + return entries.reduce( + (acc, entry) => + acc.concat({ title: entry.title, url: entry.url, depth }, flattenToc(entry.items, depth + 1)), + [], + ); +} diff --git a/docs/lib/scrollIntoView.ts b/docs/lib/scrollIntoView.ts new file mode 100644 index 0000000..3d0fe43 --- /dev/null +++ b/docs/lib/scrollIntoView.ts @@ -0,0 +1,22 @@ +export function scrollIntoView(container: HTMLElement, selected: HTMLElement, scrollPadding = 0) { + if (!selected) { + container.scrollTop = 0; + return; + } + const offsetParents: HTMLElement[] = []; + let pointer = selected.offsetParent; + while (pointer != null && container !== pointer && container.contains(pointer)) { + offsetParents.push(pointer as HTMLElement); + pointer = (pointer as HTMLElement).offsetParent; + } + const top = selected.offsetTop + offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0); + const bottom = top + selected.offsetHeight; + const viewRectTop = container.scrollTop + scrollPadding; + const viewRectBottom = container.scrollTop + container.clientHeight - scrollPadding; + + if (top < viewRectTop) { + container.scrollTop = top - scrollPadding; + } else if (bottom > viewRectBottom) { + container.scrollTop = bottom - container.clientHeight + scrollPadding; + } +} diff --git a/docs/lib/useScrollSpy.ts b/docs/lib/useScrollSpy.ts new file mode 100644 index 0000000..b5f29c1 --- /dev/null +++ b/docs/lib/useScrollSpy.ts @@ -0,0 +1,42 @@ +import { useEffect, useRef, useState } from 'react'; + +export function useScrollSpy(selectors: string[]) { + const [activeId, setActiveId] = useState(selectors[0]); + const [previousId, setPreviousId] = useState(); + const observer = useRef(null); + + useEffect(() => { + const elements = selectors.map((selector) => + document.querySelector(`[id='${selector.replace('#', '')}']`), + ); + + observer.current = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + const id = `#${entry.target.getAttribute('id')}`; + if (entry?.isIntersecting) { + setPreviousId(activeId); + setActiveId(id); + } else { + if (id === previousId) { + setPreviousId(null); + } + if (activeId === id && previousId) { + setActiveId(previousId); + } + } + } + }, + { rootMargin: '-30% 0px' }, + ); + + for (const element of elements) { + if (element) { + observer.current?.observe(element); + } + } + return () => observer.current?.disconnect(); + }, [selectors, previousId, activeId]); + + return activeId; +} diff --git a/docs/next.config.mjs b/docs/next.config.mjs index fe09896..1cb7ce3 100644 --- a/docs/next.config.mjs +++ b/docs/next.config.mjs @@ -1,3 +1,5 @@ +import { build } from 'velite'; + /** @type {import('next').NextConfig} */ const nextConfig = { eslint: { @@ -15,6 +17,7 @@ const nextConfig = { plugins: [ ...config.plugins, new (class { + static started = false; apply(compiler) { compiler.hooks.afterEnvironment.tap('@react-universal/webpack-plugin', () => { compiler.options.resolve.conditionNames = [ @@ -22,6 +25,13 @@ const nextConfig = { 'source', ]; }); + compiler.hooks.beforeCompile.tapPromise('@react-universal/webpack-plugin', async () => { + if (!this.started) { + this.started = true; + const dev = compiler.options.mode === 'development'; + await build({ watch: dev, clean: !dev }); + } + }); } })(), ], diff --git a/docs/package.json b/docs/package.json index a1da39f..a350118 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,26 +1,35 @@ { "name": "@react-universal/docs", "version": "1.0.0", + "type": "module", "private": true, "scripts": { "build": "next build", "dev": "next dev", "lint": "next lint", - "start": "next start" + "start": "next start", + "typecheck": "tsc" }, "dependencies": { "@react-universal/components": "workspace:*", "@react-universal/core": "workspace:*", "@react-universal/elements": "workspace:*", - "next": "^15.0.3", + "@react-universal/next": "workspace:*", + "@shikijs/rehype": "^1.24.0", + "@types/node": "^22.10.1", + "@types/react": "~18.3.14", + "@types/react-dom": "~18.3.2", + "next": "^15.0.4", "react": "18.3.1", "react-dom": "18.3.1", - "react-native": "0.76.3" - }, - "devDependencies": { - "@types/node": "^22.10.1", - "@types/react": "~18.3.12", - "@types/react-dom": "~18.3.1", - "typescript": "~5.7.2" + "react-icons": "^5.4.0", + "react-native": "0.76.3", + "rehype-slug": "^6.0.0", + "remark-directive": "^3.0.0", + "remark-gfm": "^4.0.0", + "scule": "^1.3.0", + "typescript": "~5.7.2", + "unified": "^11.0.5", + "velite": "^0.2.1" } } diff --git a/docs/tsconfig.json b/docs/tsconfig.json index fa7c119..6e874bd 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowJs": true, + "baseUrl": ".", "customConditions": ["source"], "esModuleInterop": true, "incremental": true, @@ -10,6 +11,10 @@ "module": "ESNext", "moduleResolution": "Bundler", "noEmit": true, + "noUnusedLocals": true, + "paths": { + "#/*": ["./*"] + }, "plugins": [ { "name": "next" @@ -18,8 +23,16 @@ "resolveJsonModule": true, "skipLibCheck": true, "strict": true, - "target": "ES2022" + "target": "ES2022", + "verbatimModuleSyntax": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".velite/**/*", + "../@types/*.d.ts" + ], "exclude": ["node_modules"] } diff --git a/docs/velite.config.ts b/docs/velite.config.ts new file mode 100644 index 0000000..8b8611e --- /dev/null +++ b/docs/velite.config.ts @@ -0,0 +1,82 @@ +import rehypeShiki from '@shikijs/rehype'; +import rehypeSlug from 'rehype-slug'; +import remarkDirective from 'remark-directive'; +import remarkGfm from 'remark-gfm'; +import { defineCollection, defineConfig, s } from 'velite'; +import docsConfig from './docs.config'; + +const docs = defineCollection({ + name: 'Docs', + pattern: ['content/**/*.mdx'], + schema: s + .object({ + title: s.string(), + slug: s.path(), + description: s.string(), + metadata: s.metadata(), + content: s.markdown(), + toc: s.toc(), + code: s.mdx(), + hideToc: s.boolean().optional(), + links: s + .object({ + source: s.string().optional(), + storybook: s.string().optional(), + }) + .optional(), + }) + .transform((data, { meta }) => ({ + ...data, + slug: data.slug.replace(/^content\//, ''), + links: { + ...data.links, + source: data.links?.source + ? `${docsConfig.repoUrl}/tree/${docsConfig.repoBranch}/packages/react/src/${data.links.source}` + : undefined, + storybook: data.links?.storybook + ? `${docsConfig.storybookUrl}/?path=/story/${data.links.storybook}` + : undefined, + }, + category: meta.path + .replace(/.*\/content\//, '') + .replace(/\/[^/]*$/, '') + .replace(process.cwd(), ''), + })), +}); + +export default defineConfig({ + root: process.cwd(), + collections: { + docs, + }, + mdx: { + remarkPlugins: [remarkDirective, remarkGfm], + rehypePlugins: [ + rehypeSlug, + [rehypeShiki, { theme: 'dark-plus' }], + // [ + // rehypeShiki, + // { + // transformers: [ + // transformerNotationDiff(), + // transformerNotationFocus(), + // transformerNotationHighlight(), + // transformerNotationWordHighlight(), + // transformerMetaHighlight(), + // transformerMetaWordHighlight(), + // ], + // theme: 'dark-plus', + // }, + // ], + // [ + // rehypeAutolinkHeadings, + // { + // behavior: 'wrap', + // properties: { + // className: ['subheading-anchor'], + // }, + // }, + // ], + ], + }, +}); diff --git a/package.json b/package.json index ec8687d..ef96c8b 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,31 @@ { "name": "react-universal", "private": true, - "workspaces": ["app", "docs", "packages/*", "tools/*"], + "workspaces": ["app", "docs", "packages/*", "sandbox/*"], "scripts": { "build": "yarn workspaces foreach -Apt --no-private run build", "clean": "yarn workspaces foreach -Ap exec run -T rimraf dist", "format": "biome format --write .", "lint": "biome lint .", + "postinstall": "manypkg check", "storybook": "yarn workspace @react-universal/vite-plugin build && sb dev", - "test": "vitest" + "test": "vitest", + "typecheck": "yarn tsc && yarn workspaces foreach -Apt run typecheck" }, "resolutions": { "vite": "^5.4.11" }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@manypkg/cli": "^0.23.0", "@react-native/normalize-colors": "0.76.3", - "@storybook/addon-a11y": "^8.4.6", - "@storybook/addon-essentials": "^8.4.6", - "@storybook/react-vite": "^8.4.6", + "@storybook/addon-a11y": "^8.4.7", + "@storybook/addon-essentials": "^8.4.7", + "@storybook/react-vite": "^8.4.7", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.0.1", - "@types/react": "~18.3.12", + "@testing-library/react": "^16.1.0", + "@types/react": "~18.3.14", "@vitest/browser": "^2.1.8", "jsdom": "^25.0.1", "lefthook": "^1.8.5", @@ -33,7 +36,7 @@ "react-native-unistyles": "^2.20.0", "react-test-renderer": "18.3.1", "rimraf": "^6.0.1", - "storybook": "^8.4.6", + "storybook": "^8.4.7", "tsup": "^8.3.5", "typescript": "~5.7.2", "vite": "^5.4.11", diff --git a/packages/components/package.json b/packages/components/package.json index 1b7d429..acceda0 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -40,10 +40,10 @@ "@floating-ui/dom": "^1.6.12", "@floating-ui/react-native": "^0.10.7", "@react-universal/core": "workspace:*", - "@tamagui/compose-refs": "^1.118.1" + "@tamagui/compose-refs": "^1.119.0" }, "devDependencies": { - "@types/react": "~18.3.12", + "@types/react": "~18.3.14", "react": "18.3.1", "react-native": "0.76.3" }, diff --git a/packages/components/src/Popover/Popover.tsx b/packages/components/src/Popover/Popover.tsx index 63a39e7..b0ba7a3 100644 --- a/packages/components/src/Popover/Popover.tsx +++ b/packages/components/src/Popover/Popover.tsx @@ -109,6 +109,7 @@ export const Popover = forwardRef const supportedProps: ForwardedProps = pickProps(props); supportedProps.dir = componentDirection; + // @ts-expect-error: `popover` is missing in React types supportedProps.popover = 'manual'; supportedProps.role = role; diff --git a/packages/components/src/Show/Show.tsx b/packages/components/src/Show/Show.tsx new file mode 100644 index 0000000..6328b87 --- /dev/null +++ b/packages/components/src/Show/Show.tsx @@ -0,0 +1,22 @@ +import { runIfFunction } from '@react-universal/utils'; +import { isValidElement } from 'react'; + +export interface ShowProps { + /** + * The children to render if `when` is `true` + */ + children: React.ReactNode | ((props: T) => React.ReactNode); + /** + * The fallback content to render if `when` is `false` + */ + fallback?: React.ReactNode; + /** + * If `true`, it'll render the `children` prop + */ + when: T | null | undefined; +} + +export const Show = ({ children, fallback, when }: ShowProps): React.ReactNode => { + const result = when ? runIfFunction(children, when) : fallback; + return isValidElement(result) ? result : <>{result}; +}; diff --git a/packages/components/src/Show/index.ts b/packages/components/src/Show/index.ts new file mode 100644 index 0000000..f4c79b9 --- /dev/null +++ b/packages/components/src/Show/index.ts @@ -0,0 +1,2 @@ +export { Show } from './Show'; +export type { ShowProps } from './Show'; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 6db54b3..b8dec29 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -7,6 +7,7 @@ export * from './Link'; export * from './Modal'; export * from './Popover'; export * from './ScrollView'; +export * from './Show'; export * from './Stack'; export * from './Text'; export * from './TextInput'; diff --git a/packages/core/package.json b/packages/core/package.json index 2c30169..33e3b9d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,11 +32,11 @@ "@emotion/utils": "^1.4.2", "@react-native/normalize-colors": "0.76.3", "@react-universal/utils": "workspace:*", - "@tamagui/constants": "^1.118.1", - "@tamagui/react-native-use-responder-events": "^1.118.1" + "@tamagui/constants": "^1.119.0", + "@tamagui/react-native-use-responder-events": "^1.119.0" }, "devDependencies": { - "@types/react": "~18.3.12", + "@types/react": "~18.3.14", "react": "18.3.1", "react-native": "0.76.3", "react-native-unistyles": "^2.20.0" diff --git a/packages/core/src/sxConfig/defaultSxConfig.ts b/packages/core/src/sxConfig/defaultSxConfig.ts index a8ad0fe..06bfb72 100644 --- a/packages/core/src/sxConfig/defaultSxConfig.ts +++ b/packages/core/src/sxConfig/defaultSxConfig.ts @@ -13,7 +13,7 @@ import type { MarginProps, PaddingProps } from './spacing'; import { margin, padding } from './spacing'; import type { TypographyProps } from './typography'; -type CSSCustomProps = Record<`--${string}`, string>; +type CSSCustomProps = Record<`--${string}`, number | string>; interface OtherProps extends CSSCustomProps { backdropFilter?: BreakpointValue; @@ -263,6 +263,7 @@ export const defaultSxConfig: SxConfig = { textAlign: {}, textTransform: {}, textWrap: {}, + whiteSpace: {}, typography: { cssProperty: false, themeKey: 'typography', diff --git a/packages/core/src/sxConfig/typography.ts b/packages/core/src/sxConfig/typography.ts index 1f3bf7c..8159dfb 100644 --- a/packages/core/src/sxConfig/typography.ts +++ b/packages/core/src/sxConfig/typography.ts @@ -9,6 +9,8 @@ type TypographyProp = Breakpoi export interface TypographyPropsWeb { /** @platform web */ textWrap?: TypographyProp; + /** @platform web */ + whiteSpace?: TypographyProp; } export interface TypographyProps extends TypographyPropsWeb { diff --git a/packages/core/src/theme/defaultTheme.ts b/packages/core/src/theme/defaultTheme.ts index 43da59f..c9dec2b 100644 --- a/packages/core/src/theme/defaultTheme.ts +++ b/packages/core/src/theme/defaultTheme.ts @@ -15,9 +15,11 @@ export const defaultTheme = { white: '#ffffff', background: { default: '#ffffff', + muted: '#ffffff', }, text: { default: '#09090b', + muted: '#52525b', }, border: { default: '#e4e4e7', diff --git a/packages/elements/package.json b/packages/elements/package.json index 7819d2f..2c483d8 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -29,7 +29,7 @@ "@react-universal/core": "workspace:*" }, "devDependencies": { - "@types/react": "~18.3.12", + "@types/react": "~18.3.14", "react": "18.3.1", "react-native": "0.76.3" }, diff --git a/packages/next/package.json b/packages/next/package.json new file mode 100644 index 0000000..167d735 --- /dev/null +++ b/packages/next/package.json @@ -0,0 +1,52 @@ +{ + "name": "@react-universal/next", + "version": "1.0.0", + "license": "MIT", + "exports": { + ".": { + "source": "./src/index.ts", + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./*": { + "source": "./src/*.ts", + "import": { + "types": "./dist/*.d.mts", + "default": "./dist/*.mjs" + }, + "require": { + "types": "./dist/*.d.ts", + "default": "./dist/*.js" + } + }, + "./package.json": "./package.json" + }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsup src/index.ts" + }, + "dependencies": { + "@emotion/cache": "^11.13.5", + "@emotion/react": "^11.13.5", + "@react-universal/core": "workspace:*", + "@react-universal/utils": "workspace:*" + }, + "devDependencies": { + "next": "^15.0.4", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "peerDependencies": { + "next": "^14.0.0 || ^15.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } +} diff --git a/packages/next/src/AppRouterProvider.tsx b/packages/next/src/AppRouterProvider.tsx new file mode 100644 index 0000000..7593a0c --- /dev/null +++ b/packages/next/src/AppRouterProvider.tsx @@ -0,0 +1,118 @@ +'use client'; + +import createCache from '@emotion/cache'; +import type { Options } from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; +import type { Theme } from '@react-universal/core'; +import { ThemeProvider } from '@react-universal/core'; +import { isString } from '@react-universal/utils'; +import { useServerInsertedHTML } from 'next/navigation'; +import { useState } from 'react'; + +export interface AppRouterProdivderProps { + children: React.ReactNode; + /** + * These are the options passed to `createCache()` from + * `import createCache from '@emotion/cache'`. + */ + options?: Partial & { + /** + * If `true`, the generated styles are wrapped within `@layer 🌌`. This is + * useful if you want to override the React Universal's generated styles + * with a different styling solution, like Tailwind CSS, plain CSS etc. + */ + enableCssLayer?: boolean; + }; + theme?: Theme; +} + +export const AppRouterProdivder: React.FC = ({ + children, + options, + theme, +}) => { + const [registry] = useState(() => { + const cache = createCache({ ...options, key: options?.key ?? 'u' }); + cache.compat = true; + + const _insert = cache.insert; + let inserted: { name: string; global: boolean }[] = []; + + cache.insert = (...args) => { + if (options?.enableCssLayer) { + args[1].styles = `@layer 🌌 {${args[1].styles}}`; + } + const [selector, serialized] = args; + if (cache.inserted[serialized.name] === undefined) { + inserted.push({ + name: serialized.name, + global: !selector, + }); + } + return _insert(...args); + }; + + const flush = () => { + const prevInserted = inserted; + inserted = []; + return prevInserted; + }; + + return { cache, flush }; + }); + + useServerInsertedHTML(() => { + const inserted = registry.flush(); + if (inserted.length === 0) { + return null; + } + let styles = ''; + let dataEmotion = registry.cache.key; + + const globals: { + name: string; + style: string; + }[] = []; + + for (const { name, global } of inserted) { + const style = registry.cache.inserted[name]; + + if (isString(style)) { + if (global) { + globals.push({ name, style }); + } else { + styles += style; + dataEmotion += ` ${name}`; + } + } + } + + return ( + <> + {globals.map(({ name, style }) => ( +