diff --git a/.changeset/config.json b/.changeset/config.json index de0b181715..ca32e1a413 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,5 +11,5 @@ "useCalculatedVersion": true, "prereleaseTemplate": "rc.{timestamp}" }, - "ignore": ["@repo/ssr-testing", "@repo/storybook"] + "ignore": ["@repo/*"] } diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml new file mode 100644 index 0000000000..25871e4cbe --- /dev/null +++ b/.github/workflows/build-apps.yml @@ -0,0 +1,35 @@ +name: Build Test Apps + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-apps: + name: Build Test Apps + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run build + run: pnpm build:apps diff --git a/apps/ssr-testing/README.md b/apps/ssr-testing/README.md deleted file mode 100644 index 2464eab64e..0000000000 --- a/apps/ssr-testing/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# `@repo/ssr-testing` - -This is a testing playground for SSR support in our primitives using a [Next.js](https://nextjs.org/) project. - -## Getting Started - -From the root of the repo, run the development server: - -```sh -pnpm dev:ssr -``` diff --git a/apps/ssr-testing/app/layout.tsx b/apps/ssr-testing/app/layout.tsx deleted file mode 100644 index d197088b3e..0000000000 --- a/apps/ssr-testing/app/layout.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from 'react'; -import type { Metadata } from 'next'; -import Link from 'next/link'; - -export default function Layout({ children }: { children: React.ReactNode }) { - return ( - - - -

SSR / RSC testing

-
-
- AccessibleIcon - Accordion - AlertDialog - Avatar - Checkbox - Collapsible - ContextMenu - Dialog - DropdownMenu - Form - HoverCard - Label - Menubar - NavigationMenu - OneTimePasswordField - PasswordToggleField - Popover - Portal - Progress - RadioGroup - RovingFocusGroup - ScrollArea - Select - Separator - Slider - Slot - Switch - Tabs - Toast - ToggleGroup - Toolbar - Tooltip - VisuallyHidden -
- -
{children}
-
- - -
- ); -} - -export const metadata: Metadata = { - title: 'SSR testing', -}; diff --git a/apps/ssr-testing/app/page.tsx b/apps/ssr-testing/app/page.tsx deleted file mode 100644 index 67e0859135..0000000000 --- a/apps/ssr-testing/app/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return null; -} diff --git a/apps/ssr-testing/app/portal/conditional-portal.tsx b/apps/ssr-testing/app/portal/conditional-portal.tsx deleted file mode 100644 index 7c7190adb6..0000000000 --- a/apps/ssr-testing/app/portal/conditional-portal.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client'; - -import * as React from 'react'; -import { Portal } from 'radix-ui'; - -export const ConditionalPortal = () => { - const [container, setContainer] = React.useState(null); - const [open, setOpen] = React.useState(false); - return ( -
- - - {open && ( - - This content is rendered in a custom container - - )} -
- ); -}; diff --git a/apps/ssr-testing/app/portal/custom-portal-container.tsx b/apps/ssr-testing/app/portal/custom-portal-container.tsx deleted file mode 100644 index 7645c2fbda..0000000000 --- a/apps/ssr-testing/app/portal/custom-portal-container.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import * as React from 'react'; -import { Portal } from 'radix-ui'; - -export const CustomPortalContainer = () => { - const [container, setContainer] = React.useState(null); - return ( -
- - - This content is rendered in a custom container - -
- ); -}; diff --git a/apps/ssr-testing/app/portal/page.tsx b/apps/ssr-testing/app/portal/page.tsx deleted file mode 100644 index 2aa83b84d5..0000000000 --- a/apps/ssr-testing/app/portal/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from 'react'; -import { Portal } from 'radix-ui'; -import { CustomPortalContainer } from './custom-portal-container'; -import { ConditionalPortal } from './conditional-portal'; - -export default function Page() { - return ( -
-
-

This content is rendered in the main DOM tree

-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos porro, est ex quia itaque - facere fugit necessitatibus aut enim. Nisi rerum quae, repellat in perspiciatis explicabo - laboriosam necessitatibus eius pariatur. -

- - -

This content is rendered in a portal (another DOM tree)

-

- Because of the portal, it can appear in a different DOM tree from the main one (by - default a new element inside the body), even though it is part of the same React tree. -

-
-
- -
- - -
- -
- ); -} diff --git a/apps/ssr-testing/app/roving-focus-group/page.tsx b/apps/ssr-testing/app/roving-focus-group/page.tsx deleted file mode 100644 index ab491b6789..0000000000 --- a/apps/ssr-testing/app/roving-focus-group/page.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import * as React from 'react'; -import { RovingFocusProvider, RovingFocusToggle, ButtonGroup, Button } from './roving-focus.client'; - -export default function Page() { - return ( - <> -

Basic

- -
- -
- -

no orientation (both) + no looping

- - - - - - - - -

no orientation (both) + looping

- - - - - - - - -

horizontal orientation + no looping

- - - - - - - - -

horizontal orientation + looping

- - - - - - - - -

vertical orientation + no looping

- - - - - - - - -

vertical orientation + looping

- - - - - - - -
- -

Nested

- - - -
- - - - - - - - -
- - - -
- - ); -} diff --git a/apps/ssr-testing/app/roving-focus-group/roving-focus.client.tsx b/apps/ssr-testing/app/roving-focus-group/roving-focus.client.tsx deleted file mode 100644 index 2112a7d261..0000000000 --- a/apps/ssr-testing/app/roving-focus-group/roving-focus.client.tsx +++ /dev/null @@ -1,95 +0,0 @@ -'use client'; -import * as React from 'react'; -import { composeEventHandlers, RovingFocus } from 'radix-ui/internal'; - -type Direction = 'ltr' | 'rtl'; - -const RovingFocusContext = React.createContext<{ - dir: 'ltr' | 'rtl'; - setDir: React.Dispatch>; -}>({ - dir: 'ltr', - setDir: () => void 0, -}); -RovingFocusContext.displayName = 'RovingFocusContext'; - -export function RovingFocusProvider({ children }: { children: React.ReactNode }) { - const [dir, setDir] = React.useState('ltr'); - return ( -
- {children} -
- ); -} - -export function RovingFocusToggle() { - const { dir, setDir } = React.use(RovingFocusContext); - return ( - - ); -} - -const ButtonGroupContext = React.createContext<{ - value?: string; - setValue: React.Dispatch>; -}>({} as any); - -type ButtonGroupProps = Omit, 'defaultValue'> & - RovingFocus.RovingFocusGroupProps & { defaultValue?: string }; - -export function ButtonGroup({ defaultValue, ...props }: ButtonGroupProps) { - const [value, setValue] = React.useState(defaultValue); - const { dir } = React.use(RovingFocusContext); - return ( - - - - ); -} - -type ButtonProps = Omit, 'value'> & { value?: string }; - -export function Button(props: ButtonProps) { - const { value: contextValue, setValue } = React.use(ButtonGroupContext); - const isSelected = - contextValue !== undefined && props.value !== undefined && contextValue === props.value; - - return ( - - +
+ + + ); +} diff --git a/apps/test-next/app/dismissable-layer/page.tsx b/apps/test-next/app/dismissable-layer/page.tsx new file mode 100644 index 0000000000..cffa1af518 --- /dev/null +++ b/apps/test-next/app/dismissable-layer/page.tsx @@ -0,0 +1,5 @@ +import * as DismissableLayer from '@repo/test-registry/components/dismissable-layer'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/dropdown-menu/page.tsx b/apps/test-next/app/dropdown-menu/page.tsx new file mode 100644 index 0000000000..d221b4aa83 --- /dev/null +++ b/apps/test-next/app/dropdown-menu/page.tsx @@ -0,0 +1,5 @@ +import * as DropdownMenu from '@repo/test-registry/components/dropdown-menu'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/favicon.ico b/apps/test-next/app/favicon.ico new file mode 100644 index 0000000000..718d6fea48 Binary files /dev/null and b/apps/test-next/app/favicon.ico differ diff --git a/apps/test-next/app/focus-guards/page.tsx b/apps/test-next/app/focus-guards/page.tsx new file mode 100644 index 0000000000..6ed83543e2 --- /dev/null +++ b/apps/test-next/app/focus-guards/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as FocusGuards from '@repo/test-registry/components/focus-guards'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/focus-scope/page.tsx b/apps/test-next/app/focus-scope/page.tsx new file mode 100644 index 0000000000..7414dded4c --- /dev/null +++ b/apps/test-next/app/focus-scope/page.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import * as FocusScope from '@repo/test-registry/components/focus-scope'; + +export default function Page() { + return ( +
+ +
+ +
+ ); +} diff --git a/apps/test-next/app/form/page.tsx b/apps/test-next/app/form/page.tsx new file mode 100644 index 0000000000..81118cf9ea --- /dev/null +++ b/apps/test-next/app/form/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as Form from '@repo/test-registry/components/form'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/globals.css b/apps/test-next/app/globals.css new file mode 100644 index 0000000000..9fbccd9b03 --- /dev/null +++ b/apps/test-next/app/globals.css @@ -0,0 +1,4 @@ +@import '@repo/test-styles/resets.css'; +@import '@repo/test-styles/colors.css'; +@import '@repo/test-styles/global.css'; +@import '@repo/test-styles/components.css'; diff --git a/apps/test-next/app/hover-card/page.tsx b/apps/test-next/app/hover-card/page.tsx new file mode 100644 index 0000000000..08776e123f --- /dev/null +++ b/apps/test-next/app/hover-card/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as HoverCard from '@repo/test-registry/components/hover-card'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/label/page.tsx b/apps/test-next/app/label/page.tsx new file mode 100644 index 0000000000..eeeee3c833 --- /dev/null +++ b/apps/test-next/app/label/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as Label from '@repo/test-registry/components/label'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/layout.tsx b/apps/test-next/app/layout.tsx new file mode 100644 index 0000000000..de7134241f --- /dev/null +++ b/apps/test-next/app/layout.tsx @@ -0,0 +1,38 @@ +import type * as React from 'react'; +import type { Metadata } from 'next'; +import { primitives } from '@repo/test-registry'; +import Link from 'next/link'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + +

Next.js testing

+
+
+

Public APIs

+ {primitives.public.map((primitive) => ( + + {primitive.name} + + ))} +
+

Internal APIs

