Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(system): missing useHref logic #2943

Merged
merged 19 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/curvy-eels-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nextui-org/system": patch
---

Fix missing useHref logic (#2934)
wingkwong marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions .changeset/friendly-months-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nextui-org/pagination": patch
"@nextui-org/use-aria-link": patch
"@nextui-org/use-aria-menu": patch
---

Add missing router.open parameters due to rounter change
wingkwong marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function Page() {

// Specify how each of the Autocomplete values should change when an
// option is selected from the list box
const onSelectionChange = (key: React.Key) => {
const onSelectionChange = (key: React.Key | null) => {
setFieldState((prevState) => {
let selectedItem = prevState.items.find((option) => option.value === key);

Expand Down
90 changes: 73 additions & 17 deletions apps/docs/content/docs/guide/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ component configures all NextUI components within it to navigate using the clien
Set this up once in the root of your app, and any NextUI component with the href prop will automatically navigate
using your router.

### NextUIProvider Setup
## NextUIProvider Setup
wingkwong marked this conversation as resolved.
Show resolved Hide resolved

The `NextUIProvider` accepts a prop called `navigate`. This should be set to a function received from your
router for performing a client side navigation programmatically. The following example shows the general
Expand All @@ -43,9 +43,9 @@ function App() {

<Spacer y={2} />

### Next.js
## Next.js
wingkwong marked this conversation as resolved.
Show resolved Hide resolved

#### App Router
### App Router

Go to your `app/providers.tsx` or `app/providers.jsx` (create it if it doesn't exist) and add the
`useRouter` hook from `next/navigation`, it returns a router object that can be used to perform navigation.
Expand Down Expand Up @@ -95,15 +95,49 @@ export default function RootLayout({children}: { children: React.ReactNode }) {

> **Note**: Skip this step if you already set up the `NextUIProvider` in your app.

#### Add useHref (Optional)

If you are using the Next.js [basePath](https://nextjs.org/docs/app/api-reference/next-config-js/basePath) setting, you'll need to configure an environment variable to access it.

```js
// next.config.js
const basePath = '...';
const nextConfig = {
basePath,
env: {
BASE_PATH: basePath
}
};
```
Then, provide a custom `useHref` function to prepend it to the href for all links.

```tsx {9,12}
// app/providers.tsx
'use client'

import {NextUIProvider} from '@nextui-org/react';
import {useRouter} from 'next/navigation'

export function Providers({children}: { children: React.ReactNode }) {
const router = useRouter();
const useHref = (href: string) => process.env.BASE_PATH + href;

return (
<NextUIProvider navigate={router.push} useHref={useHref}>
{children}
</NextUIProvider>
)
}
```

</Steps>

#### Pages Router
### Pages Router

Go to pages`/_app.js` or `pages/_app.tsx` (create it if it doesn't exist) and add the`useRouter` hook
from `next/router`, it returns a router object that can be used to perform navigation.


```tsx {7}
```tsx {7,10}
// pages/_app.tsx
import type { AppProps } from 'next/app';
import {NextUIProvider} from '@nextui-org/react';
Expand All @@ -122,23 +156,47 @@ function MyApp({ Component, pageProps }: AppProps) {
export default MyApp;
```

### React Router
When using the [basePath](https://nextjs.org/docs/app/api-reference/next-config-js/basePath) configuration option, provide a `useHref` option to the router passed to Provider to prepend it to links automatically.

The `useNavigate` hook from `react-router-dom` returns a `navigate` function that can be used to perform navigation.
```tsx {8,11}
// pages/_app.tsx
import type { AppProps } from 'next/app';
import {NextUIProvider} from '@nextui-org/react';
import {useRouter} from 'next/router';

function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
const useHref = (href: string) => router.basePath + href;

return (
<NextUIProvider navigate={router.push} useHref={useHref}>
<Component {...pageProps} />
</NextUIProvider>
)
}

export default MyApp;
```

## React Router

The `useNavigate` hook from `react-router-dom` returns a `navigate` function that can be used to perform navigation.

The `useHref` hook can also be provided if you're using React Router's `basename` option. Ensure that the component that calls useNavigate and renders Provider is inside the router component (e.g. `BrowserRouter`) so that it has access to React Router's internal context. The React Router `<Routes>` element should also be defined inside `<NextUIProvider>` so that links inside the rendered routes have access to the router.

Go to the `App` file commonly called `App.jsx` or `App.tsx`, add the `useNavigate` hook and pass the
`navigate` function to the `NextUIProvider`:

```jsx {6,9}
// App.tsx or App.jsx
import {BrowserRouter, useNavigate} from 'react-router-dom';
import {BrowserRouter, useNavigate, useHref} from 'react-router-dom';
import {NextUIProvider} from '@nextui-org/react';

function App() {
const navigate = useNavigate();

return (
<NextUIProvider navigate={navigate}>
<NextUIProvider navigate={navigate} useHref={useHref}>
{/* Your app here... */}
<Routes>
<Route path="/" element={<HomePage />} />
Expand All @@ -164,17 +222,16 @@ component (e.g. `BrowserRouter`) so that it has access to React Router's interna
element should also be defined inside `NextUIProvider` so that links inside the rendered routes have access
to the router.

## Remix

### Remix

Remix uses React Router under the hood, so the same `useNavigate` hook described above also works in Remix
Remix uses React Router under the hood, so the same `useNavigate` and `useHref` hook described above also works in Remix
apps. `NextUIProvider` should be rendered at the `root` of each page that includes NextUI components, or in
`app/root.tsx` to add it to all pages. See the [Remix docs](https://remix.run/docs/en/main/file-conventions/root)
for more details.

```jsx {14}
// app/root.tsx
import {useNavigate, Outlet} from '@remix-run/react';
import {useNavigate, useHref, Outlet} from '@remix-run/react';
import {NextUIProvider} from '@nextui-org/react';

export default function App() {
Expand All @@ -186,7 +243,7 @@ export default function App() {
{/* ... */}
</head>
<body>
<NextUIProvider navigate={navigate}>
<NextUIProvider navigate={navigate} useHref={useHref}>
<Outlet />
</NextUIProvider>
{/* ... */}
Expand All @@ -196,8 +253,7 @@ export default function App() {
}
```


### Usage examples
## Usage examples
wingkwong marked this conversation as resolved.
Show resolved Hide resolved

Now that you have set up the `NextUIProvider` in your app, you can use the `href` prop in the `Tabs`,
`Listbox` and `Dropdown` items to navigate between pages.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@commitlint/config-conventional": "^17.2.0",
"@react-bootstrap/babel-preset": "^2.1.0",
"@react-types/link": "^3.4.4",
"@react-types/shared": "^3.22.1",
"@react-types/shared": "^3.23.0",
"@swc-node/jest": "^1.5.2",
"@swc/core": "^1.3.35",
"@swc/jest": "^0.2.24",
Expand Down
5 changes: 3 additions & 2 deletions packages/components/pagination/src/use-pagination-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ export function usePaginationItem(props: UsePaginationItemProps) {
e.currentTarget.href &&
// If props are applied to a router Link component, it may have already prevented default.
!e.isDefaultPrevented() &&
shouldClientNavigate(e.currentTarget, e)
shouldClientNavigate(e.currentTarget, e) &&
props.href
) {
e.preventDefault();
router.open(e.currentTarget, e);
router.open(e.currentTarget, e, props.href, props.routerOptions);
}
},
};
Expand Down
16 changes: 15 additions & 1 deletion packages/core/system/src/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {ModalProviderProps} from "@react-aria/overlays";
import type {ProviderContextProps} from "./provider-context";
import type {Href} from "@react-types/shared";

import {I18nProvider, I18nProviderProps} from "@react-aria/i18n";
import {RouterProvider} from "@react-aria/utils";
Expand All @@ -23,11 +24,20 @@ export interface NextUIProviderProps
* Link, Menu, Tabs, Table, etc.
*/
navigate?: (path: string) => void;
/**
* Convert an `href` provided to a link component to a native `href`
* For example, a router might accept hrefs relative to a base path,
* or offer additional custom ways of specifying link destinations.
* The original href specified on the link is passed to the navigate function of the RouterProvider,
* and useHref is used to generate the full native href to put on the actual DOM element.
*/
useHref?: (href: Href) => string;
}

export const NextUIProvider: React.FC<NextUIProviderProps> = ({
children,
navigate,
useHref,
locale = "en-US",
defaultDates = {
minDate: new CalendarDate(1900, 1, 1),
Expand All @@ -39,7 +49,11 @@ export const NextUIProvider: React.FC<NextUIProviderProps> = ({
let contents = children;

if (navigate) {
contents = <RouterProvider navigate={navigate}>{contents}</RouterProvider>;
contents = (
<RouterProvider navigate={navigate} useHref={useHref}>
{contents}
</RouterProvider>
);
}

const context = useMemo<ProviderContextProps>(
Expand Down
5 changes: 3 additions & 2 deletions packages/hooks/use-aria-link/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ export function useAriaLink(props: AriaLinkOptions, ref: RefObject<FocusableElem
e.currentTarget.href &&
// If props are applied to a router Link component, it may have already prevented default.
!e.isDefaultPrevented() &&
shouldClientNavigate(e.currentTarget, e)
shouldClientNavigate(e.currentTarget, e) &&
props.href
) {
e.preventDefault();
router.open(e.currentTarget, e);
router.open(e.currentTarget, e, props.href, props.routerOptions);
}
},
}),
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/use-aria-menu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"dependencies": {
"@react-aria/utils": "^3.23.2",
"@react-types/shared": "^3.22.1",
"@react-types/shared": "^3.23.0",
"@react-aria/menu": "^3.13.1",
"@react-aria/interactions": "^3.21.1",
"@react-stately/tree": "^3.7.6",
Expand Down
8 changes: 4 additions & 4 deletions packages/hooks/use-aria-menu/src/use-menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
KeyboardEvents,
PressEvent,
PressEvents,
RouterOptions,
} from "@react-types/shared";
import {chain, filterDOMProps, mergeProps, useRouter, useSlotId} from "@react-aria/utils";
import {getItemCount} from "@react-stately/collections";
Expand Down Expand Up @@ -139,6 +140,8 @@ export function useMenuItem<T>(
let isSelected = props.isSelected ?? state.selectionManager.isSelected(key);
let data = menuData.get(state);
// @ts-ignore
let item = state.collection.getItem(key);
// @ts-ignore
let onClose = props.onClose || data.onClose;
// @ts-ignore
let onAction = isTrigger ? () => {} : props.onAction || data.onAction;
Expand All @@ -150,7 +153,7 @@ export function useMenuItem<T>(
}

if (e.target instanceof HTMLAnchorElement) {
router.open(e.target, e);
router.open(e.target, e, item?.props.href, item?.props.routerOptions as RouterOptions);
}
};

Expand Down Expand Up @@ -184,9 +187,6 @@ export function useMenuItem<T>(
ariaProps["aria-checked"] = isSelected;
}

// @ts-ignore
let item = state.collection.getItem(key);

if (isVirtualized) {
// @ts-ignore
ariaProps["aria-posinset"] = item?.index;
Expand Down
Loading
Loading