+ {primitives.internal.map((primitive) => ( + + {primitive.name} + + ))} +
+
{children}
+
+ + + ); +} diff --git a/apps/test-next/app/menu/page.tsx b/apps/test-next/app/menu/page.tsx new file mode 100644 index 0000000000..b6d40386b8 --- /dev/null +++ b/apps/test-next/app/menu/page.tsx @@ -0,0 +1,15 @@ +import * as Menu from '@repo/test-registry/components/menu'; + +export default function Page() { + return ( +
+ +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-next/app/menubar/page.tsx b/apps/test-next/app/menubar/page.tsx new file mode 100644 index 0000000000..1e256aaf9b --- /dev/null +++ b/apps/test-next/app/menubar/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as Menubar from '@repo/test-registry/components/menubar'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/navigation-menu/page.tsx b/apps/test-next/app/navigation-menu/page.tsx new file mode 100644 index 0000000000..1cdfb0d840 --- /dev/null +++ b/apps/test-next/app/navigation-menu/page.tsx @@ -0,0 +1,5 @@ +import * as NavigationMenu from '@repo/test-registry/components/navigation-menu'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/one-time-password-field/page.tsx b/apps/test-next/app/one-time-password-field/page.tsx new file mode 100644 index 0000000000..756d1d8aba --- /dev/null +++ b/apps/test-next/app/one-time-password-field/page.tsx @@ -0,0 +1,5 @@ +import * as OneTimePasswordField from '@repo/test-registry/components/one-time-password-field'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/page.tsx b/apps/test-next/app/page.tsx new file mode 100644 index 0000000000..bdf0e1fd25 --- /dev/null +++ b/apps/test-next/app/page.tsx @@ -0,0 +1,3 @@ +export default function Home() { + return
Please select a primitive from the sidebar
; +} diff --git a/apps/test-next/app/password-toggle-field/page.tsx b/apps/test-next/app/password-toggle-field/page.tsx new file mode 100644 index 0000000000..637a53c7ab --- /dev/null +++ b/apps/test-next/app/password-toggle-field/page.tsx @@ -0,0 +1,5 @@ +import * as PasswordToggleField from '@repo/test-registry/components/password-toggle-field'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/popover/page.tsx b/apps/test-next/app/popover/page.tsx new file mode 100644 index 0000000000..69df924b59 --- /dev/null +++ b/apps/test-next/app/popover/page.tsx @@ -0,0 +1,5 @@ +import * as Popover from '@repo/test-registry/components/popover'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/popper/page.tsx b/apps/test-next/app/popper/page.tsx new file mode 100644 index 0000000000..1445cb5c42 --- /dev/null +++ b/apps/test-next/app/popper/page.tsx @@ -0,0 +1,15 @@ +import * as Popper from '@repo/test-registry/components/popper'; + +export default function Page() { + return ( +
+ +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-next/app/portal/page.tsx b/apps/test-next/app/portal/page.tsx new file mode 100644 index 0000000000..a869cfdcd5 --- /dev/null +++ b/apps/test-next/app/portal/page.tsx @@ -0,0 +1,13 @@ +import * as Portal from '@repo/test-registry/components/portal'; + +export default function Page() { + return ( +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-next/app/progress/page.tsx b/apps/test-next/app/progress/page.tsx new file mode 100644 index 0000000000..1c3788f236 --- /dev/null +++ b/apps/test-next/app/progress/page.tsx @@ -0,0 +1,5 @@ +import * as Progress from '@repo/test-registry/components/progress'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/radio-group/page.tsx b/apps/test-next/app/radio-group/page.tsx new file mode 100644 index 0000000000..4737c444a0 --- /dev/null +++ b/apps/test-next/app/radio-group/page.tsx @@ -0,0 +1,5 @@ +import * as RadioGroup from '@repo/test-registry/components/radio-group'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/roving-focus/page.tsx b/apps/test-next/app/roving-focus/page.tsx new file mode 100644 index 0000000000..e9fd86bb88 --- /dev/null +++ b/apps/test-next/app/roving-focus/page.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import * as RovingFocus from '@repo/test-registry/components/roving-focus'; + +export default function Page() { + return ( + <> +

Basic

+ +

Nested

+ + + ); +} diff --git a/apps/test-next/app/scroll-area/page.tsx b/apps/test-next/app/scroll-area/page.tsx new file mode 100644 index 0000000000..2975e15004 --- /dev/null +++ b/apps/test-next/app/scroll-area/page.tsx @@ -0,0 +1,5 @@ +import * as ScrollArea from '@repo/test-registry/components/scroll-area'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/select/page.tsx b/apps/test-next/app/select/page.tsx new file mode 100644 index 0000000000..ebab628536 --- /dev/null +++ b/apps/test-next/app/select/page.tsx @@ -0,0 +1,5 @@ +import * as Select from '@repo/test-registry/components/select'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/separator/page.tsx b/apps/test-next/app/separator/page.tsx new file mode 100644 index 0000000000..befc175921 --- /dev/null +++ b/apps/test-next/app/separator/page.tsx @@ -0,0 +1,5 @@ +import * as Separator from '@repo/test-registry/components/separator'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/slider/page.tsx b/apps/test-next/app/slider/page.tsx new file mode 100644 index 0000000000..f44843a0f6 --- /dev/null +++ b/apps/test-next/app/slider/page.tsx @@ -0,0 +1,5 @@ +import * as Slider from '@repo/test-registry/components/slider'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/slot/page.tsx b/apps/test-next/app/slot/page.tsx new file mode 100644 index 0000000000..9ee17eeb09 --- /dev/null +++ b/apps/test-next/app/slot/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import * as Slot from '@repo/test-registry/components/slot'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/switch/page.tsx b/apps/test-next/app/switch/page.tsx new file mode 100644 index 0000000000..7a331ff84d --- /dev/null +++ b/apps/test-next/app/switch/page.tsx @@ -0,0 +1,5 @@ +import * as Switch from '@repo/test-registry/components/switch'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/tabs/page.tsx b/apps/test-next/app/tabs/page.tsx new file mode 100644 index 0000000000..aa763ecde8 --- /dev/null +++ b/apps/test-next/app/tabs/page.tsx @@ -0,0 +1,5 @@ +import * as Tabs from '@repo/test-registry/components/tabs'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/toast/page.tsx b/apps/test-next/app/toast/page.tsx new file mode 100644 index 0000000000..e5a76bb8b3 --- /dev/null +++ b/apps/test-next/app/toast/page.tsx @@ -0,0 +1,5 @@ +import * as Toast from '@repo/test-registry/components/toast'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/toggle-group/page.tsx b/apps/test-next/app/toggle-group/page.tsx new file mode 100644 index 0000000000..a7bea7f0e0 --- /dev/null +++ b/apps/test-next/app/toggle-group/page.tsx @@ -0,0 +1,5 @@ +import * as ToggleGroup from '@repo/test-registry/components/toggle-group'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/toggle/page.tsx b/apps/test-next/app/toggle/page.tsx new file mode 100644 index 0000000000..df722f71cf --- /dev/null +++ b/apps/test-next/app/toggle/page.tsx @@ -0,0 +1,5 @@ +import * as Toggle from '@repo/test-registry/components/toggle'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/toolbar/page.tsx b/apps/test-next/app/toolbar/page.tsx new file mode 100644 index 0000000000..31024ad77f --- /dev/null +++ b/apps/test-next/app/toolbar/page.tsx @@ -0,0 +1,5 @@ +import * as Toolbar from '@repo/test-registry/components/toolbar'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/tooltip/page.tsx b/apps/test-next/app/tooltip/page.tsx new file mode 100644 index 0000000000..efe77a52e8 --- /dev/null +++ b/apps/test-next/app/tooltip/page.tsx @@ -0,0 +1,5 @@ +import * as Tooltip from '@repo/test-registry/components/tooltip'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/app/visually-hidden/page.tsx b/apps/test-next/app/visually-hidden/page.tsx new file mode 100644 index 0000000000..e20db888d1 --- /dev/null +++ b/apps/test-next/app/visually-hidden/page.tsx @@ -0,0 +1,5 @@ +import * as VisuallyHidden from '@repo/test-registry/components/visually-hidden'; + +export default function Page() { + return ; +} diff --git a/apps/test-next/eslint.config.mjs b/apps/test-next/eslint.config.mjs new file mode 100644 index 0000000000..475c68fbf0 --- /dev/null +++ b/apps/test-next/eslint.config.mjs @@ -0,0 +1,23 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import nextVitals from 'eslint-config-next/core-web-vitals'; +import nextTs from 'eslint-config-next/typescript'; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + '.next/**', + 'out/**', + 'build/**', + 'next-env.d.ts', + ]), + { + rules: { + '@next/next/no-img-element': 'off', + }, + }, +]); + +export default eslintConfig; diff --git a/apps/test-next/next.config.ts b/apps/test-next/next.config.ts new file mode 100644 index 0000000000..e9ffa3083a --- /dev/null +++ b/apps/test-next/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/apps/test-next/package.json b/apps/test-next/package.json new file mode 100644 index 0000000000..1352797d00 --- /dev/null +++ b/apps/test-next/package.json @@ -0,0 +1,29 @@ +{ + "name": "@repo/test-next", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "next dev -p 3003", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@repo/test-data": "workspace:*", + "@repo/test-registry": "workspace:*", + "@repo/test-styles": "workspace:*", + "next": "16.0.1", + "react": "19.2.0", + "radix-ui": "workspace:*", + "react-dom": "19.2.0" + }, + "devDependencies": { + "@types/node": "^22", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "eslint": "^9.38.0", + "eslint-config-next": "16.0.1", + "typescript": "^5.9.3" + } +} diff --git a/apps/test-next/postcss.config.mjs b/apps/test-next/postcss.config.mjs new file mode 100644 index 0000000000..8aa71477f2 --- /dev/null +++ b/apps/test-next/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: {}, +}; + +export default config; diff --git a/apps/test-next/public/favicon.ico b/apps/test-next/public/favicon.ico new file mode 100644 index 0000000000..718d6fea48 Binary files /dev/null and b/apps/test-next/public/favicon.ico differ diff --git a/apps/test-next/tsconfig.json b/apps/test-next/tsconfig.json new file mode 100644 index 0000000000..3453a3215c --- /dev/null +++ b/apps/test-next/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules", "../../**/*"] +} diff --git a/apps/test-react-router-rsc/.gitignore b/apps/test-react-router-rsc/.gitignore new file mode 100644 index 0000000000..039ee62d21 --- /dev/null +++ b/apps/test-react-router-rsc/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.env +/node_modules/ + +# React Router +/.react-router/ +/build/ diff --git a/apps/test-react-router-rsc/README.md b/apps/test-react-router-rsc/README.md new file mode 100644 index 0000000000..ee739101c3 --- /dev/null +++ b/apps/test-react-router-rsc/README.md @@ -0,0 +1,62 @@ +# Welcome to React Router! (Experimental RSC) + +โš ๏ธ **EXPERIMENTAL**: This template demonstrates React Server Components with React Router. This is experimental technology and not recommended for production use. + +A modern template for exploring React Server Components (RSC) with React Router, powered by Vite. + +## Features + +- ๐Ÿงช **Experimental React Server Components** +- ๐Ÿš€ Server-side rendering with RSC +- โšก๏ธ Hot Module Replacement (HMR) +- ๐Ÿ“ฆ Asset bundling and optimization with Vite +- ๐Ÿ”„ Data loading and mutations +- ๐Ÿ”’ TypeScript by default +- ๐Ÿ“– [React Router docs](https://reactrouter.com/) +- ๐Ÿ“š [React Server Components guide](https://reactrouter.com/how-to/react-server-components) + +## Getting Started + +### Installation + +Install the dependencies: + +```bash +pnpm install +``` + +### Development + +Start the development server with HMR: + +```bash +pnpm dev +# or from the root of the repo +pnpm dev:react-router-rsc +``` + +Your application will be available at `http://localhost:5173`. + +## Building for Production + +Create a production build: + +```bash +pnpm build +``` + +## Running Production Build + +Run the production server: + +```bash +npm start +``` + +## Understanding React Server Components + +Learn more about React Server Components with React Router in our [comprehensive guide](https://reactrouter.com/how-to/react-server-components). + +--- + +Built with โค๏ธ using React Router. diff --git a/apps/test-react-router-rsc/app/globals.css b/apps/test-react-router-rsc/app/globals.css new file mode 100644 index 0000000000..9fbccd9b03 --- /dev/null +++ b/apps/test-react-router-rsc/app/globals.css @@ -0,0 +1,4 @@ +@import '@repo/test-styles/resets.css'; +@import '@repo/test-styles/colors.css'; +@import '@repo/test-styles/global.css'; +@import '@repo/test-styles/components.css'; diff --git a/apps/test-react-router-rsc/app/root.tsx b/apps/test-react-router-rsc/app/root.tsx new file mode 100644 index 0000000000..8a10c72dd3 --- /dev/null +++ b/apps/test-react-router-rsc/app/root.tsx @@ -0,0 +1,90 @@ +import { + isRouteErrorResponse, + Links, + Meta, + NavLink, + Outlet, + ScrollRestoration, +} from 'react-router'; +import { primitives } from '@repo/test-registry'; +import type { Route } from './+types/root'; +import './globals.css'; + +export const links: Route.LinksFunction = () => [ + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossOrigin: 'anonymous', + }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', + }, +]; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + +

React Router RSC testing

+
+
+

Public APIs

+ {primitives.public.map((primitive) => ( + + {primitive.name} + + ))} +
+

Internal APIs

+ {primitives.internal.map((primitive) => ( + + {primitive.name} + + ))} +
+
{children}
+
+ + + + ); +} + +export default function App() { + return ; +} + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = 'Oops!'; + let details = 'An unexpected error occurred.'; + let stack: string | undefined; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? '404' : 'Error'; + details = + error.status === 404 ? 'The requested page could not be found.' : error.statusText || details; + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message; + stack = error.stack; + } + + return ( +
+

{message}

+

{details}

+ {stack && ( +
+          {stack}
+        
+ )} +
+ ); +} diff --git a/apps/test-react-router-rsc/app/routes.ts b/apps/test-react-router-rsc/app/routes.ts new file mode 100644 index 0000000000..ca9af6722c --- /dev/null +++ b/apps/test-react-router-rsc/app/routes.ts @@ -0,0 +1,10 @@ +import { type RouteConfig, index, route } from '@react-router/dev/routes'; +import { primitives } from '@repo/test-registry'; + +export default [ + index('routes/index.tsx'), + // Public APIs + ...primitives.public.map((primitive) => route(primitive.id, `routes/${primitive.id}.tsx`)), + // Internal APIs + ...primitives.internal.map((primitive) => route(primitive.id, `routes/${primitive.id}.tsx`)), +] satisfies RouteConfig; diff --git a/apps/test-react-router-rsc/app/routes/accessible-icon.tsx b/apps/test-react-router-rsc/app/routes/accessible-icon.tsx new file mode 100644 index 0000000000..96a755c583 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/accessible-icon.tsx @@ -0,0 +1,5 @@ +import * as AccessibleIcon from '@repo/test-registry/components/accessible-icon'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/accordion.tsx b/apps/test-react-router-rsc/app/routes/accordion.tsx new file mode 100644 index 0000000000..9987a30b48 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/accordion.tsx @@ -0,0 +1,5 @@ +import * as Accordion from '@repo/test-registry/components/accordion'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/alert-dialog.tsx b/apps/test-react-router-rsc/app/routes/alert-dialog.tsx new file mode 100644 index 0000000000..575cff7dcf --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/alert-dialog.tsx @@ -0,0 +1,5 @@ +import * as AlertDialog from '@repo/test-registry/components/alert-dialog'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/arrow.tsx b/apps/test-react-router-rsc/app/routes/arrow.tsx new file mode 100644 index 0000000000..a8269259e0 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/arrow.tsx @@ -0,0 +1,5 @@ +import * as Arrow from '@repo/test-registry/components/arrow'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/aspect-ratio.tsx b/apps/test-react-router-rsc/app/routes/aspect-ratio.tsx new file mode 100644 index 0000000000..bb1be25fb6 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatio from '@repo/test-registry/components/aspect-ratio'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/avatar.tsx b/apps/test-react-router-rsc/app/routes/avatar.tsx new file mode 100644 index 0000000000..1e6d3c6500 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/avatar.tsx @@ -0,0 +1,5 @@ +import * as Avatar from '@repo/test-registry/components/avatar'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/checkbox.tsx b/apps/test-react-router-rsc/app/routes/checkbox.tsx new file mode 100644 index 0000000000..b62e7a7beb --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/checkbox.tsx @@ -0,0 +1,5 @@ +import * as Checkbox from '@repo/test-registry/components/checkbox'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/collapsible.tsx b/apps/test-react-router-rsc/app/routes/collapsible.tsx new file mode 100644 index 0000000000..9699f299f8 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/collapsible.tsx @@ -0,0 +1,5 @@ +import * as Collapsible from '@repo/test-registry/components/collapsible'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/collection.tsx b/apps/test-react-router-rsc/app/routes/collection.tsx new file mode 100644 index 0000000000..10e0f76ad0 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/collection.tsx @@ -0,0 +1,26 @@ +import { + Unstable, + UnstableItem, + Stable, + StableItem, +} from '@repo/test-registry/components/collection'; + +export async function ServerComponent() { + return ( +
+

Unstable Collection

+ + Item 1 + Item 2 + Item 3 + +
+

Stable Collection

+ + Item 1 + Item 2 + Item 3 + +
+ ); +} diff --git a/apps/test-react-router-rsc/app/routes/context-menu.tsx b/apps/test-react-router-rsc/app/routes/context-menu.tsx new file mode 100644 index 0000000000..84cc2b4290 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/context-menu.tsx @@ -0,0 +1,5 @@ +import * as ContextMenu from '@repo/test-registry/components/context-menu'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/dialog.tsx b/apps/test-react-router-rsc/app/routes/dialog.tsx new file mode 100644 index 0000000000..4fffaea34e --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/dialog.tsx @@ -0,0 +1,5 @@ +import * as Dialog from '@repo/test-registry/components/dialog'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/direction.tsx b/apps/test-react-router-rsc/app/routes/direction.tsx new file mode 100644 index 0000000000..5ed63e6ba8 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/direction.tsx @@ -0,0 +1,10 @@ +import * as Direction from '@repo/test-registry/components/direction'; + +export async function ServerComponent() { + return ( +
+ + +
+ ); +} diff --git a/apps/test-react-router-rsc/app/routes/dismissable-layer.tsx b/apps/test-react-router-rsc/app/routes/dismissable-layer.tsx new file mode 100644 index 0000000000..719f9981e9 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/dismissable-layer.tsx @@ -0,0 +1,5 @@ +import * as DismissableLayer from '@repo/test-registry/components/dismissable-layer'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/dropdown-menu.tsx b/apps/test-react-router-rsc/app/routes/dropdown-menu.tsx new file mode 100644 index 0000000000..759d736af2 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/dropdown-menu.tsx @@ -0,0 +1,5 @@ +import * as DropdownMenu from '@repo/test-registry/components/dropdown-menu'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/focus-guards.tsx b/apps/test-react-router-rsc/app/routes/focus-guards.tsx new file mode 100644 index 0000000000..ea3b04e11e --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/focus-guards.tsx @@ -0,0 +1,5 @@ +import * as FocusGuards from '@repo/test-registry/components/focus-guards'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/focus-scope.tsx b/apps/test-react-router-rsc/app/routes/focus-scope.tsx new file mode 100644 index 0000000000..3d8e80c610 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/focus-scope.tsx @@ -0,0 +1,11 @@ +import * as FocusScope from '@repo/test-registry/components/focus-scope'; + +export async function ServerComponent() { + return ( +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router-rsc/app/routes/form.tsx b/apps/test-react-router-rsc/app/routes/form.tsx new file mode 100644 index 0000000000..f612508ca8 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/form.tsx @@ -0,0 +1,5 @@ +import * as Form from '@repo/test-registry/components/form'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/hover-card.tsx b/apps/test-react-router-rsc/app/routes/hover-card.tsx new file mode 100644 index 0000000000..86a9022cdb --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/hover-card.tsx @@ -0,0 +1,5 @@ +import * as HoverCard from '@repo/test-registry/components/hover-card'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/index.tsx b/apps/test-react-router-rsc/app/routes/index.tsx new file mode 100644 index 0000000000..2651384916 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/index.tsx @@ -0,0 +1,12 @@ +import type { Route } from './+types/index'; + +export function meta(_args: Route.MetaArgs) { + return [ + { title: 'New React Router App' }, + { name: 'description', content: 'Welcome to React Router!' }, + ]; +} + +export default function Home() { + return
Please select a primitive from the sidebar
; +} diff --git a/apps/test-react-router-rsc/app/routes/label.tsx b/apps/test-react-router-rsc/app/routes/label.tsx new file mode 100644 index 0000000000..e092e7ced7 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/label.tsx @@ -0,0 +1,5 @@ +import * as Label from '@repo/test-registry/components/label'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/menu.tsx b/apps/test-react-router-rsc/app/routes/menu.tsx new file mode 100644 index 0000000000..31ee3adf74 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/menu.tsx @@ -0,0 +1,15 @@ +import * as Menu from '@repo/test-registry/components/menu'; + +export async function ServerComponent() { + return ( +
+ +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router-rsc/app/routes/menubar.tsx b/apps/test-react-router-rsc/app/routes/menubar.tsx new file mode 100644 index 0000000000..325cc2ceb2 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/menubar.tsx @@ -0,0 +1,5 @@ +import * as Menubar from '@repo/test-registry/components/menubar'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/navigation-menu.tsx b/apps/test-react-router-rsc/app/routes/navigation-menu.tsx new file mode 100644 index 0000000000..c89989d05a --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/navigation-menu.tsx @@ -0,0 +1,5 @@ +import * as NavigationMenu from '@repo/test-registry/components/navigation-menu'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/one-time-password-field.tsx b/apps/test-react-router-rsc/app/routes/one-time-password-field.tsx new file mode 100644 index 0000000000..f5fedc7646 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/one-time-password-field.tsx @@ -0,0 +1,5 @@ +import * as OneTimePasswordField from '@repo/test-registry/components/one-time-password-field'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/password-toggle-field.tsx b/apps/test-react-router-rsc/app/routes/password-toggle-field.tsx new file mode 100644 index 0000000000..251cb8358a --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/password-toggle-field.tsx @@ -0,0 +1,5 @@ +import * as PasswordToggleField from '@repo/test-registry/components/password-toggle-field'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/popover.tsx b/apps/test-react-router-rsc/app/routes/popover.tsx new file mode 100644 index 0000000000..738c7ae6f3 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/popover.tsx @@ -0,0 +1,5 @@ +import * as Popover from '@repo/test-registry/components/popover'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/popper.tsx b/apps/test-react-router-rsc/app/routes/popper.tsx new file mode 100644 index 0000000000..4dea36a0cf --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/popper.tsx @@ -0,0 +1,15 @@ +import * as Popper from '@repo/test-registry/components/popper'; + +export async function ServerComponent() { + return ( +
+ +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router-rsc/app/routes/portal.tsx b/apps/test-react-router-rsc/app/routes/portal.tsx new file mode 100644 index 0000000000..6ec768d0e9 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/portal.tsx @@ -0,0 +1,13 @@ +import * as Portal from '@repo/test-registry/components/portal'; + +export async function ServerComponent() { + return ( +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router-rsc/app/routes/progress.tsx b/apps/test-react-router-rsc/app/routes/progress.tsx new file mode 100644 index 0000000000..2ed436ed80 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/progress.tsx @@ -0,0 +1,5 @@ +import * as Progress from '@repo/test-registry/components/progress'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/radio-group.tsx b/apps/test-react-router-rsc/app/routes/radio-group.tsx new file mode 100644 index 0000000000..ef28fd98a4 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/radio-group.tsx @@ -0,0 +1,5 @@ +import * as RadioGroup from '@repo/test-registry/components/radio-group'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/roving-focus.tsx b/apps/test-react-router-rsc/app/routes/roving-focus.tsx new file mode 100644 index 0000000000..79a2aeb34e --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/roving-focus.tsx @@ -0,0 +1,12 @@ +import * as RovingFocus from '@repo/test-registry/components/roving-focus'; + +export async function ServerComponent() { + return ( + <> +

Basic

+ +

Nested

+ + + ); +} diff --git a/apps/test-react-router-rsc/app/routes/scroll-area.tsx b/apps/test-react-router-rsc/app/routes/scroll-area.tsx new file mode 100644 index 0000000000..d775b735a4 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/scroll-area.tsx @@ -0,0 +1,5 @@ +import * as ScrollArea from '@repo/test-registry/components/scroll-area'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/select.tsx b/apps/test-react-router-rsc/app/routes/select.tsx new file mode 100644 index 0000000000..539eef1383 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/select.tsx @@ -0,0 +1,5 @@ +import * as Select from '@repo/test-registry/components/select'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/separator.tsx b/apps/test-react-router-rsc/app/routes/separator.tsx new file mode 100644 index 0000000000..6e00362d3f --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/separator.tsx @@ -0,0 +1,5 @@ +import * as Separator from '@repo/test-registry/components/separator'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/slider.tsx b/apps/test-react-router-rsc/app/routes/slider.tsx new file mode 100644 index 0000000000..e1099a9c0e --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/slider.tsx @@ -0,0 +1,5 @@ +import * as Slider from '@repo/test-registry/components/slider'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/slot.tsx b/apps/test-react-router-rsc/app/routes/slot.tsx new file mode 100644 index 0000000000..d96e5e2d10 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/slot.tsx @@ -0,0 +1,5 @@ +import * as Slot from '@repo/test-registry/components/slot'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/switch.tsx b/apps/test-react-router-rsc/app/routes/switch.tsx new file mode 100644 index 0000000000..b0a0b0d4f7 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/switch.tsx @@ -0,0 +1,5 @@ +import * as Switch from '@repo/test-registry/components/switch'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/tabs.tsx b/apps/test-react-router-rsc/app/routes/tabs.tsx new file mode 100644 index 0000000000..52565b7f2a --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/tabs.tsx @@ -0,0 +1,5 @@ +import * as Tabs from '@repo/test-registry/components/tabs'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/toast.tsx b/apps/test-react-router-rsc/app/routes/toast.tsx new file mode 100644 index 0000000000..b4c5150b35 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/toast.tsx @@ -0,0 +1,5 @@ +import * as Toast from '@repo/test-registry/components/toast'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/toggle-group.tsx b/apps/test-react-router-rsc/app/routes/toggle-group.tsx new file mode 100644 index 0000000000..48821f77f5 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/toggle-group.tsx @@ -0,0 +1,5 @@ +import * as ToggleGroup from '@repo/test-registry/components/toggle-group'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/toggle.tsx b/apps/test-react-router-rsc/app/routes/toggle.tsx new file mode 100644 index 0000000000..574eade2d6 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/toggle.tsx @@ -0,0 +1,5 @@ +import * as Toggle from '@repo/test-registry/components/toggle'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/toolbar.tsx b/apps/test-react-router-rsc/app/routes/toolbar.tsx new file mode 100644 index 0000000000..0985e30225 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/toolbar.tsx @@ -0,0 +1,5 @@ +import * as Toolbar from '@repo/test-registry/components/toolbar'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/tooltip.tsx b/apps/test-react-router-rsc/app/routes/tooltip.tsx new file mode 100644 index 0000000000..d1b4e9702e --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/tooltip.tsx @@ -0,0 +1,5 @@ +import * as Tooltip from '@repo/test-registry/components/tooltip'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/app/routes/visually-hidden.tsx b/apps/test-react-router-rsc/app/routes/visually-hidden.tsx new file mode 100644 index 0000000000..fff4ad3f86 --- /dev/null +++ b/apps/test-react-router-rsc/app/routes/visually-hidden.tsx @@ -0,0 +1,5 @@ +import * as VisuallyHidden from '@repo/test-registry/components/visually-hidden'; + +export async function ServerComponent() { + return ; +} diff --git a/apps/test-react-router-rsc/eslint.config.js b/apps/test-react-router-rsc/eslint.config.js new file mode 100644 index 0000000000..726acba12b --- /dev/null +++ b/apps/test-react-router-rsc/eslint.config.js @@ -0,0 +1,31 @@ +// @ts-check +import { defineConfig, globalIgnores } from 'eslint/config'; +import { configs } from '@repo/eslint-config/vite'; + +export default defineConfig([ + globalIgnores(['dist']), + ...configs, + { + rules: { + 'react-refresh/only-export-components': [ + 'error', + { + allowExportNames: [ + // https://reactrouter.com/start/framework/route-module + 'middleware', + 'clientMiddleware', + 'loader', + 'clientLoader', + 'action', + 'clientAction', + 'headers', + 'handle', + 'links', + 'meta', + 'shouldRevalidate', + ], + }, + ], + }, + }, +]); diff --git a/apps/test-react-router-rsc/package.json b/apps/test-react-router-rsc/package.json new file mode 100644 index 0000000000..464db12009 --- /dev/null +++ b/apps/test-react-router-rsc/package.json @@ -0,0 +1,40 @@ +{ + "name": "@repo/test-react-router-rsc", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "react-router build", + "dev": "react-router dev -p 3005", + "start": "react-router-serve build/server/index.js", + "typecheck": "react-router typegen && tsc" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@react-router/serve": "7.9.2", + "@remix-run/node-fetch-server": "0.9.0", + "@repo/test-data": "workspace:*", + "@repo/test-registry": "workspace:*", + "@repo/test-styles": "workspace:*", + "isbot": "^5.1.32", + "radix-ui": "workspace:*", + "react": "19.2.0", + "react-dom": "19.2.0", + "react-router": "7.9.2" + }, + "devDependencies": { + "@react-router/dev": "7.9.2", + "@react-router/fs-routes": "7.9.2", + "@repo/eslint-config": "workspace:*", + "@types/node": "^22", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-rsc": "0.4.31", + "eslint": "^9.38.0", + "eslint-plugin-react-refresh": "^0.4.22", + "typescript": "5.9.3", + "vite": "7.1.6", + "vite-plugin-devtools-json": "^1.0.0", + "vite-tsconfig-paths": "5.1.4" + } +} diff --git a/apps/test-react-router-rsc/public/favicon.ico b/apps/test-react-router-rsc/public/favicon.ico new file mode 100644 index 0000000000..5dbdfcddcb Binary files /dev/null and b/apps/test-react-router-rsc/public/favicon.ico differ diff --git a/apps/test-react-router-rsc/react-router.config.ts b/apps/test-react-router-rsc/react-router.config.ts new file mode 100644 index 0000000000..9c6ea11734 --- /dev/null +++ b/apps/test-react-router-rsc/react-router.config.ts @@ -0,0 +1,5 @@ +import type { Config } from "@react-router/dev/config"; + +export default { + // Config options... +} satisfies Config; diff --git a/apps/test-react-router-rsc/tsconfig.json b/apps/test-react-router-rsc/tsconfig.json new file mode 100644 index 0000000000..b5c602b526 --- /dev/null +++ b/apps/test-react-router-rsc/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["**/*.ts", "**/*.tsx", "./.react-router/types/**/*"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipLibCheck": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "moduleResolution": "Bundler", + "module": "ESNext", + "target": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "types": ["vite/client", "@vitejs/plugin-rsc/types"], + "jsx": "react-jsx", + "rootDirs": [".", "./.react-router/types"], + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + } + } +} diff --git a/apps/test-react-router-rsc/vite.config.ts b/apps/test-react-router-rsc/vite.config.ts new file mode 100644 index 0000000000..dcf1d639c6 --- /dev/null +++ b/apps/test-react-router-rsc/vite.config.ts @@ -0,0 +1,9 @@ +import { unstable_reactRouterRSC as reactRouterRSC } from '@react-router/dev/vite'; +import rsc from '@vitejs/plugin-rsc'; +import { defineConfig } from 'vite'; +import devtoolsJson from 'vite-plugin-devtools-json'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [tsconfigPaths(), reactRouterRSC(), rsc(), devtoolsJson()], +}); diff --git a/apps/test-react-router/.dockerignore b/apps/test-react-router/.dockerignore new file mode 100644 index 0000000000..9b8d514712 --- /dev/null +++ b/apps/test-react-router/.dockerignore @@ -0,0 +1,4 @@ +.react-router +build +node_modules +README.md \ No newline at end of file diff --git a/apps/test-react-router/.gitignore b/apps/test-react-router/.gitignore new file mode 100644 index 0000000000..039ee62d21 --- /dev/null +++ b/apps/test-react-router/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.env +/node_modules/ + +# React Router +/.react-router/ +/build/ diff --git a/apps/test-react-router/Dockerfile b/apps/test-react-router/Dockerfile new file mode 100644 index 0000000000..207bf937e3 --- /dev/null +++ b/apps/test-react-router/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine AS development-dependencies-env +COPY . /app +WORKDIR /app +RUN npm ci + +FROM node:20-alpine AS production-dependencies-env +COPY ./package.json package-lock.json /app/ +WORKDIR /app +RUN npm ci --omit=dev + +FROM node:20-alpine AS build-env +COPY . /app/ +COPY --from=development-dependencies-env /app/node_modules /app/node_modules +WORKDIR /app +RUN npm run build + +FROM node:20-alpine +COPY ./package.json package-lock.json /app/ +COPY --from=production-dependencies-env /app/node_modules /app/node_modules +COPY --from=build-env /app/build /app/build +WORKDIR /app +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/apps/test-react-router/README.md b/apps/test-react-router/README.md new file mode 100644 index 0000000000..a0aa124416 --- /dev/null +++ b/apps/test-react-router/README.md @@ -0,0 +1,84 @@ +# Welcome to React Router! + +A modern, production-ready template for building full-stack React applications using React Router. + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default) + +## Features + +- ๐Ÿš€ Server-side rendering +- โšก๏ธ Hot Module Replacement (HMR) +- ๐Ÿ“ฆ Asset bundling and optimization +- ๐Ÿ”„ Data loading and mutations +- ๐Ÿ”’ TypeScript by default +- ๐Ÿ“– [React Router docs](https://reactrouter.com/) + +## Getting Started + +### Installation + +Install the dependencies: + +```bash +pnpm install +``` + +### Development + +Start the development server with HMR: + +```bash +pnpm dev +# or from the root of the repo +pnpm dev:react-router +``` + +Your application will be available at `http://localhost:5173`. + +## Building for Production + +Create a production build: + +```bash +pnpm build +``` + +## Deployment + +### Docker Deployment + +To build and run using Docker: + +```bash +docker build -t my-app . + +# Run the container +docker run -p 3000:3000 my-app +``` + +The containerized application can be deployed to any platform that supports Docker, including: + +- AWS ECS +- Google Cloud Run +- Azure Container Apps +- Digital Ocean App Platform +- Fly.io +- Railway + +### DIY Deployment + +If you're familiar with deploying Node applications, the built-in app server is production-ready. + +Make sure to deploy the output of `npm run build` + +``` +โ”œโ”€โ”€ package.json +โ”œโ”€โ”€ package-lock.json (or pnpm-lock.yaml, or bun.lockb) +โ”œโ”€โ”€ build/ +โ”‚ โ”œโ”€โ”€ client/ # Static assets +โ”‚ โ””โ”€โ”€ server/ # Server-side code +``` + +--- + +Built with โค๏ธ using React Router. diff --git a/apps/test-react-router/app/globals.css b/apps/test-react-router/app/globals.css new file mode 100644 index 0000000000..9fbccd9b03 --- /dev/null +++ b/apps/test-react-router/app/globals.css @@ -0,0 +1,4 @@ +@import '@repo/test-styles/resets.css'; +@import '@repo/test-styles/colors.css'; +@import '@repo/test-styles/global.css'; +@import '@repo/test-styles/components.css'; diff --git a/apps/test-react-router/app/root.tsx b/apps/test-react-router/app/root.tsx new file mode 100644 index 0000000000..3fa4e5315a --- /dev/null +++ b/apps/test-react-router/app/root.tsx @@ -0,0 +1,92 @@ +import { + isRouteErrorResponse, + NavLink, + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from 'react-router'; +import { primitives } from '@repo/test-registry'; +import type { Route } from './+types/root'; +import './globals.css'; + +export const links: Route.LinksFunction = () => [ + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossOrigin: 'anonymous', + }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', + }, +]; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + +

React Router testing

+
+
+

Public APIs

+ {primitives.public.map((primitive) => ( + + {primitive.name} + + ))} +
+

Internal APIs

+ {primitives.internal.map((primitive) => ( + + {primitive.name} + + ))} +
+
{children}
+
+ + + + + ); +} + +export default function App() { + return ; +} + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = 'Oops!'; + let details = 'An unexpected error occurred.'; + let stack: string | undefined; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? '404' : 'Error'; + details = + error.status === 404 ? 'The requested page could not be found.' : error.statusText || details; + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message; + stack = error.stack; + } + + return ( +
+

{message}

+

{details}

+ {stack && ( +
+          {stack}
+        
+ )} +
+ ); +} diff --git a/apps/test-react-router/app/routes.ts b/apps/test-react-router/app/routes.ts new file mode 100644 index 0000000000..ca9af6722c --- /dev/null +++ b/apps/test-react-router/app/routes.ts @@ -0,0 +1,10 @@ +import { type RouteConfig, index, route } from '@react-router/dev/routes'; +import { primitives } from '@repo/test-registry'; + +export default [ + index('routes/index.tsx'), + // Public APIs + ...primitives.public.map((primitive) => route(primitive.id, `routes/${primitive.id}.tsx`)), + // Internal APIs + ...primitives.internal.map((primitive) => route(primitive.id, `routes/${primitive.id}.tsx`)), +] satisfies RouteConfig; diff --git a/apps/test-react-router/app/routes/accessible-icon.tsx b/apps/test-react-router/app/routes/accessible-icon.tsx new file mode 100644 index 0000000000..32b248e40a --- /dev/null +++ b/apps/test-react-router/app/routes/accessible-icon.tsx @@ -0,0 +1,5 @@ +import * as AccessibleIcon from '@repo/test-registry/components/accessible-icon'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/accordion.tsx b/apps/test-react-router/app/routes/accordion.tsx new file mode 100644 index 0000000000..8b1ea7973f --- /dev/null +++ b/apps/test-react-router/app/routes/accordion.tsx @@ -0,0 +1,5 @@ +import * as Accordion from '@repo/test-registry/components/accordion'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/alert-dialog.tsx b/apps/test-react-router/app/routes/alert-dialog.tsx new file mode 100644 index 0000000000..3d978ac60d --- /dev/null +++ b/apps/test-react-router/app/routes/alert-dialog.tsx @@ -0,0 +1,5 @@ +import * as AlertDialog from '@repo/test-registry/components/alert-dialog'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/arrow.tsx b/apps/test-react-router/app/routes/arrow.tsx new file mode 100644 index 0000000000..a889e8b7ae --- /dev/null +++ b/apps/test-react-router/app/routes/arrow.tsx @@ -0,0 +1,5 @@ +import * as Arrow from '@repo/test-registry/components/arrow'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/aspect-ratio.tsx b/apps/test-react-router/app/routes/aspect-ratio.tsx new file mode 100644 index 0000000000..6a91fd4f30 --- /dev/null +++ b/apps/test-react-router/app/routes/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatio from '@repo/test-registry/components/aspect-ratio'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/avatar.tsx b/apps/test-react-router/app/routes/avatar.tsx new file mode 100644 index 0000000000..75e98cfdfb --- /dev/null +++ b/apps/test-react-router/app/routes/avatar.tsx @@ -0,0 +1,5 @@ +import * as Avatar from '@repo/test-registry/components/avatar'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/checkbox.tsx b/apps/test-react-router/app/routes/checkbox.tsx new file mode 100644 index 0000000000..64f74ec0d1 --- /dev/null +++ b/apps/test-react-router/app/routes/checkbox.tsx @@ -0,0 +1,5 @@ +import * as Checkbox from '@repo/test-registry/components/checkbox'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/collapsible.tsx b/apps/test-react-router/app/routes/collapsible.tsx new file mode 100644 index 0000000000..75b50f9f23 --- /dev/null +++ b/apps/test-react-router/app/routes/collapsible.tsx @@ -0,0 +1,5 @@ +import * as Collapsible from '@repo/test-registry/components/collapsible'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/collection.tsx b/apps/test-react-router/app/routes/collection.tsx new file mode 100644 index 0000000000..1d4a406c18 --- /dev/null +++ b/apps/test-react-router/app/routes/collection.tsx @@ -0,0 +1,26 @@ +import { + Unstable, + UnstableItem, + Stable, + StableItem, +} from '@repo/test-registry/components/collection'; + +export default function Page() { + return ( +
+

Unstable Collection

+ + Item 1 + Item 2 + Item 3 + +
+

Stable Collection

+ + Item 1 + Item 2 + Item 3 + +
+ ); +} diff --git a/apps/test-react-router/app/routes/context-menu.tsx b/apps/test-react-router/app/routes/context-menu.tsx new file mode 100644 index 0000000000..b40cbe3e30 --- /dev/null +++ b/apps/test-react-router/app/routes/context-menu.tsx @@ -0,0 +1,5 @@ +import * as ContextMenu from '@repo/test-registry/components/context-menu'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/dialog.tsx b/apps/test-react-router/app/routes/dialog.tsx new file mode 100644 index 0000000000..d7698910ad --- /dev/null +++ b/apps/test-react-router/app/routes/dialog.tsx @@ -0,0 +1,5 @@ +import * as Dialog from '@repo/test-registry/components/dialog'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/direction.tsx b/apps/test-react-router/app/routes/direction.tsx new file mode 100644 index 0000000000..219ba966e9 --- /dev/null +++ b/apps/test-react-router/app/routes/direction.tsx @@ -0,0 +1,10 @@ +import * as Direction from '@repo/test-registry/components/direction'; + +export default function Page() { + return ( +
+ + +
+ ); +} diff --git a/apps/test-react-router/app/routes/dismissable-layer.tsx b/apps/test-react-router/app/routes/dismissable-layer.tsx new file mode 100644 index 0000000000..cffa1af518 --- /dev/null +++ b/apps/test-react-router/app/routes/dismissable-layer.tsx @@ -0,0 +1,5 @@ +import * as DismissableLayer from '@repo/test-registry/components/dismissable-layer'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/dropdown-menu.tsx b/apps/test-react-router/app/routes/dropdown-menu.tsx new file mode 100644 index 0000000000..d221b4aa83 --- /dev/null +++ b/apps/test-react-router/app/routes/dropdown-menu.tsx @@ -0,0 +1,5 @@ +import * as DropdownMenu from '@repo/test-registry/components/dropdown-menu'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/focus-guards.tsx b/apps/test-react-router/app/routes/focus-guards.tsx new file mode 100644 index 0000000000..9d3845352d --- /dev/null +++ b/apps/test-react-router/app/routes/focus-guards.tsx @@ -0,0 +1,5 @@ +import * as FocusGuards from '@repo/test-registry/components/focus-guards'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/focus-scope.tsx b/apps/test-react-router/app/routes/focus-scope.tsx new file mode 100644 index 0000000000..21ca07db56 --- /dev/null +++ b/apps/test-react-router/app/routes/focus-scope.tsx @@ -0,0 +1,11 @@ +import * as FocusScope from '@repo/test-registry/components/focus-scope'; + +export default function Page() { + return ( +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router/app/routes/form.tsx b/apps/test-react-router/app/routes/form.tsx new file mode 100644 index 0000000000..c5c16502dc --- /dev/null +++ b/apps/test-react-router/app/routes/form.tsx @@ -0,0 +1,5 @@ +import * as Form from '@repo/test-registry/components/form'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/hover-card.tsx b/apps/test-react-router/app/routes/hover-card.tsx new file mode 100644 index 0000000000..c20fbfd51c --- /dev/null +++ b/apps/test-react-router/app/routes/hover-card.tsx @@ -0,0 +1,5 @@ +import * as HoverCard from '@repo/test-registry/components/hover-card'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/index.tsx b/apps/test-react-router/app/routes/index.tsx new file mode 100644 index 0000000000..2651384916 --- /dev/null +++ b/apps/test-react-router/app/routes/index.tsx @@ -0,0 +1,12 @@ +import type { Route } from './+types/index'; + +export function meta(_args: Route.MetaArgs) { + return [ + { title: 'New React Router App' }, + { name: 'description', content: 'Welcome to React Router!' }, + ]; +} + +export default function Home() { + return
Please select a primitive from the sidebar
; +} diff --git a/apps/test-react-router/app/routes/label.tsx b/apps/test-react-router/app/routes/label.tsx new file mode 100644 index 0000000000..24aac9352c --- /dev/null +++ b/apps/test-react-router/app/routes/label.tsx @@ -0,0 +1,5 @@ +import * as Label from '@repo/test-registry/components/label'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/menu.tsx b/apps/test-react-router/app/routes/menu.tsx new file mode 100644 index 0000000000..b6d40386b8 --- /dev/null +++ b/apps/test-react-router/app/routes/menu.tsx @@ -0,0 +1,15 @@ +import * as Menu from '@repo/test-registry/components/menu'; + +export default function Page() { + return ( +
+ +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router/app/routes/menubar.tsx b/apps/test-react-router/app/routes/menubar.tsx new file mode 100644 index 0000000000..f55666a4cf --- /dev/null +++ b/apps/test-react-router/app/routes/menubar.tsx @@ -0,0 +1,5 @@ +import * as Menubar from '@repo/test-registry/components/menubar'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/navigation-menu.tsx b/apps/test-react-router/app/routes/navigation-menu.tsx new file mode 100644 index 0000000000..1cdfb0d840 --- /dev/null +++ b/apps/test-react-router/app/routes/navigation-menu.tsx @@ -0,0 +1,5 @@ +import * as NavigationMenu from '@repo/test-registry/components/navigation-menu'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/one-time-password-field.tsx b/apps/test-react-router/app/routes/one-time-password-field.tsx new file mode 100644 index 0000000000..756d1d8aba --- /dev/null +++ b/apps/test-react-router/app/routes/one-time-password-field.tsx @@ -0,0 +1,5 @@ +import * as OneTimePasswordField from '@repo/test-registry/components/one-time-password-field'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/password-toggle-field.tsx b/apps/test-react-router/app/routes/password-toggle-field.tsx new file mode 100644 index 0000000000..637a53c7ab --- /dev/null +++ b/apps/test-react-router/app/routes/password-toggle-field.tsx @@ -0,0 +1,5 @@ +import * as PasswordToggleField from '@repo/test-registry/components/password-toggle-field'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/popover.tsx b/apps/test-react-router/app/routes/popover.tsx new file mode 100644 index 0000000000..69df924b59 --- /dev/null +++ b/apps/test-react-router/app/routes/popover.tsx @@ -0,0 +1,5 @@ +import * as Popover from '@repo/test-registry/components/popover'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/popper.tsx b/apps/test-react-router/app/routes/popper.tsx new file mode 100644 index 0000000000..1445cb5c42 --- /dev/null +++ b/apps/test-react-router/app/routes/popper.tsx @@ -0,0 +1,15 @@ +import * as Popper from '@repo/test-registry/components/popper'; + +export default function Page() { + return ( +
+ +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router/app/routes/portal.tsx b/apps/test-react-router/app/routes/portal.tsx new file mode 100644 index 0000000000..a869cfdcd5 --- /dev/null +++ b/apps/test-react-router/app/routes/portal.tsx @@ -0,0 +1,13 @@ +import * as Portal from '@repo/test-registry/components/portal'; + +export default function Page() { + return ( +
+ +
+ +
+ +
+ ); +} diff --git a/apps/test-react-router/app/routes/progress.tsx b/apps/test-react-router/app/routes/progress.tsx new file mode 100644 index 0000000000..1c3788f236 --- /dev/null +++ b/apps/test-react-router/app/routes/progress.tsx @@ -0,0 +1,5 @@ +import * as Progress from '@repo/test-registry/components/progress'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/radio-group.tsx b/apps/test-react-router/app/routes/radio-group.tsx new file mode 100644 index 0000000000..4737c444a0 --- /dev/null +++ b/apps/test-react-router/app/routes/radio-group.tsx @@ -0,0 +1,5 @@ +import * as RadioGroup from '@repo/test-registry/components/radio-group'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/roving-focus.tsx b/apps/test-react-router/app/routes/roving-focus.tsx new file mode 100644 index 0000000000..66e1cec87c --- /dev/null +++ b/apps/test-react-router/app/routes/roving-focus.tsx @@ -0,0 +1,12 @@ +import * as RovingFocus from '@repo/test-registry/components/roving-focus'; + +export default function Page() { + return ( + <> +

Basic

+ +

Nested

+ + + ); +} diff --git a/apps/test-react-router/app/routes/scroll-area.tsx b/apps/test-react-router/app/routes/scroll-area.tsx new file mode 100644 index 0000000000..2975e15004 --- /dev/null +++ b/apps/test-react-router/app/routes/scroll-area.tsx @@ -0,0 +1,5 @@ +import * as ScrollArea from '@repo/test-registry/components/scroll-area'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/select.tsx b/apps/test-react-router/app/routes/select.tsx new file mode 100644 index 0000000000..ebab628536 --- /dev/null +++ b/apps/test-react-router/app/routes/select.tsx @@ -0,0 +1,5 @@ +import * as Select from '@repo/test-registry/components/select'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/separator.tsx b/apps/test-react-router/app/routes/separator.tsx new file mode 100644 index 0000000000..befc175921 --- /dev/null +++ b/apps/test-react-router/app/routes/separator.tsx @@ -0,0 +1,5 @@ +import * as Separator from '@repo/test-registry/components/separator'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/slider.tsx b/apps/test-react-router/app/routes/slider.tsx new file mode 100644 index 0000000000..f44843a0f6 --- /dev/null +++ b/apps/test-react-router/app/routes/slider.tsx @@ -0,0 +1,5 @@ +import * as Slider from '@repo/test-registry/components/slider'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/slot.tsx b/apps/test-react-router/app/routes/slot.tsx new file mode 100644 index 0000000000..68d54c208a --- /dev/null +++ b/apps/test-react-router/app/routes/slot.tsx @@ -0,0 +1,5 @@ +import * as Slot from '@repo/test-registry/components/slot'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/switch.tsx b/apps/test-react-router/app/routes/switch.tsx new file mode 100644 index 0000000000..7a331ff84d --- /dev/null +++ b/apps/test-react-router/app/routes/switch.tsx @@ -0,0 +1,5 @@ +import * as Switch from '@repo/test-registry/components/switch'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/tabs.tsx b/apps/test-react-router/app/routes/tabs.tsx new file mode 100644 index 0000000000..aa763ecde8 --- /dev/null +++ b/apps/test-react-router/app/routes/tabs.tsx @@ -0,0 +1,5 @@ +import * as Tabs from '@repo/test-registry/components/tabs'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/toast.tsx b/apps/test-react-router/app/routes/toast.tsx new file mode 100644 index 0000000000..e5a76bb8b3 --- /dev/null +++ b/apps/test-react-router/app/routes/toast.tsx @@ -0,0 +1,5 @@ +import * as Toast from '@repo/test-registry/components/toast'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/toggle-group.tsx b/apps/test-react-router/app/routes/toggle-group.tsx new file mode 100644 index 0000000000..a7bea7f0e0 --- /dev/null +++ b/apps/test-react-router/app/routes/toggle-group.tsx @@ -0,0 +1,5 @@ +import * as ToggleGroup from '@repo/test-registry/components/toggle-group'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/toggle.tsx b/apps/test-react-router/app/routes/toggle.tsx new file mode 100644 index 0000000000..df722f71cf --- /dev/null +++ b/apps/test-react-router/app/routes/toggle.tsx @@ -0,0 +1,5 @@ +import * as Toggle from '@repo/test-registry/components/toggle'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/toolbar.tsx b/apps/test-react-router/app/routes/toolbar.tsx new file mode 100644 index 0000000000..31024ad77f --- /dev/null +++ b/apps/test-react-router/app/routes/toolbar.tsx @@ -0,0 +1,5 @@ +import * as Toolbar from '@repo/test-registry/components/toolbar'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/tooltip.tsx b/apps/test-react-router/app/routes/tooltip.tsx new file mode 100644 index 0000000000..efe77a52e8 --- /dev/null +++ b/apps/test-react-router/app/routes/tooltip.tsx @@ -0,0 +1,5 @@ +import * as Tooltip from '@repo/test-registry/components/tooltip'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/app/routes/visually-hidden.tsx b/apps/test-react-router/app/routes/visually-hidden.tsx new file mode 100644 index 0000000000..e20db888d1 --- /dev/null +++ b/apps/test-react-router/app/routes/visually-hidden.tsx @@ -0,0 +1,5 @@ +import * as VisuallyHidden from '@repo/test-registry/components/visually-hidden'; + +export default function Page() { + return ; +} diff --git a/apps/test-react-router/eslint.config.js b/apps/test-react-router/eslint.config.js new file mode 100644 index 0000000000..726acba12b --- /dev/null +++ b/apps/test-react-router/eslint.config.js @@ -0,0 +1,31 @@ +// @ts-check +import { defineConfig, globalIgnores } from 'eslint/config'; +import { configs } from '@repo/eslint-config/vite'; + +export default defineConfig([ + globalIgnores(['dist']), + ...configs, + { + rules: { + 'react-refresh/only-export-components': [ + 'error', + { + allowExportNames: [ + // https://reactrouter.com/start/framework/route-module + 'middleware', + 'clientMiddleware', + 'loader', + 'clientLoader', + 'action', + 'clientAction', + 'headers', + 'handle', + 'links', + 'meta', + 'shouldRevalidate', + ], + }, + ], + }, + }, +]); diff --git a/apps/test-react-router/package.json b/apps/test-react-router/package.json new file mode 100644 index 0000000000..5059283455 --- /dev/null +++ b/apps/test-react-router/package.json @@ -0,0 +1,38 @@ +{ + "name": "@repo/test-react-router", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "react-router build", + "dev": "react-router dev -p 3004", + "start": "react-router-serve ./build/server/index.js", + "typecheck": "react-router typegen && tsc" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@react-router/node": "^7.9.5", + "@react-router/serve": "^7.9.5", + "@repo/test-data": "workspace:*", + "@repo/test-registry": "workspace:*", + "@repo/test-styles": "workspace:*", + "isbot": "^5.1.32", + "radix-ui": "workspace:*", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router": "^7.9.5" + }, + "devDependencies": { + "@react-router/dev": "^7.9.5", + "@repo/eslint-config": "workspace:*", + "@types/node": "^22", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "eslint": "^9.38.0", + "eslint-plugin-react-refresh": "^0.4.22", + "typescript": "^5.9.3", + "vite": "^7.1.12", + "vite-plugin-devtools-json": "^1.0.0", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/apps/test-react-router/public/favicon.ico b/apps/test-react-router/public/favicon.ico new file mode 100644 index 0000000000..5dbdfcddcb Binary files /dev/null and b/apps/test-react-router/public/favicon.ico differ diff --git a/apps/test-react-router/react-router.config.ts b/apps/test-react-router/react-router.config.ts new file mode 100644 index 0000000000..6ff16f9177 --- /dev/null +++ b/apps/test-react-router/react-router.config.ts @@ -0,0 +1,7 @@ +import type { Config } from "@react-router/dev/config"; + +export default { + // Config options... + // Server-side render by default, to enable SPA mode set this to `false` + ssr: true, +} satisfies Config; diff --git a/apps/test-react-router/tsconfig.json b/apps/test-react-router/tsconfig.json new file mode 100644 index 0000000000..dc391a45f7 --- /dev/null +++ b/apps/test-react-router/tsconfig.json @@ -0,0 +1,27 @@ +{ + "include": [ + "**/*", + "**/.server/**/*", + "**/.client/**/*", + ".react-router/types/**/*" + ], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["node", "vite/client"], + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "rootDirs": [".", "./.react-router/types"], + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true + } +} diff --git a/apps/test-react-router/vite.config.ts b/apps/test-react-router/vite.config.ts new file mode 100644 index 0000000000..b91e8a413b --- /dev/null +++ b/apps/test-react-router/vite.config.ts @@ -0,0 +1,8 @@ +import { reactRouter } from '@react-router/dev/vite'; +import { defineConfig } from 'vite'; +import devtoolsJson from 'vite-plugin-devtools-json'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [reactRouter(), tsconfigPaths(), devtoolsJson()], +}); diff --git a/apps/test-vite-react-17/.gitignore b/apps/test-vite-react-17/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/apps/test-vite-react-17/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/test-vite-react-17/README.md b/apps/test-vite-react-17/README.md new file mode 100644 index 0000000000..c4579af556 --- /dev/null +++ b/apps/test-vite-react-17/README.md @@ -0,0 +1,21 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +## Installation + +Install the dependencies: + +```bash +pnpm install +``` + +## Development + +Start the development server with HMR: + +```bash +pnpm dev +# or from the root of the repo +pnpm dev:vite-react-17 +``` diff --git a/apps/test-vite-react-17/eslint.config.js b/apps/test-vite-react-17/eslint.config.js new file mode 100644 index 0000000000..90a46a235b --- /dev/null +++ b/apps/test-vite-react-17/eslint.config.js @@ -0,0 +1,5 @@ +// @ts-check +import { defineConfig, globalIgnores } from 'eslint/config'; +import { configs } from '@repo/eslint-config/vite'; + +export default defineConfig([globalIgnores(['dist']), ...configs]); diff --git a/apps/test-vite-react-17/index.html b/apps/test-vite-react-17/index.html new file mode 100644 index 0000000000..f621db35a8 --- /dev/null +++ b/apps/test-vite-react-17/index.html @@ -0,0 +1,13 @@ + + + + + + + test-vite-react-17 + + +
+ + + diff --git a/apps/test-vite-react-17/package.json b/apps/test-vite-react-17/package.json new file mode 100644 index 0000000000..cb8e929fe8 --- /dev/null +++ b/apps/test-vite-react-17/package.json @@ -0,0 +1,32 @@ +{ + "name": "@repo/test-vite-react-17", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite -p 3006", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@repo/test-data": "workspace:*", + "@repo/test-registry": "workspace:*", + "@repo/test-styles": "workspace:*", + "radix-ui": "workspace:*", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@types/node": "^22", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.38.0", + "eslint-plugin-react-refresh": "^0.4.22", + "typescript": "~5.9.3", + "vite": "^7.1.12" + } +} diff --git a/apps/test-vite-react-17/public/vite.svg b/apps/test-vite-react-17/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/apps/test-vite-react-17/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/test-vite-react-17/src/app.tsx b/apps/test-vite-react-17/src/app.tsx new file mode 100644 index 0000000000..388ce36dd5 --- /dev/null +++ b/apps/test-vite-react-17/src/app.tsx @@ -0,0 +1,3 @@ +export function App() { + return
Home
; +} diff --git a/apps/test-vite-react-17/src/assets/react.svg b/apps/test-vite-react-17/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/apps/test-vite-react-17/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/test-vite-react-17/src/globals.css b/apps/test-vite-react-17/src/globals.css new file mode 100644 index 0000000000..9fbccd9b03 --- /dev/null +++ b/apps/test-vite-react-17/src/globals.css @@ -0,0 +1,4 @@ +@import '@repo/test-styles/resets.css'; +@import '@repo/test-styles/colors.css'; +@import '@repo/test-styles/global.css'; +@import '@repo/test-styles/components.css'; diff --git a/apps/test-vite-react-17/src/main.tsx b/apps/test-vite-react-17/src/main.tsx new file mode 100644 index 0000000000..071905a46a --- /dev/null +++ b/apps/test-vite-react-17/src/main.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { App } from './app.tsx'; +import './globals.css'; + +const rootElement = document.getElementById('root')!; +// @ts-expect-error +// eslint-disable-next-line react/no-deprecated +ReactDOM.render( + + + , + rootElement, +); diff --git a/apps/test-vite-react-17/tsconfig.app.json b/apps/test-vite-react-17/tsconfig.app.json new file mode 100644 index 0000000000..a9b5a59ca6 --- /dev/null +++ b/apps/test-vite-react-17/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/apps/test-vite-react-17/tsconfig.json b/apps/test-vite-react-17/tsconfig.json new file mode 100644 index 0000000000..1ffef600d9 --- /dev/null +++ b/apps/test-vite-react-17/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/test-vite-react-17/tsconfig.node.json b/apps/test-vite-react-17/tsconfig.node.json new file mode 100644 index 0000000000..8a67f62f4c --- /dev/null +++ b/apps/test-vite-react-17/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/test-vite-react-17/vite.config.ts b/apps/test-vite-react-17/vite.config.ts new file mode 100644 index 0000000000..8b0f57b91a --- /dev/null +++ b/apps/test-vite-react-17/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/apps/test-vite-react-18/.gitignore b/apps/test-vite-react-18/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/apps/test-vite-react-18/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/test-vite-react-18/README.md b/apps/test-vite-react-18/README.md new file mode 100644 index 0000000000..baf67d8b3c --- /dev/null +++ b/apps/test-vite-react-18/README.md @@ -0,0 +1,21 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +## Installation + +Install the dependencies: + +```bash +pnpm install +``` + +## Development + +Start the development server with HMR: + +```bash +pnpm dev +# or from the root of the repo +pnpm dev:vite-react-18 +``` diff --git a/apps/test-vite-react-18/eslint.config.js b/apps/test-vite-react-18/eslint.config.js new file mode 100644 index 0000000000..90a46a235b --- /dev/null +++ b/apps/test-vite-react-18/eslint.config.js @@ -0,0 +1,5 @@ +// @ts-check +import { defineConfig, globalIgnores } from 'eslint/config'; +import { configs } from '@repo/eslint-config/vite'; + +export default defineConfig([globalIgnores(['dist']), ...configs]); diff --git a/apps/test-vite-react-18/index.html b/apps/test-vite-react-18/index.html new file mode 100644 index 0000000000..7bc904ec6c --- /dev/null +++ b/apps/test-vite-react-18/index.html @@ -0,0 +1,13 @@ + + + + + + + test-vite-react-18 + + +
+ + + diff --git a/apps/test-vite-react-18/package.json b/apps/test-vite-react-18/package.json new file mode 100644 index 0000000000..8ff98038de --- /dev/null +++ b/apps/test-vite-react-18/package.json @@ -0,0 +1,32 @@ +{ + "name": "@repo/test-vite-react-18", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite -p 3007", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@repo/test-data": "workspace:*", + "@repo/test-registry": "workspace:*", + "@repo/test-styles": "workspace:*", + "radix-ui": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@types/node": "^22", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.38.0", + "eslint-plugin-react-refresh": "^0.4.22", + "typescript": "~5.9.3", + "vite": "^7.1.12" + } +} diff --git a/apps/test-vite-react-18/public/vite.svg b/apps/test-vite-react-18/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/apps/test-vite-react-18/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/test-vite-react-18/src/app.tsx b/apps/test-vite-react-18/src/app.tsx new file mode 100644 index 0000000000..388ce36dd5 --- /dev/null +++ b/apps/test-vite-react-18/src/app.tsx @@ -0,0 +1,3 @@ +export function App() { + return
Home
; +} diff --git a/apps/test-vite-react-18/src/assets/react.svg b/apps/test-vite-react-18/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/apps/test-vite-react-18/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/test-vite-react-18/src/globals.css b/apps/test-vite-react-18/src/globals.css new file mode 100644 index 0000000000..9fbccd9b03 --- /dev/null +++ b/apps/test-vite-react-18/src/globals.css @@ -0,0 +1,4 @@ +@import '@repo/test-styles/resets.css'; +@import '@repo/test-styles/colors.css'; +@import '@repo/test-styles/global.css'; +@import '@repo/test-styles/components.css'; diff --git a/apps/test-vite-react-18/src/main.tsx b/apps/test-vite-react-18/src/main.tsx new file mode 100644 index 0000000000..df6f3a83ef --- /dev/null +++ b/apps/test-vite-react-18/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './app.tsx'; +import './globals.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/apps/test-vite-react-18/tsconfig.app.json b/apps/test-vite-react-18/tsconfig.app.json new file mode 100644 index 0000000000..a9b5a59ca6 --- /dev/null +++ b/apps/test-vite-react-18/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/apps/test-vite-react-18/tsconfig.json b/apps/test-vite-react-18/tsconfig.json new file mode 100644 index 0000000000..1ffef600d9 --- /dev/null +++ b/apps/test-vite-react-18/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/test-vite-react-18/tsconfig.node.json b/apps/test-vite-react-18/tsconfig.node.json new file mode 100644 index 0000000000..8a67f62f4c --- /dev/null +++ b/apps/test-vite-react-18/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/test-vite-react-18/vite.config.ts b/apps/test-vite-react-18/vite.config.ts new file mode 100644 index 0000000000..8b0f57b91a --- /dev/null +++ b/apps/test-vite-react-18/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/apps/test-vite-react-19/.gitignore b/apps/test-vite-react-19/.gitignore new file mode 100644 index 0000000000..a547bf36d8 --- /dev/null +++ b/apps/test-vite-react-19/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/test-vite-react-19/README.md b/apps/test-vite-react-19/README.md new file mode 100644 index 0000000000..a7f7c36d42 --- /dev/null +++ b/apps/test-vite-react-19/README.md @@ -0,0 +1,21 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +## Installation + +Install the dependencies: + +```bash +pnpm install +``` + +## Development + +Start the development server with HMR: + +```bash +pnpm dev +# or from the root of the repo +pnpm dev:vite-react-19 +``` diff --git a/apps/test-vite-react-19/eslint.config.js b/apps/test-vite-react-19/eslint.config.js new file mode 100644 index 0000000000..90a46a235b --- /dev/null +++ b/apps/test-vite-react-19/eslint.config.js @@ -0,0 +1,5 @@ +// @ts-check +import { defineConfig, globalIgnores } from 'eslint/config'; +import { configs } from '@repo/eslint-config/vite'; + +export default defineConfig([globalIgnores(['dist']), ...configs]); diff --git a/apps/test-vite-react-19/index.html b/apps/test-vite-react-19/index.html new file mode 100644 index 0000000000..c2c866da32 --- /dev/null +++ b/apps/test-vite-react-19/index.html @@ -0,0 +1,13 @@ + + + + + + + test-vite-react-19 + + +
+ + + diff --git a/apps/test-vite-react-19/package.json b/apps/test-vite-react-19/package.json new file mode 100644 index 0000000000..837b3d24ab --- /dev/null +++ b/apps/test-vite-react-19/package.json @@ -0,0 +1,32 @@ +{ + "name": "@repo/test-vite-react-19", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite -p 3008", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@repo/test-data": "workspace:*", + "@repo/test-registry": "workspace:*", + "@repo/test-styles": "workspace:*", + "radix-ui": "workspace:*", + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@types/node": "^22", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.38.0", + "eslint-plugin-react-refresh": "^0.4.22", + "typescript": "~5.9.3", + "vite": "^7.1.12" + } +} diff --git a/apps/test-vite-react-19/public/vite.svg b/apps/test-vite-react-19/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/apps/test-vite-react-19/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/test-vite-react-19/src/app.tsx b/apps/test-vite-react-19/src/app.tsx new file mode 100644 index 0000000000..388ce36dd5 --- /dev/null +++ b/apps/test-vite-react-19/src/app.tsx @@ -0,0 +1,3 @@ +export function App() { + return
Home
; +} diff --git a/apps/test-vite-react-19/src/assets/react.svg b/apps/test-vite-react-19/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/apps/test-vite-react-19/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/test-vite-react-19/src/globals.css b/apps/test-vite-react-19/src/globals.css new file mode 100644 index 0000000000..9fbccd9b03 --- /dev/null +++ b/apps/test-vite-react-19/src/globals.css @@ -0,0 +1,4 @@ +@import '@repo/test-styles/resets.css'; +@import '@repo/test-styles/colors.css'; +@import '@repo/test-styles/global.css'; +@import '@repo/test-styles/components.css'; diff --git a/apps/test-vite-react-19/src/main.tsx b/apps/test-vite-react-19/src/main.tsx new file mode 100644 index 0000000000..df6f3a83ef --- /dev/null +++ b/apps/test-vite-react-19/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './app.tsx'; +import './globals.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/apps/test-vite-react-19/tsconfig.app.json b/apps/test-vite-react-19/tsconfig.app.json new file mode 100644 index 0000000000..a9b5a59ca6 --- /dev/null +++ b/apps/test-vite-react-19/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/apps/test-vite-react-19/tsconfig.json b/apps/test-vite-react-19/tsconfig.json new file mode 100644 index 0000000000..1ffef600d9 --- /dev/null +++ b/apps/test-vite-react-19/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/test-vite-react-19/tsconfig.node.json b/apps/test-vite-react-19/tsconfig.node.json new file mode 100644 index 0000000000..8a67f62f4c --- /dev/null +++ b/apps/test-vite-react-19/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/test-vite-react-19/vite.config.ts b/apps/test-vite-react-19/vite.config.ts new file mode 100644 index 0000000000..8b0f57b91a --- /dev/null +++ b/apps/test-vite-react-19/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/apps/ssr-testing/app/accessible-icon/page.tsx b/internal/test-registry/components/accessible-icon.tsx similarity index 67% rename from apps/ssr-testing/app/accessible-icon/page.tsx rename to internal/test-registry/components/accessible-icon.tsx index b6ad8fe79f..bda1c1789f 100644 --- a/apps/ssr-testing/app/accessible-icon/page.tsx +++ b/internal/test-registry/components/accessible-icon.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; +import { Cross1Icon } from '@radix-ui/react-icons'; import { AccessibleIcon } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( ); diff --git a/apps/ssr-testing/app/accordion/page.tsx b/internal/test-registry/components/accordion.tsx similarity index 51% rename from apps/ssr-testing/app/accordion/page.tsx rename to internal/test-registry/components/accordion.tsx index af20e290d8..14f8b9f4f1 100644 --- a/apps/ssr-testing/app/accordion/page.tsx +++ b/internal/test-registry/components/accordion.tsx @@ -1,42 +1,42 @@ import * as React from 'react'; import { Accordion } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( - - - - One + + + + One - + Per erat orci nostra luctus sociosqu mus risus penatibus, duis elit vulputate viverra integer ullamcorper congue curabitur sociis, nisi malesuada scelerisque quam suscipit habitant sed. - - - Two + + + Two - + Cursus sed mattis commodo fermentum conubia ipsum pulvinar sagittis, diam eget bibendum porta nascetur ac dictum, leo tellus dis integer platea ultrices mi. - - - Three (disabled) + + + Three (disabled) - + Sociis hac sapien turpis conubia sagittis justo dui, inceptos penatibus feugiat himenaeos euismod magna, nec tempor pulvinar eu etiam mattis. - - - Four + + + Four - + Odio placerat quisque sapien sagittis non sociis ligula penatibus dignissim vitae, enim vulputate nullam semper potenti etiam volutpat libero. diff --git a/apps/ssr-testing/app/alert-dialog/page.tsx b/internal/test-registry/components/alert-dialog.tsx similarity index 95% rename from apps/ssr-testing/app/alert-dialog/page.tsx rename to internal/test-registry/components/alert-dialog.tsx index be897f8ea6..bd79d9532e 100644 --- a/apps/ssr-testing/app/alert-dialog/page.tsx +++ b/internal/test-registry/components/alert-dialog.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { AlertDialog } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( delete everything diff --git a/internal/test-registry/components/arrow.tsx b/internal/test-registry/components/arrow.tsx new file mode 100644 index 0000000000..9a4c2eb31b --- /dev/null +++ b/internal/test-registry/components/arrow.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import { Arrow } from 'radix-ui/internal'; + +export function Basic() { + return ; +} diff --git a/internal/test-registry/components/aspect-ratio.tsx b/internal/test-registry/components/aspect-ratio.tsx new file mode 100644 index 0000000000..ce477b7d81 --- /dev/null +++ b/internal/test-registry/components/aspect-ratio.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import { AspectRatio } from 'radix-ui'; + +export function Basic() { + return ( +
+ + Landscape photograph by Tobias Tullius + +
+ ); +} diff --git a/apps/ssr-testing/app/avatar/page.tsx b/internal/test-registry/components/avatar.tsx similarity index 87% rename from apps/ssr-testing/app/avatar/page.tsx rename to internal/test-registry/components/avatar.tsx index 64faea3bc6..bc84b67f1d 100644 --- a/apps/ssr-testing/app/avatar/page.tsx +++ b/internal/test-registry/components/avatar.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Avatar } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( A diff --git a/apps/ssr-testing/app/checkbox/page.tsx b/internal/test-registry/components/checkbox.tsx similarity index 84% rename from apps/ssr-testing/app/checkbox/page.tsx rename to internal/test-registry/components/checkbox.tsx index 5d76ebb2be..66f29158b6 100644 --- a/apps/ssr-testing/app/checkbox/page.tsx +++ b/internal/test-registry/components/checkbox.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Checkbox } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( [ โœ” ] diff --git a/apps/ssr-testing/app/collapsible/page.tsx b/internal/test-registry/components/collapsible.tsx similarity index 88% rename from apps/ssr-testing/app/collapsible/page.tsx rename to internal/test-registry/components/collapsible.tsx index 53c0ead329..d4545eace9 100644 --- a/apps/ssr-testing/app/collapsible/page.tsx +++ b/internal/test-registry/components/collapsible.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Collapsible } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( Trigger diff --git a/internal/test-registry/components/collection.tsx b/internal/test-registry/components/collection.tsx new file mode 100644 index 0000000000..29060e37aa --- /dev/null +++ b/internal/test-registry/components/collection.tsx @@ -0,0 +1,103 @@ +'use client'; +import * as React from 'react'; +import { Collection as CollectionPrimitive } from 'radix-ui/internal'; +import type { Context } from 'radix-ui/internal'; + +const ElementHashMap = new WeakMap(); + +const [ + UnstableCollection, + { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + useCollection: useUnstableCollection, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + createCollectionScope: createUnstableCollectionScope, + useInitCollection: useInitUnstableCollection, + }, +] = CollectionPrimitive.unstable_createCollection< + HTMLDivElement, + { + test: number; + // TODO: remove when package is updated to extend BaseItemData internally + id?: string; + } +>('UNSTABLE'); + +const [ + Collection, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + useCollection, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + createCollectionScope, +] = CollectionPrimitive.createCollection('STABLE'); + +type ScopedProps

= P & { __scopeCollection?: Context.Scope }; + +export function Unstable(props: ScopedProps<{ children: React.ReactNode }>) { + const collectionState = useInitUnstableCollection(); + const [collection] = collectionState; + + return ( +

+ {collection.size ? ( +
+ {Array.from(collection).map(([element, { element: _, ...data }]) => { + let key = ElementHashMap.get(element); + if (!key) { + key = crypto.randomUUID(); + } + return ( + +
Item content: {element.textContent}
+
Item data: {JSON.stringify(data)}
+
+ ); + })} +
+ ) : ( +

No items in collection

+ )} +
+ + +
{props.children}
+
+
+
+ ); +} + +export function UnstableItem(props: ScopedProps<{ children: React.ReactNode }>) { + return ( + +
{props.children}
+
+ ); +} + +export function Stable(props: ScopedProps<{ children: React.ReactNode }>) { + return ( + + +
{props.children}
+
+
+ ); +} + +export function StableItem(props: ScopedProps<{ children: React.ReactNode }>) { + return ( + +
{props.children}
+
+ ); +} + +function getElementKey(element: HTMLDivElement) { + let key = ElementHashMap.get(element); + if (!key) { + key = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + ElementHashMap.set(element, key); + } + return key; +} diff --git a/apps/ssr-testing/app/context-menu/page.tsx b/internal/test-registry/components/context-menu.tsx similarity index 95% rename from apps/ssr-testing/app/context-menu/page.tsx rename to internal/test-registry/components/context-menu.tsx index 5dc7fd0bb7..0169cd0c3f 100644 --- a/apps/ssr-testing/app/context-menu/page.tsx +++ b/internal/test-registry/components/context-menu.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { ContextMenu } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( Right click here diff --git a/apps/ssr-testing/app/dialog/page.tsx b/internal/test-registry/components/dialog.tsx similarity index 93% rename from apps/ssr-testing/app/dialog/page.tsx rename to internal/test-registry/components/dialog.tsx index 7df2aa8b3e..a15587e751 100644 --- a/apps/ssr-testing/app/dialog/page.tsx +++ b/internal/test-registry/components/dialog.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Dialog } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( open diff --git a/internal/test-registry/components/direction.tsx b/internal/test-registry/components/direction.tsx new file mode 100644 index 0000000000..3d2a9bb471 --- /dev/null +++ b/internal/test-registry/components/direction.tsx @@ -0,0 +1,16 @@ +'use client'; +import * as React from 'react'; +import { Direction } from 'radix-ui'; + +export function Basic({ dir }: { dir: 'ltr' | 'rtl' }) { + return ( + + + + ); +} + +function DirectionReader() { + const dir = Direction.useDirection(); + return
Direction is {dir}
; +} diff --git a/internal/test-registry/components/dismissable-layer.tsx b/internal/test-registry/components/dismissable-layer.tsx new file mode 100644 index 0000000000..c8b3041cc9 --- /dev/null +++ b/internal/test-registry/components/dismissable-layer.tsx @@ -0,0 +1,64 @@ +'use client'; +import * as React from 'react'; +import { DismissableLayer as DismissableLayerPrimitive } from 'radix-ui/internal'; + +export function Basic() { + const [key, setKey] = React.useState(''); + return ( +
+ +
+ +
+ ); +} + +function DismissableLayerImpl() { + const [disableOutsidePointerEvents, setDisableOutsidePointerEvents] = React.useState(false); + const [dismissed, setDismissed] = React.useState(false); + return ( +
+ {dismissed &&

Dismissed!

} + +
+ { + console.log('escape key down', event); + }} + onPointerDownOutside={(event) => { + console.log('pointer down outside', event); + }} + onFocusOutside={(event) => { + console.log('focus outside', event); + }} + onInteractOutside={(event) => { + console.log('interact outside', event); + }} + onDismiss={() => { + setDismissed(true); + console.log('dismiss'); + }} + > +
+

Hello!

+ +
+
+
+ ); +} diff --git a/apps/ssr-testing/app/dropdown-menu/page.tsx b/internal/test-registry/components/dropdown-menu.tsx similarity index 95% rename from apps/ssr-testing/app/dropdown-menu/page.tsx rename to internal/test-registry/components/dropdown-menu.tsx index 2a0d494f92..b5cf8a897e 100644 --- a/apps/ssr-testing/app/dropdown-menu/page.tsx +++ b/internal/test-registry/components/dropdown-menu.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { DropdownMenu } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( Open diff --git a/internal/test-registry/components/focus-guards.tsx b/internal/test-registry/components/focus-guards.tsx new file mode 100644 index 0000000000..8d782690b8 --- /dev/null +++ b/internal/test-registry/components/focus-guards.tsx @@ -0,0 +1,27 @@ +'use client'; +import * as React from 'react'; +import { FocusGuards as FocusGuardsPrimitive } from 'radix-ui/internal'; + +export function Basic() { + const [disabled, setDisabled] = React.useState(false); + return ( +
+ + +
+

Hello!

+
+
+
+ ); +} + +function FocusGuards({ children, disabled }: { children: React.ReactNode; disabled: boolean }) { + return disabled ? ( + children + ) : ( + {children} + ); +} diff --git a/internal/test-registry/components/focus-scope.tsx b/internal/test-registry/components/focus-scope.tsx new file mode 100644 index 0000000000..b52934ca20 --- /dev/null +++ b/internal/test-registry/components/focus-scope.tsx @@ -0,0 +1,263 @@ +'use client'; +import React from 'react'; +import { FocusScope } from 'radix-ui/internal'; + +export function Basic() { + const [trapped, setTrapped] = React.useState(false); + const [hasDestroyButton, setHasDestroyButton] = React.useState(true); + + return ( + <> +
+ {' '} + +
+ {trapped ? ( + +
+ + + + {hasDestroyButton && ( +
+ +
+ )} + +
+
+ ) : null} +
+ +
+ + ); +} + +export function Multiple() { + const [trapped1, setTrapped1] = React.useState(false); + const [trapped2, setTrapped2] = React.useState(false); + + return ( +
+
+ +
+ {trapped1 ? ( + +
+

One

+ + + + +
+
+ ) : null} + +
+ +
+ {trapped2 ? ( + +
+

Two

+ + + + +
+
+ ) : null} +
+ +
+
+ ); +} + +// true => default focus, false => no focus, ref => focus element +type FocusParam = boolean | React.RefObject; + +export const WithOptions = () => { + const [open, setOpen] = React.useState(false); + const [isEmptyForm, setIsEmptyForm] = React.useState(false); + + const [trapFocus, setTrapFocus] = React.useState(false); + const [focusOnMount, setFocusOnMount] = React.useState(false); + const [focusOnUnmount, setFocusOnUnmount] = React.useState(false); + + const ageFieldRef = React.useRef(null); + const nextButtonRef = React.useRef(null); + + return ( +
+

FocusScope

+ +
+ + + {focusOnMount !== false && !isEmptyForm && ( + + )} + {focusOnMount !== false && ( + + )} + + {focusOnUnmount !== false && ( + + )} +
+ +
+ +
+ + + + {open ? ( + { + if (focusOnMount !== true) { + event.preventDefault(); + if (focusOnMount) focusOnMount.current?.focus(); + } + }} + onUnmountAutoFocus={(event) => { + if (focusOnUnmount !== true) { + event.preventDefault(); + if (focusOnUnmount) focusOnUnmount.current?.focus(); + } + }} + > +
+ {!isEmptyForm && ( + <> + + + + + + )} +
+
+ ) : null} + + +
+ ); +}; diff --git a/apps/ssr-testing/app/form/page.tsx b/internal/test-registry/components/form.tsx similarity index 92% rename from apps/ssr-testing/app/form/page.tsx rename to internal/test-registry/components/form.tsx index 119722f486..ea62cea12d 100644 --- a/apps/ssr-testing/app/form/page.tsx +++ b/internal/test-registry/components/form.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Form } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( diff --git a/apps/ssr-testing/app/hover-card/page.tsx b/internal/test-registry/components/hover-card.tsx similarity index 91% rename from apps/ssr-testing/app/hover-card/page.tsx rename to internal/test-registry/components/hover-card.tsx index 2f4da36ea6..de6c9620f5 100644 --- a/apps/ssr-testing/app/hover-card/page.tsx +++ b/internal/test-registry/components/hover-card.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { HoverCard } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( Hover me diff --git a/apps/ssr-testing/app/label/page.tsx b/internal/test-registry/components/label.tsx similarity index 76% rename from apps/ssr-testing/app/label/page.tsx rename to internal/test-registry/components/label.tsx index 2ca477956e..62dc3938b3 100644 --- a/apps/ssr-testing/app/label/page.tsx +++ b/internal/test-registry/components/label.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Label } from 'radix-ui'; -export default function Page() { +export function Basic() { return Label; } diff --git a/internal/test-registry/components/menu.tsx b/internal/test-registry/components/menu.tsx new file mode 100644 index 0000000000..d2915e1e8b --- /dev/null +++ b/internal/test-registry/components/menu.tsx @@ -0,0 +1,393 @@ +'use client'; +import * as React from 'react'; +import { Direction } from 'radix-ui'; +import { Menu } from 'radix-ui/internal'; +import { foodGroups } from '@repo/test-data/foods'; + +export function Basic() { + const [animated, setAnimated] = React.useState(false); + return ( +
+ +
+ + window.alert('undo')}> + Undo + + window.alert('redo')}> + Redo + + + window.alert('cut')}> + Cut + + window.alert('copy')}> + Copy + + window.alert('paste')}> + Paste + + +
+ ); +} + +export function Submenus() { + const [open1, setOpen1] = React.useState(false); + const [open2, setOpen2] = React.useState(false); + const [open3, setOpen3] = React.useState(false); + const [open4, setOpen4] = React.useState(false); + const [rtl, setRtl] = React.useState(false); + const [animated, setAnimated] = React.useState(false); + + React.useEffect(() => { + if (rtl) { + document.documentElement.setAttribute('dir', 'rtl'); + return () => document.documentElement.removeAttribute('dir'); + } + }, [rtl]); + + return ( + +
+ + +
+ + window.alert('undo')}> + Undo + + + + Disabled + + window.alert('one')}> + One + + + window.alert('one')}> + One + + window.alert('two')}> + Two + + window.alert('three')}> + Three + + window.alert('four')}> + Four + + window.alert('five')}> + Five + + window.alert('six')}> + Six + + + + window.alert('one')}> + One + + window.alert('two')}> + Two + + window.alert('three')}> + Three + + + window.alert('two')}>Two + + window.alert('one')}> + One + + window.alert('two')}> + Two + + window.alert('three')}> + Three + + + window.alert('three')}> + Three + + + + + window.alert('cut')}> + Cut + + window.alert('copy')}> + Copy + + window.alert('paste')}> + Paste + + +
+ ); +} + +export function WithLabels() { + return ( + + {foodGroups.map((foodGroup, index) => ( + + {foodGroup.label && ( + + {foodGroup.label} + + )} + {foodGroup.foods.map((food) => ( + window.alert(food.label)} + className="MenuItem" + > + {food.label} + + ))} + {index < foodGroups.length - 1 && } + + ))} + + ); +} + +const suits = [ + { emoji: 'โ™ฅ๏ธ', label: 'Hearts' }, + { emoji: 'โ™ ๏ธ', label: 'Spades' }, + { emoji: 'โ™ฆ๏ธ', label: 'Diamonds' }, + { emoji: 'โ™ฃ๏ธ', label: 'Clubs' }, +]; + +export function Typeahead() { + return ( + <> +

Testing ground for typeahead behavior

+
+
+

Text labels

+
+

For comparison try the closed select below

+ +
+ +
+ +
+

Complex children

+

(relying on `.textContent` โ€” default)

+ + {suits.map((suit) => ( + + {suit.label} + + {suit.emoji} + + + ))} + +
+ +
+

Complex children

+

(with explicit `textValue` prop)

+ + {suits.map((suit) => ( + + + {suit.emoji} + + {suit.label} + + ))} + +
+
+ + ); +} + +export function CheckboxItems() { + const options = ['Crows', 'Ravens', 'Magpies', 'Jackdaws']; + + const [selection, setSelection] = React.useState([]); + + const handleSelectAll = () => { + setSelection((currentSelection) => (currentSelection.length === options.length ? [] : options)); + }; + + return ( + + + Select all + + {selection.length === options.length ? : 'โ€”'} + + + + {options.map((option) => ( + + setSelection((current) => + current.includes(option) + ? current.filter((el) => el !== option) + : current.concat(option), + ) + } + > + {option} + + + + + ))} + + ); +} + +export function RadioItems() { + const files = ['README.md', 'index.js', 'page.css']; + const [file, setFile] = React.useState(files[1]); + + return ( + + window.alert('minimize')}> + Minimize window + + window.alert('zoom')}> + Zoom + + window.alert('smaller')}> + Smaller + + + + {files.map((file) => ( + + {file} + + + + + ))} + + + ); +} + +type MenuProps = Omit< + React.ComponentProps & + React.ComponentProps & { animated?: boolean }, + 'trapFocus' | 'onCloseAutoFocus' | 'disableOutsidePointerEvents' | 'disableOutsideScroll' +>; + +function MenuWithAnchor(props: MenuProps) { + const { open = true, children, animated, ...contentProps } = props; + return ( + {}} modal={false}> + {/* inline-block allows anchor to move when rtl changes on document */} + + + event.preventDefault()} + align="start" + data-animated={animated || undefined} + {...contentProps} + className={['MenuContent', contentProps.className].filter(Boolean).join(' ')} + > + {children} + + + + ); +} + +function Submenu(props: MenuProps & { animated: boolean; disabled?: boolean; heading?: string }) { + const { + heading = 'Submenu', + open = true, + onOpenChange, + children, + animated, + disabled, + ...contentProps + } = props; + return ( + + + {heading} โ†’ + + + + {children} + + + + ); +} + +function TickIcon() { + return ( + + + + ); +} diff --git a/apps/ssr-testing/app/menubar/page.tsx b/internal/test-registry/components/menubar.tsx similarity index 94% rename from apps/ssr-testing/app/menubar/page.tsx rename to internal/test-registry/components/menubar.tsx index 0d1c0f4250..68a9bac05c 100644 --- a/apps/ssr-testing/app/menubar/page.tsx +++ b/internal/test-registry/components/menubar.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Menubar } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( diff --git a/apps/ssr-testing/app/navigation-menu/page.tsx b/internal/test-registry/components/navigation-menu.tsx similarity index 94% rename from apps/ssr-testing/app/navigation-menu/page.tsx rename to internal/test-registry/components/navigation-menu.tsx index 6593c09a93..22bcb104cb 100644 --- a/apps/ssr-testing/app/navigation-menu/page.tsx +++ b/internal/test-registry/components/navigation-menu.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { NavigationMenu } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( diff --git a/apps/ssr-testing/app/one-time-password-field/page.tsx b/internal/test-registry/components/one-time-password-field.tsx similarity index 96% rename from apps/ssr-testing/app/one-time-password-field/page.tsx rename to internal/test-registry/components/one-time-password-field.tsx index 70746a5ed9..ca60e72ae9 100644 --- a/apps/ssr-testing/app/one-time-password-field/page.tsx +++ b/internal/test-registry/components/one-time-password-field.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { unstable_OneTimePasswordField as OneTimePasswordField } from 'radix-ui'; -export default function Page() { +export function Basic() { return (
diff --git a/apps/ssr-testing/app/password-toggle-field/page.tsx b/internal/test-registry/components/password-toggle-field.tsx similarity index 98% rename from apps/ssr-testing/app/password-toggle-field/page.tsx rename to internal/test-registry/components/password-toggle-field.tsx index 4b6ce56107..436f0565be 100644 --- a/apps/ssr-testing/app/password-toggle-field/page.tsx +++ b/internal/test-registry/components/password-toggle-field.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { unstable_PasswordToggleField as PasswordToggleField } from 'radix-ui'; -export default function Page() { +export function Basic() { return (
diff --git a/apps/ssr-testing/app/popover/page.tsx b/internal/test-registry/components/popover.tsx similarity index 92% rename from apps/ssr-testing/app/popover/page.tsx rename to internal/test-registry/components/popover.tsx index 6e2d0f2fb9..588d7bd10a 100644 --- a/apps/ssr-testing/app/popover/page.tsx +++ b/internal/test-registry/components/popover.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Popover } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( open diff --git a/internal/test-registry/components/popper.tsx b/internal/test-registry/components/popper.tsx new file mode 100644 index 0000000000..8e1c01251d --- /dev/null +++ b/internal/test-registry/components/popper.tsx @@ -0,0 +1,144 @@ +'use client'; +import * as React from 'react'; +import { Portal } from 'radix-ui'; +import { Popper } from 'radix-ui/internal'; + +export function Basic() { + const [open, setOpen] = React.useState(false); + const [animated, setAnimated] = React.useState(false); + return ( +
+ +
+ + + setOpen(true)}> + open + + + {open && ( + + + + + )} + + +
+ ); +} + +export const WithCustomArrow = () => { + const [open, setOpen] = React.useState(false); + return ( + + + setOpen(true)}> + open + + + {open && ( + + + +
+ + + )} + + + ); +}; + +export function WithPortal() { + const [open, setOpen] = React.useState(false); + return ( + + + setOpen(true)}> + open + + + {open && ( + + + + + + + )} + + + ); +} + +export function WithUpdatePositionStrategyAlways() { + const [open, setOpen] = React.useState(false); + const [left, setLeft] = React.useState(0); + React.useEffect(() => { + const intervalId = setInterval(() => { + setLeft((prev) => (prev + 50) % 300); + }, 500); + return () => clearInterval(intervalId); + }, []); + return ( + + + setOpen(true)} + style={{ marginLeft: left }} + > + open + + + {open && ( + + + + + + + )} + + + ); +} + +function Scrollable(props: React.HTMLAttributes) { + return ( +
+ ); +} diff --git a/internal/test-registry/components/portal.tsx b/internal/test-registry/components/portal.tsx new file mode 100644 index 0000000000..05b25e0e7e --- /dev/null +++ b/internal/test-registry/components/portal.tsx @@ -0,0 +1,61 @@ +'use client'; +import * as React from 'react'; +import { Portal } from 'radix-ui'; + +export function Basic() { + return ( +
+

This content is rendered in the main DOM tree

+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos porro, est ex quia itaque + facere fugit necessitatibus aut enim. Nisi rerum quae, repellat in perspiciatis explicabo + laboriosam necessitatibus eius pariatur. +

+ + +

This content is rendered in a portal (another DOM tree)

+

+ Because of the portal, it can appear in a different DOM tree from the main one (by default + a new element inside the body), even though it is part of the same React tree. +

+
+
+ ); +} + +export function Custom() { + const [container, setContainer] = React.useState(null); + return ( +
+ + + This content is rendered in a custom container + +
+ ); +} + +export function Conditional() { + const [container, setContainer] = React.useState(null); + const [open, setOpen] = React.useState(false); + return ( +
+ + + {open && ( + + This content is rendered in a custom container + + )} +
+ ); +} diff --git a/apps/ssr-testing/app/progress/page.tsx b/internal/test-registry/components/progress.tsx similarity index 85% rename from apps/ssr-testing/app/progress/page.tsx rename to internal/test-registry/components/progress.tsx index 3d5613412c..a142e2bb7b 100644 --- a/apps/ssr-testing/app/progress/page.tsx +++ b/internal/test-registry/components/progress.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Progress } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( Progress diff --git a/apps/ssr-testing/app/radio-group/page.tsx b/internal/test-registry/components/radio-group.tsx similarity index 96% rename from apps/ssr-testing/app/radio-group/page.tsx rename to internal/test-registry/components/radio-group.tsx index bc7b1f0151..36e50a00a6 100644 --- a/apps/ssr-testing/app/radio-group/page.tsx +++ b/internal/test-registry/components/radio-group.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Label, RadioGroup } from 'radix-ui'; -export default function Page() { +export function Basic() { return ( Favourite pet diff --git a/internal/test-registry/components/roving-focus.tsx b/internal/test-registry/components/roving-focus.tsx new file mode 100644 index 0000000000..63709645ee --- /dev/null +++ b/internal/test-registry/components/roving-focus.tsx @@ -0,0 +1,202 @@ +'use client'; +import * as React from 'react'; +import { composeEventHandlers, RovingFocus } from 'radix-ui/internal'; + +export function Basic() { + return ( + +
+ +
+ +

no orientation (both) + no looping

+ + + + + + + + +

no orientation (both) + looping

+ + + + + + + + +

horizontal orientation + no looping

+ + + + + + + + +

horizontal orientation + looping

+ + + + + + + + +

vertical orientation + no looping

+ + + + + + + + +

vertical orientation + looping

+ + + + + + + +
+ ); +} + +export function Nested() { + return ( + + + +
+ + + + + + + + +
+ + + +
+ ); +} + +type Direction = 'ltr' | 'rtl'; + +const RovingFocusContext = React.createContext<{ + dir: 'ltr' | 'rtl'; + setDir: React.Dispatch>; +}>({ + dir: 'ltr', + setDir: () => void 0, +}); +RovingFocusContext.displayName = 'RovingFocusContext'; + +function RovingFocusProvider({ children }: { children: React.ReactNode }) { + const [dir, setDir] = React.useState('ltr'); + return ( +
+ {children} +
+ ); +} + +function RovingFocusToggle() { + const { dir, setDir } = React.use(RovingFocusContext); + return ( + + ); +} + +const ButtonGroupContext = React.createContext<{ + value: string | undefined; + setValue: React.Dispatch>; +}>({ + value: undefined, + setValue: () => void 0, +}); + +type ButtonGroupProps = Omit, 'defaultValue'> & + RovingFocus.RovingFocusGroupProps & { defaultValue?: string }; + +function ButtonGroup({ defaultValue, ...props }: ButtonGroupProps) { + const [value, setValue] = React.useState(defaultValue); + const { dir } = React.use(RovingFocusContext); + return ( + + + + ); +} + +type ButtonProps = Omit, 'value'> & { value?: string }; + +function Button(props: ButtonProps) { + const { value: contextValue, setValue } = React.use(ButtonGroupContext); + const isSelected = + contextValue !== undefined && props.value !== undefined && contextValue === props.value; + + return ( + +