Repository: OKAMca/stack
Pull request #268: STACK-76-Component-NextLink-wrapper
Author: login: poclerson
Labels: name: ready to review
Created: 2025-04-07T18:08:45Z | Merged: 2025-04-29T20:56:10Z

## Issue
https://okamca.atlassian.net/browse/STACK-76

## Implementation details
### Features
New useLink hook/Link component should support:
- [x] Scrolling to top on click
- [x] Extend the next/link interface for compatibility
- [x] Automatic pre-pending of the locale (are we planning on having a CMS-agnostic locale provider of some kind? currently using next/navigation `useParams`/direct prop assignement)
- [x] Stack-ui themeing
- [x] All props from `TDefaultComponent` 

### Docs
- [x] Should have demos in next app
- [x] Should have storybook documentation
- [x] Should have stack-ui Anchor component's `nextLinkProps` as deprecated

## PR Checklist      
- [x] Lint must pass
- [x] Build must pass
- [x] There should be no warnings/errors in the console/terminal (check locally)

## How to test this PR
- next-component storybook -> Link story
- demo
- /en/next-component/link/products
- Test interactivity in next app environment

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
  - Introduced new components for localized content and product navigation, featuring interactive links with advanced scrolling behaviours and responsive layouts.
  - Added hooks for managing link behaviour and hash navigation, enhancing user interaction with URLs.
  - Implemented a new theming system for links, allowing for customizable styles and variants.

- **Documentation**
  - Expanded Storybook guides and component demos provide clear instructions for using the new interactive features.

- **Chores**
  - Upgraded key dependencies and refined configuration settings to ensure improved stability and performance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/demo/app/[locale]/Providers.tsx b/apps/demo/app/[locale]/Providers.tsx
new file mode 100644
index 00000000..7743d63a
--- /dev/null
+++ b/apps/demo/app/[locale]/Providers.tsx
@@ -0,0 +1,10 @@
+'use client'
+
+import { I18nProvider } from 'react-aria'
+
+const Providers = (props: { children: React.ReactNode; locale: string }) => {
+  const { children, locale } = props
+  return <I18nProvider locale={locale}>{children}</I18nProvider>
+}
+
+export default Providers
diff --git a/apps/demo/app/[locale]/layout.tsx b/apps/demo/app/[locale]/layout.tsx
new file mode 100644
index 00000000..e2d35a2c
--- /dev/null
+++ b/apps/demo/app/[locale]/layout.tsx
@@ -0,0 +1,5 @@
+import Providers from './Providers'
+
+export default function RootLayout({ children, params }: { children: React.ReactNode; params: { locale: string } }) {
+  return <Providers locale={params.locale}>{children}</Providers>
+}
diff --git a/apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx b/apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx
new file mode 100644
index 00000000..980a8028
--- /dev/null
+++ b/apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx
@@ -0,0 +1,45 @@
+import type { TLinkProps } from '@okam/next-component'
+import { Link } from '@okam/next-component'
+import { Box, Typography } from '@okam/stack-ui'
+
+const ProductsLinks = (
+  props: Parameters<typeof Page>[0] & {
+    title: string
+    description: string
+    scroll: TLinkProps['scroll']
+  },
+) => {
+  const { title, description, scroll, ...rest } = props
+  const { id } = rest.params
+  const route = `/next-component/link/products`
+  const nextId = Number.isNaN(parseInt(id, 10)) ? 1 : parseInt(id, 10) + 1
+  const href = `${route}/${nextId}`
+
+  return (
+    <Box customTheme="flex flex-col gap-4">
+      <Typography tokens={{ size: 'h2' }}>{title}</Typography>
+      <Typography tokens={{ size: 'p' }}>{description}</Typography>
+      <Link scroll={scroll} href={route}>
+        Home page
+      </Link>
+      <Link scroll={scroll} href={href}>
+        Next product
+      </Link>
+    </Box>
+  )
+}
+
+export default async function Page(props: { params: { id: string; locale: string } }) {
+  return (
+    <Box customTheme="p-8">
+      <ProductsLinks
+        {...props}
+        title="True"
+        description="Default next.js behavior. Try to restore scroll position if the page has already been navigated to."
+        scroll
+      />
+      <ProductsLinks {...props} title="False" description="Disable scrolling." scroll={false} />
+      <ProductsLinks {...props} title="Top" description="Scroll to top on click." scroll="top" />
+    </Box>
+  )
+}
diff --git a/apps/demo/app/[locale]/next-component/link/products/layout.tsx b/apps/demo/app/[locale]/next-component/link/products/layout.tsx
new file mode 100644
index 00000000..129d2fb0
--- /dev/null
+++ b/apps/demo/app/[locale]/next-component/link/products/layout.tsx
@@ -0,0 +1,20 @@
+import { Box, Typography } from '@okam/stack-ui'
+
+export default async function Layout(props: { children: React.ReactNode; params: { id: string; locale: string } }) {
+  const { children } = props
+
+  return (
+    <Box>
+      <Box customTheme="h-48 bg-gray-300 justify-center items-center flex">
+        <Typography tokens={{ size: 'h1' }}>Top of the page</Typography>
+      </Box>
+      <Box customTheme="h-screen bg-gray-100 justify-center items-center flex">
+        <Typography tokens={{ size: 'h1' }}>Very long content</Typography>
+      </Box>
+      {children}
+      <Box customTheme="h-48 bg-black text-white justify-center items-center flex">
+        <Typography tokens={{ size: 'h3' }}>Products footer</Typography>
+      </Box>
+    </Box>
+  )
+}
diff --git a/apps/demo/app/[locale]/next-component/link/products/page.tsx b/apps/demo/app/[locale]/next-component/link/products/page.tsx
new file mode 100644
index 00000000..acb809e5
--- /dev/null
+++ b/apps/demo/app/[locale]/next-component/link/products/page.tsx
@@ -0,0 +1,13 @@
+import { Link } from '@okam/next-component'
+import { Box, Typography } from '@okam/stack-ui'
+
+export default async function Page() {
+  return (
+    <Box>
+      <Box customTheme="h-48 bg-gray-300 justify-center items-center flex">
+        <Typography tokens={{ size: 'h1' }}>Products home page</Typography>
+      </Box>
+      <Link href="/next-component/link/products/1">Products 1</Link>
+    </Box>
+  )
+}
diff --git a/libs/stack/next-component/.storybook/preview.ts b/libs/stack/next-component/.storybook/preview.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/libs/stack/next-component/.storybook/preview.tsx b/libs/stack/next-component/.storybook/preview.tsx
new file mode 100644
index 00000000..0142ad55
--- /dev/null
+++ b/libs/stack/next-component/.storybook/preview.tsx
@@ -0,0 +1,15 @@
+import '../src/tailwind.css'
+import type { Preview } from '@storybook/react'
+import BaseThemeProvider from '../src/theme'
+
+const preview: Preview = {
+  decorators: [
+    (Story) => (
+      <BaseThemeProvider>
+        <Story />
+      </BaseThemeProvider>
+    ),
+  ],
+}
+
+export default preview
diff --git a/libs/stack/next-component/package.json b/libs/stack/next-component/package.json
index 5b8dd931..b0e62f32 100644
--- a/libs/stack/next-component/package.json
+++ b/libs/stack/next-component/package.json
@@ -8,7 +8,10 @@
     "url": "https://github.com/OKAMca/stack.git"
   },
   "dependencies": {
-    "@okam/stack-ui": "1.33.0",
-    "next": "^14.1.1"
+    "next": "^14.1.1",
+    "react-use": "17.5.1",
+    "react": "18.3.1",
+    "tailwind-variants": "^0.3.0",
+    "@okam/stack-ui": "1.33.0"
   }
 }
diff --git a/libs/stack/next-component/postcss.config.js b/libs/stack/next-component/postcss.config.js
new file mode 100644
index 00000000..a8f1ac99
--- /dev/null
+++ b/libs/stack/next-component/postcss.config.js
@@ -0,0 +1,15 @@
+const { join } = require('path')
+
+// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
+// option from your application's configuration (i.e. project.json).
+//
+// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
+
+module.exports = {
+  plugins: {
+    tailwindcss: {
+      config: join(__dirname, 'tailwind.config.js'),
+    },
+    autoprefixer: {},
+  },
+}
diff --git a/libs/stack/next-component/src/components/Link/index.tsx b/libs/stack/next-component/src/components/Link/index.tsx
new file mode 100644
index 00000000..a8aaa1d5
--- /dev/null
+++ b/libs/stack/next-component/src/components/Link/index.tsx
@@ -0,0 +1,46 @@
+'use client'
+
+import { Anchor } from '@okam/stack-ui'
+import NextLink from 'next/link'
+import type { Ref } from 'react'
+import { forwardRef } from 'react'
+import { useLink } from '../../hooks/useLink'
+import type { TLinkProps } from './interface'
+
+const Link = forwardRef((props: TLinkProps, ref: Ref<HTMLButtonElement & HTMLAnchorElement>) => {
+  const {
+    themeName = 'link',
+    tokens,
+    customTheme,
+    as = NextLink,
+    children,
+    scroll,
+    onPathnameChange,
+    onSearchParamsChange,
+    onHashChange,
+    behavior,
+    urlDecorator: urlDecoratorProp,
+    href: hrefProp,
+    ...rest
+  } = props
+
+  const linkProps = useLink(props)
+
+  return (
+    <Anchor
+      ref={ref}
+      {...rest}
+      nextLinkProps={linkProps}
+      as={as}
+      themeName={themeName}
+      tokens={tokens}
+      customTheme={customTheme}
+    >
+      {children}
+    </Anchor>
+  )
+})
+
+Link.displayName = 'Link'
+
+export default Link
diff --git a/libs/stack/next-component/src/components/Link/interface.ts b/libs/stack/next-component/src/components/Link/interface.ts
new file mode 100644
index 00000000..12452ce8
--- /dev/null
+++ b/libs/stack/next-component/src/components/Link/interface.ts
@@ -0,0 +1,4 @@
+import type { TDefaultComponent, TToken } from '@okam/stack-ui'
+import type { TLink } from '../../hooks/useLink/interface'
+
+export interface TLinkProps<T = TToken> extends Omit<TDefaultComponent<T>, keyof TLink>, TLink {}
diff --git a/libs/stack/next-component/src/components/Link/link.mdx b/libs/stack/next-component/src/components/Link/link.mdx
new file mode 100644
index 00000000..2aa25936
--- /dev/null
+++ b/libs/stack/next-component/src/components/Link/link.mdx
@@ -0,0 +1,42 @@
+import * as LinkStories from './link.stories'
+import { Meta, ArgTypes, Canvas, Controls } from '@storybook/blocks'
+
+<Meta of={LinkStories} />
+
+# Link
+
+The next component `Link` component is a wrapper for next/link that accepts themeing via `themeName`, `tokens` and `customTheme` props.
+
+It also add a few more features to the next/link component
+
+## Additional features
+
+- Option to scroll to the top of the page when the link is clicked
+- Automatic prepending of the locale to the href using either the `locale` prop or the next/navigation `useParams` hook (with the `locale` route parameter).
+- Callback functions for any changes to the href (pathname, searchParams, hash)
+
+## Props
+
+<Controls />
+
+## Example usage
+
+```tsx
+// Route: /[locale]/[category]/[productId]
+// Current pathname: /en/electronics/123
+
+<Link href="/electronics/456">Go to product 456</Link>
+// Resulting href: /en/electronics/456
+// The locale is deduced from the `locale` route parameter
+
+// Route: /[locale]/[category]/[productId]
+// Current pathname: /en/electronics/123
+
+<Link href="/electronics/456" locale="fr">Go to product 456</Link>
+// Resulting href: /fr/electronics/456
+// Passing the `locale` prop explicitly overrides the `locale` route parameter
+```
+
+## Showcase
+
+<Canvas of={LinkStories.ScrollDefault} />
diff --git a/libs/stack/next-component/src/components/Link/link.stories.tsx b/libs/stack/next-component/src/components/Link/link.stories.tsx
new file mode 100644
index 00000000..1454ee65
--- /dev/null
+++ b/libs/stack/next-component/src/components/Link/link.stories.tsx
@@ -0,0 +1,164 @@
+import { Box } from '@okam/stack-ui'
+import type { Meta, StoryObj } from '@storybook/react'
+import Link from './index'
+
+const meta: Meta<typeof Link> = {
+  title: 'Link',
+  component: Link,
+  parameters: {
+    docs: {
+      decorators: false,
+    },
+    nextjs: {
+      appDirectory: true,
+      navigation: {
+        pathname: '/[locale]/products/[id]',
+        asPath: '/fr/products/1',
+        query: {
+          id: '1',
+          locale: 'fr',
+        },
+      },
+    },
+  },
+  decorators: [
+    (Story) => (
+      <>
+        <Box customTheme="h-screen bg-gray-100 flex items-center justify-center mb-8">This is long content</Box>
+        <Story />
+      </>
+    ),
+  ],
+  args: {
+    locale: 'en',
+    href: '/products/2',
+    children: 'Product 2',
+    scroll: true,
+    behavior: 'instant',
+    onClick: (e) => console.log('Click', e),
+    onMouseEnter: (e) => console.log('Mouse enter', e),
+    onTouchStart: (e) => console.log('Touch start', e),
+    onPathnameChange: (pathname) => console.log('Pathname change', pathname),
+    onSearchParamsChange: (searchParams) => console.log('Search params change', searchParams),
+    onHashChange: (hash) => console.log('Hash change', hash),
+  },
+  argTypes: {
+    scroll: {
+      description: 'Defines the scrolling action on link click.',
+      control: { type: 'radio' },
+      options: [true, false, 'top'],
+      table: {
+        type: {
+          summary: 'boolean | "top"',
+          detail: `
+- \`true\` attempts to preserve previously set scrolling position.
+- \`false\` will not scroll.
+- \`"top"\` will scroll to the top of the page (behavior depending on \`behavior\` prop)
+          `,
+        },
+      },
+    },
+    children: {
+      control: false,
+      table: {
+        type: { summary: 'ReactNode' },
+      },
+    },
+    onPathnameChange: {
+      control: false,
+      description: 'Callback function that is called when the pathname changes. Relies on `usePathname` hook.',
+    },
+    onSearchParamsChange: {
+      control: false,
+      description: 'Callback function that is called when the search params change. Relies on `useSearchParams` hook.',
+    },
+    onHashChange: {
+      control: false,
+      description: 'Callback function that is called when the hash changes. Relies on `useHash` hook.',
+    },
+    href: {
+      table: {
+        category: 'next/link',
+        type: { summary: 'string | URL' },
+      },
+    },
+    prefetch: {
+      table: {
+        category: 'next/link',
+      },
+    },
+    replace: {
+      table: {
+        category: 'next/link',
+      },
+    },
+    shallow: {
+      table: {
+        category: 'next/link',
+      },
+    },
+    passHref: {
+      table: {
+        category: 'next/link',
+      },
+    },
+    legacyBehavior: {
+      table: {
+        category: 'next/link',
+      },
+    },
+    locale: {
+      description:
+        'The active locale is automatically prepended. locale allows for providing a different locale. When `false` href has to include the locale as the default behavior is disabled. By default, the locale will try to be set via the `locale` prop, then the `useParams` hook. with the `locale` dynamic route segment.',
+      table: {
+        category: 'next/link',
+      },
+    },
+    urlDecorator: {
+      control: false,
+      name: 'as (urlDecorator)',
+      description:
+        'Originally named `as` in next/link, prop was renamed to avoid conflicts with the `as` prop of `TDefaultComponent`.',
+      table: {
+        category: 'next/link',
+        type: { summary: 'string | URL' },
+      },
+    },
+    onClick: {
+      control: false,
+      table: {
+        category: 'next/link',
+      },
+    },
+    onMouseEnter: {
+      control: false,
+      table: {
+        category: 'next/link',
+      },
+    },
+    onTouchStart: {
+      control: false,
+      table: {
+        category: 'next/link',
+      },
+    },
+  },
+}
+
+export default meta
+
+type Story = StoryObj<typeof Link>
+
+export const ScrollDefault: Story = {}
+
+export const ScrollFalse: Story = {
+  args: {
+    scroll: false,
+  },
+}
+
+export const ScrollTop: Story = {
+  args: {
+    scroll: 'top',
+  },
+}
diff --git a/libs/stack/next-component/src/hooks/useHash/index.ts b/libs/stack/next-component/src/hooks/useHash/index.ts
new file mode 100644
index 00000000..a6606e2f
--- /dev/null
+++ b/libs/stack/next-component/src/hooks/useHash/index.ts
@@ -0,0 +1,32 @@
+'use client'
+
+import { useState } from 'react'
+import { useEvent } from 'react-use'
+
+function getHash() {
+  if (typeof window === 'undefined') return ''
+  return window.location.hash
+}
+
+/**
+ * A hook that lets you read the current URL's hash.
+ *
+ * @returns The current `window.location.hash`, including the leading `#` character.
+ */
+export function useHash() {
+  const defaultHash = getHash()
+  const [hash, setHash] = useState(defaultHash)
+
+  const handleHashChangeEvent = ({ newURL }: HashChangeEvent) => {
+    if (!URL.canParse(newURL)) return
+
+    const { hash: newHash } = new URL(newURL)
+    if (!newHash || newHash === hash) return
+
+    setHash(newHash)
+  }
+
+  useEvent('hashchange', handleHashChangeEvent)
+
+  return hash
+}
diff --git a/libs/stack/next-component/src/hooks/useLink/index.ts b/libs/stack/next-component/src/hooks/useLink/index.ts
new file mode 100644
index 00000000..5af482f3
--- /dev/null
+++ b/libs/stack/next-component/src/hooks/useLink/index.ts
@@ -0,0 +1,108 @@
+'use client'
+
+import type { Params } from 'next/dist/shared/lib/router/utils/route-matcher'
+import { useParams, usePathname, useSearchParams } from 'next/navigation'
+import { useCallback, useEffect } from 'react'
+import { useHash } from '../useHash'
+import type { TLink, TUseLinkReturn } from './interface'
+
+function scrollToTop(behavior: ScrollBehavior) {
+  window?.scrollTo?.({ top: 0, behavior })
+}
+
+function getParamsLocale(params: Params) {
+  const { locale } = params
+  if (Array.isArray(locale)) return locale[0]
+  return locale
+}
+
+/**
+ * Tries to get the locale, in order of priority:
+ * 1. The locale prop. Still has priority even when set to `false`
+ * 2. The locale from react-aria `useLocale`
+ * 3. The locale from next/navigation `useParams`
+ * @returns The best matched locale
+ */
+export function useLinkLocale(props: TLink) {
+  const { locale } = props
+  const params = useParams()
+  const paramsLocale = getParamsLocale(params)
+  if (locale === false) return false
+
+  return locale ?? paramsLocale ?? false
+}
+
+/**
+ * @params {props.locale} - The direct locale prop always gets priority. If no `locale` prop is provided, the prop will try to fall back to react-aria `useLocale` and then next/navigation `useParams`. If a locale is found, it will be automatically prepended to the href. Otherwise, href will be returned as is.
+ */
+export function useLink(props: TLink): TUseLinkReturn {
+  const {
+    scroll = true,
+    onMouseEnter,
+    onTouchStart,
+    onClick,
+    onPathnameChange,
+    onHashChange,
+    onSearchParamsChange,
+    href,
+    urlDecorator,
+    replace,
+    prefetch,
+    shallow,
+    passHref,
+    legacyBehavior,
+    behavior = 'instant',
+  } = props
+
+  const locale = useLinkLocale(props)
+  const localizedHref = locale ? `/${locale}${href}` : href
+  const pathname = usePathname()
+  const searchParams = useSearchParams()
+  const hash = useHash()
+
+  const isNextScroll = typeof scroll === 'boolean'
+  const nextScroll = isNextScroll ? scroll : false
+
+  const handleScroll = useCallback(() => {
+    if (isNextScroll) return
+
+    scrollToTop(behavior)
+  }, [behavior, isNextScroll])
+
+  const handleClick: typeof onClick = (event) => {
+    onClick?.(event)
+    handleScroll()
+  }
+
+  const handleTouchStart: typeof onTouchStart = (event) => {
+    onTouchStart?.(event)
+    handleScroll()
+  }
+
+  useEffect(() => {
+    onPathnameChange?.(pathname)
+  }, [onPathnameChange, pathname])
+
+  useEffect(() => {
+    onSearchParamsChange?.(searchParams)
+  }, [onSearchParamsChange, searchParams])
+
+  useEffect(() => {
+    onHashChange?.(hash)
+  }, [onHashChange, hash])
+
+  return {
+    href: localizedHref.toString(),
+    as: urlDecorator,
+    replace,
+    locale,
+    prefetch,
+    shallow,
+    onClick: handleClick,
+    onTouchStart: handleTouchStart,
+    onMouseEnter,
+    scroll: nextScroll,
+    passHref,
+    legacyBehavior,
+  }
+}
diff --git a/libs/stack/next-component/src/hooks/useLink/interface.ts b/libs/stack/next-component/src/hooks/useLink/interface.ts
new file mode 100644
index 00000000..118cabfe
--- /dev/null
+++ b/libs/stack/next-component/src/hooks/useLink/interface.ts
@@ -0,0 +1,25 @@
+import type { LinkProps as NextLinkProps } from 'next/link'
+import type { ReadonlyURLSearchParams } from 'next/navigation'
+
+export interface TLink extends Omit<NextLinkProps, 'scroll' | 'as'> {
+  /**
+   * @default true
+   * - `true`: Scrolls to the top of the clicked anchor (default Next.js behavior)
+   * - `false`: Prevents any automatic scrolling
+   * - `'top'`: Always scrolls to the top of the page, regardless of anchor
+   */
+  scroll?: 'top' | boolean
+  urlDecorator?: NextLinkProps['as']
+  /**
+   * Used for the automatic scroll when `scroll = 'top'`
+   * @default instant
+   */
+  behavior?: ScrollBehavior
+  onPathnameChange?: (pathname: string) => void
+  onSearchParamsChange?: (searchParams: ReadonlyURLSearchParams) => void
+  onHashChange?: (hash: string) => void
+}
+
+export interface TUseLinkReturn extends Omit<NextLinkProps, 'href'> {
+  href: string
+}
diff --git a/libs/stack/next-component/src/index.ts b/libs/stack/next-component/src/index.ts
index 1c52f615..581531cd 100644
--- a/libs/stack/next-component/src/index.ts
+++ b/libs/stack/next-component/src/index.ts
@@ -1,3 +1,9 @@
 export { default as Img } from './components/Img'
+export { default as Link } from './components/Link'
+
+export { useLink } from './hooks/useLink'
+export { useHash } from './hooks/useHash'
 
 export type { default as TImgProps } from './components/Img/interface'
+export type { TLinkProps } from './components/Link/interface'
+export type { TLink } from './hooks/useLink/interface'
diff --git a/libs/stack/next-component/src/tailwind.css b/libs/stack/next-component/src/tailwind.css
new file mode 100644
index 00000000..b5c61c95
--- /dev/null
+++ b/libs/stack/next-component/src/tailwind.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/libs/stack/next-component/src/theme/Link/index.ts b/libs/stack/next-component/src/theme/Link/index.ts
new file mode 100644
index 00000000..f1936484
--- /dev/null
+++ b/libs/stack/next-component/src/theme/Link/index.ts
@@ -0,0 +1,81 @@
+import { tv } from 'tailwind-variants'
+
+const linkTheme = tv({
+  base: `
+    flex
+    items-center
+    justify-center
+    gap-4
+    transition
+    duration-300
+    ease-in-out
+    disabled:pointer-events-none
+    disabled:opacity-30
+    focus-ring-black
+  `,
+  defaultVariants: {
+    buttonStyle: 'ghost',
+    type: 'button',
+    size: 'default',
+    shape: 'rounded',
+  },
+  variants: {
+    type: {
+      button: '',
+      menu: 'text-2xl',
+    },
+    buttonStyle: {
+      default: `
+        px-4
+        py-2
+        text-white
+        !bg-color-1-500
+        hover:!bg-color-1-400
+        active:!bg-color-1-400
+      `,
+      outline: `
+        px-4
+        py-2
+        bg-transparent
+        !border-color-1-500
+        text-color-1-500
+        hover:bg-color-1-500
+        hover:text-white
+        active:bg-color-1-500
+        active:text-white
+      `,
+      hollow: `
+        px-2
+        bg-transparent
+        text-color-1-500
+        hover:border-b-color-1-500
+        active:border-b-color-1-500
+        focus:border-b-color-1-500
+      `,
+      ghost: `
+        bg-transparent
+        text-color-1-500
+        hover:text-color-1-400
+        active:text-color-1-400
+      `,
+    },
+    intent: {
+      error: `
+        !bg-error
+        text-white
+        pointer-events-none
+        !border-error
+      `,
+    },
+    size: {
+      default: `min-w-12 min-h-6`,
+      large: `min-w-36 min-h-18`,
+    },
+    shape: {
+      rounded: `rounded-md`,
+      circular: `rounded-full`,
+    },
+  },
+})
+
+export default linkTheme
diff --git a/libs/stack/next-component/src/theme/index.ts b/libs/stack/next-component/src/theme/index.ts
new file mode 100644
index 00000000..2a229499
--- /dev/null
+++ b/libs/stack/next-component/src/theme/index.ts
@@ -0,0 +1,9 @@
+import { createThemeProvider, makeTheme } from '@okam/stack-ui'
+import { memo } from 'react'
+import linkTheme from './Link'
+
+const baseTheme = makeTheme({
+  link: linkTheme,
+})
+
+export default memo(createThemeProvider(baseTheme))
diff --git a/libs/stack/next-component/tailwind.config.js b/libs/stack/next-component/tailwind.config.js
new file mode 100644
index 00000000..9edeb00a
--- /dev/null
+++ b/libs/stack/next-component/tailwind.config.js
@@ -0,0 +1,14 @@
+const { createGlobPatternsForDependencies } = require('@nx/react/tailwind')
+const { join } = require('path')
+
+module.exports = {
+  content: [
+    join(__dirname, '{src,pages,components,app,theme}/**/*.{js,ts,jsx,tsx,mdx,html}'),
+    ...createGlobPatternsForDependencies(__dirname),
+  ],
+  theme: {
+    extend: {},
+  },
+  plugins: [],
+  presets: [require('../../../tailwind.preset')],
+}
diff --git a/libs/stack/next-component/tsconfig.storybook.json b/libs/stack/next-component/tsconfig.storybook.json
index 3555ddd4..cee14d87 100644
--- a/libs/stack/next-component/tsconfig.storybook.json
+++ b/libs/stack/next-component/tsconfig.storybook.json
@@ -26,6 +26,7 @@
     "src/**/*.stories.tsx",
     "src/**/*.stories.mdx",
     ".storybook/*.js",
-    ".storybook/*.ts"
+    ".storybook/*.ts",
+    ".storybook/*.tsx"
   ]
 }
diff --git a/libs/stack/stack-ui/src/components/Button/interface.ts b/libs/stack/stack-ui/src/components/Button/interface.ts
index 6cb9a9c3..d11ff419 100644
--- a/libs/stack/stack-ui/src/components/Button/interface.ts
+++ b/libs/stack/stack-ui/src/components/Button/interface.ts
@@ -19,5 +19,8 @@ export interface TButtonProps<T = TToken> extends TDefaultComponent<T> {
 }
 
 export interface TAnchorProps<T = TToken> extends TButtonProps<T> {
+  /**
+   * @deprecated To render a themeable `next/link` component, use the `Link` component from `@okam/next-component`.
+   */
   nextLinkProps?: NextLinkProps
 }
diff --git a/libs/stack/stack-ui/src/theme/index.tsx b/libs/stack/stack-ui/src/theme/index.tsx
index 44e887b9..8a9064ba 100644
--- a/libs/stack/stack-ui/src/theme/index.tsx
+++ b/libs/stack/stack-ui/src/theme/index.tsx
@@ -83,6 +83,7 @@ const BaseTheme = makeTheme({
   },
   typography: (props) => typography(props),
   button: (props) => button(props),
+  link: button,
   sidePanel: {
     wrapper: (props) => sidePanelWrapper(props),
     container: (props) => sidePanelContainer(props),
diff --git a/libs/stack/stack-ui/src/types/next-link.ts b/libs/stack/stack-ui/src/types/next-link.ts
index c27699c2..73c50d69 100644
--- a/libs/stack/stack-ui/src/types/next-link.ts
+++ b/libs/stack/stack-ui/src/types/next-link.ts
@@ -6,10 +6,11 @@ export type NextLinkProps = {
   scroll?: boolean
   shallow?: boolean
   passHref?: boolean
-  prefetch?: boolean
+  prefetch?: boolean | null
   locale?: string | false
   legacyBehavior?: boolean
   onMouseEnter?: React.MouseEventHandler<HTMLAnchorElement>
   onTouchStart?: React.TouchEventHandler<HTMLAnchorElement>
   onClick?: React.MouseEventHandler<HTMLAnchorElement>
+  as?: string | UrlObject
 }


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (14)</summary><blockquote>

<details>
<summary>apps/demo/app/[locale]/next-component/link/products/page.tsx (2)</summary><blockquote>

`4-13`: **Consider addressing async function implementation.**

The function is declared as async but doesn't contain any await statements or error handling. This is potentially misleading and could lead to unexpected behavior if asynchronous operations are added later.


Either remove the async keyword if no asynchronous operations are needed:

```diff
- export default async function Page() {
+ export default function Page() {
```

Or implement proper error handling if asynchronous operations will be added:

```typescript
export default async function Page() {
  try {
    // Async operations here
    return (
      <Box>
        <Box customTheme="h-48 bg-gray-300 justify-center items-center flex">
          <Typography tokens={{ size: 'h1' }}>Products home page</Typography>
        </Box>
        <Link href="/next-component/link/products/1">Products 1</Link>
      </Box>
    )
  } catch (error) {
    // Handle errors appropriately
    console.error('Error rendering products page:', error)
    return <div>Something went wrong loading the products page</div>
  }
}
```

---

`7-9`: **Consider enhancing accessibility.**

The current implementation uses a Box with Typography for the header, but it might be better to use proper semantic HTML for better accessibility.


Consider using a heading element inside the Typography component:

```diff
- <Typography tokens={{ size: 'h1' }}>Products home page</Typography>
+ <Typography as="h1" tokens={{ size: 'h1' }}>Products home page</Typography>
```

</blockquote></details>
<details>
<summary>apps/demo/app/[locale]/next-component/link/products/layout.tsx (2)</summary><blockquote>

`3-4`: **Remove unnecessary async keyword from function**

This function is marked as async but doesn't contain any await operations. Remove the async keyword unless you plan to add asynchronous operations in the future.

```diff
-export default async function Layout(props: { children: React.ReactNode; params: { id: string; locale: string } }) {
+export default function Layout(props: { children: React.ReactNode; params: { id: string; locale: string } }) {
```

---

`3-5`: **Destructure unused params property**

The function accepts a params object in its props but never uses it. Consider removing it from the props type if it's not needed, or use it if it's required for future functionality.

```diff
-export default async function Layout(props: { children: React.ReactNode; params: { id: string; locale: string } }) {
-  const { children } = props
+export default function Layout(props: { children: React.ReactNode }) {
+  const { children } = props
```

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/components/Link/link.mdx (2)</summary><blockquote>

`10-10`: **Fix grammar in component description**

There's a grammatical error in the component description.

```diff
-It also add a few more features to the next/link component
+It also adds a few more features to the next/link component
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~10-~10: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...kens` and `customTheme` props.  It also add a few more features to the next/link co...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

</details>

</details>

---

`40-42`: **Consider adding more showcase examples**

The documentation currently only shows one example (ScrollDefault). Consider adding examples for other variants of the Link component to demonstrate different use cases and configurations.

</blockquote></details>
<details>
<summary>apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx (3)</summary><blockquote>

`5-11`: **Consider simplifying component props type**

The current props type definition is somewhat complex. Consider creating a separate interface for better readability.

```diff
+interface ProductsLinksProps {
+  params: { id: string; locale: string };
+  title: string;
+  description: string;
+  scroll: TLinkOptions['scroll'];
+}

-const ProductsLinks = (
-  props: Parameters<typeof Page>[0] & {
-    title: string
-    description: string
-    scroll: TLinkOptions['scroll']
-  },
+const ProductsLinks = (props: ProductsLinksProps,
```

---

`15-15`: **Enhance nextId calculation to handle edge cases**

The current implementation has a minor issue - if id is not a valid number, it defaults to 1, but doesn't handle the case where id might be a negative number or other edge cases.

```diff
-  const nextId = Number.isNaN(parseInt(id, 10)) ? 1 : parseInt(id, 10) + 1
+  const parsedId = parseInt(id, 10)
+  const nextId = Number.isNaN(parsedId) || parsedId < 0 ? 1 : parsedId + 1
```

---

`32-32`: **Remove unnecessary async keyword**

This function is marked as async but doesn't contain any await operations.

```diff
-export default async function Page(props: { params: { id: string; locale: string } }) {
+export default function Page(props: { params: { id: string; locale: string } }) {
```

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/hooks/useLink/interface.ts (1)</summary><blockquote>

`4-21`: **Well-structured interface with good documentation for scroll behavior**

The `TLink` interface extends `NextLinkProps` and provides clear documentation for the `scroll` property with its different behaviors. The event handlers for pathname, search params, and hash changes are well-defined to support the component's reactive nature.


I'd suggest adding more details about possible values for the `behavior` property (smooth, auto, instant) to improve documentation completeness.

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/hooks/useLink/index.ts (3)</summary><blockquote>

`35-37`: **Documentation could be improved**

The JSDoc comment for `useLink` focuses only on the locale parameter, but doesn't explain what the hook does overall or what it returns.


Consider adding a more comprehensive description of the hook's purpose and return value:

```diff
 /**
+ * A hook that manages link behavior and localization for Next.js links.
+ * Returns an object with all necessary props for a Next.js link component.
  * @params {props.locale} - The direct locale prop always gets priority. If no `locale` prop is provided, the prop will try to fall back to react-aria `useLocale` and then next/navigation `useParams`. If a locale is found, it will be automatically prepended to the href. Otherwise, href will be returned as is.
  */
```

---

`66-70`: **Scroll handler logic could be clearer**

The `handleScroll` function checks `isNextScroll` to determine whether to scroll, but the logic could be more explicit about handling all possible values of the `scroll` prop.


Consider rewriting for clarity:

```diff
-  const handleScroll = useCallback(() => {
-    if (isNextScroll) return
-
-    scrollToTop(behavior)
-  }, [behavior, isNextScroll])
+  const handleScroll = useCallback(() => {
+    if (scroll === 'top') {
+      scrollToTop(behavior)
+    }
+    // If scroll is true or false, Next.js handles the scrolling
+  }, [behavior, scroll])
```

---

`82-92`: **Consider memoizing callback handlers to prevent unnecessary re-renders**

The effect hooks depend on the callback functions provided as props. If these callbacks aren't memoized by the component using this hook, it could lead to unnecessary re-renders.


Consider adding a note in the documentation recommending that users memoize their callbacks using `useCallback` when passing them to this hook.

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/components/Link/link.stories.tsx (1)</summary><blockquote>

`32-44`: **Console log statements in default args**

The default args include console log statements for event handlers. While acceptable for demonstration purposes in Storybook, ensure these don't make their way into production code.


Consider adding a comment to clarify these are for demonstration purposes only:

```diff
  args: {
    locale: 'en',
    href: '/products/2',
    children: 'Product 2',
    scroll: true,
    behavior: 'instant',
+   // Console logs for demonstration purposes in Storybook
    onClick: (e) => console.log('Click', e),
    onMouseEnter: (e) => console.log('Mouse enter', e),
    onTouchStart: (e) => console.log('Touch start', e),
    onPathnameChange: (pathname) => console.log('Pathname change', pathname),
    onSearchParamsChange: (searchParams) => console.log('Search params change', searchParams),
    onHashChange: (hash) => console.log('Hash change', hash),
  },
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0a261a734fc9180befad28884dbc566a1603ef6b and d7c7938a085593475e78ee59fe25028ba877ac68.

</details>

<details>
<summary>📒 Files selected for processing (23)</summary>

* `apps/demo/app/[locale]/Providers.tsx` (1 hunks)
* `apps/demo/app/[locale]/layout.tsx` (1 hunks)
* `apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx` (1 hunks)
* `apps/demo/app/[locale]/next-component/link/products/layout.tsx` (1 hunks)
* `apps/demo/app/[locale]/next-component/link/products/page.tsx` (1 hunks)
* `libs/stack/next-component/.storybook/preview.tsx` (1 hunks)
* `libs/stack/next-component/package.json` (1 hunks)
* `libs/stack/next-component/postcss.config.js` (1 hunks)
* `libs/stack/next-component/src/components/Link/index.tsx` (1 hunks)
* `libs/stack/next-component/src/components/Link/interface.ts` (1 hunks)
* `libs/stack/next-component/src/components/Link/link.mdx` (1 hunks)
* `libs/stack/next-component/src/components/Link/link.stories.tsx` (1 hunks)
* `libs/stack/next-component/src/hooks/useHash/index.ts` (1 hunks)
* `libs/stack/next-component/src/hooks/useLink/index.ts` (1 hunks)
* `libs/stack/next-component/src/hooks/useLink/interface.ts` (1 hunks)
* `libs/stack/next-component/src/index.ts` (1 hunks)
* `libs/stack/next-component/src/tailwind.css` (1 hunks)
* `libs/stack/next-component/src/theme/Link/index.ts` (1 hunks)
* `libs/stack/next-component/src/theme/index.ts` (1 hunks)
* `libs/stack/next-component/tailwind.config.js` (1 hunks)
* `libs/stack/next-component/tsconfig.storybook.json` (1 hunks)
* `libs/stack/stack-ui/src/components/Button/interface.ts` (1 hunks)
* `libs/stack/stack-ui/src/types/next-link.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (7)</summary>

<details>
<summary>apps/demo/app/[locale]/Providers.tsx (1)</summary><blockquote>

<details>
<summary>libs/stack/stack-ui/.storybook/preview.js (1)</summary>

* `locale` (40-40)

</details>

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/components/Link/interface.ts (2)</summary><blockquote>

<details>
<summary>libs/stack/next-component/src/index.ts (2)</summary>

* `TLinkProps` (8-8)
* `TLink` (9-9)

</details>
<details>
<summary>libs/stack/stack-ui/src/index.ts (1)</summary>

* `TToken` (66-66)

</details>

</blockquote></details>
<details>
<summary>apps/demo/app/[locale]/next-component/link/products/page.tsx (1)</summary><blockquote>

<details>
<summary>apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx (1)</summary>

* `Page` (32-45)

</details>

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/hooks/useLink/index.ts (2)</summary><blockquote>

<details>
<summary>libs/stack/next-component/src/hooks/useLink/interface.ts (2)</summary>

* `TLink` (4-21)
* `TUseLinkReturn` (23-25)

</details>
<details>
<summary>libs/stack/next-component/src/hooks/useHash/index.ts (1)</summary>

* `useHash` (16-32)

</details>

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/hooks/useLink/interface.ts (1)</summary><blockquote>

<details>
<summary>libs/stack/stack-ui/src/types/next-link.ts (1)</summary>

* `NextLinkProps` (3-16)

</details>

</blockquote></details>
<details>
<summary>libs/stack/next-component/src/components/Link/index.tsx (3)</summary><blockquote>

<details>
<summary>libs/stack/next-component/src/components/Link/interface.ts (1)</summary>

* `TLinkProps` (4-4)

</details>
<details>
<summary>libs/stack/next-component/src/index.ts (2)</summary>

* `TLinkProps` (8-8)
* `useLink` (4-4)

</details>
<details>
<summary>libs/stack/next-component/src/hooks/useLink/index.ts (1)</summary>

* `useLink` (38-108)

</details>

</blockquote></details>
<details>
<summary>libs/stack/next-component/tailwind.config.js (1)</summary><blockquote>

<details>
<summary>libs/stack/next-component/postcss.config.js (1)</summary>

* `require` (1-1)

</details>

</blockquote></details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>libs/stack/next-component/src/components/Link/link.mdx</summary>

[uncategorized] ~10-~10: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...kens` and `customTheme` props.  It also add a few more features to the next/link co...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (33)</summary><blockquote>

<details>
<summary>apps/demo/app/[locale]/layout.tsx (1)</summary>

`1-5`: **Well-structured Next.js layout implementation for internationalization!**

This layout component properly implements the Next.js app router pattern for internationalization by extracting the locale parameter and passing it to the Providers component. The code is clean, concise, and follows modern React patterns with proper TypeScript typing.

</details>
<details>
<summary>libs/stack/next-component/src/tailwind.css (1)</summary>

`1-3`: **Standard Tailwind CSS setup looks good.**

These are the required directives for Tailwind CSS to function properly, importing base styles, component classes, and utility classes. The implementation follows best practices.

</details>
<details>
<summary>libs/stack/next-component/src/theme/index.ts (1)</summary>

`1-9`: **Good theme provider implementation with proper memoization.**

The theme provider is correctly implemented using the stack-ui library components. I appreciate the use of React's memo to prevent unnecessary re-renders of the theme provider. The theme structure with separate modules (like linkTheme) follows good separation of concerns.

</details>
<details>
<summary>libs/stack/next-component/tsconfig.storybook.json (1)</summary>

`29-30`: **Appropriate TypeScript configuration update for Storybook.**

Including the `.storybook/*.tsx` pattern ensures that React components in the Storybook configuration are properly processed by TypeScript. This change is necessary to support the new Storybook components mentioned in the PR summary.

</details>
<details>
<summary>libs/stack/next-component/src/components/Link/interface.ts (1)</summary>

`1-4`: **Well-structured TypeScript interface for the Link component**

This interface elegantly combines the properties from `TDefaultComponent` with `TLink`, while avoiding property conflicts through the use of `Omit`. The generic type parameter with a default value provides flexibility for theming tokens.

</details>
<details>
<summary>apps/demo/app/[locale]/Providers.tsx (1)</summary>

`1-10`: **Clean implementation of internationalization provider**

The component is properly marked with 'use client' and effectively wraps children with the I18nProvider from react-aria. The props are well-typed and the implementation is concise.

</details>
<details>
<summary>libs/stack/next-component/.storybook/preview.tsx (1)</summary>

`1-15`: **Appropriate Storybook configuration with theme provider**

The Storybook configuration properly imports Tailwind CSS for styling and wraps all stories with the BaseThemeProvider. This ensures consistent theming across all component stories.

</details>
<details>
<summary>libs/stack/stack-ui/src/components/Button/interface.ts (1)</summary>

`22-24`: **Good deprecation notice with clear migration path**

The deprecation comment follows JSDoc conventions and provides clear guidance on using the new `Link` component from `@okam/next-component` instead. This helps maintain backward compatibility while steering users toward the new component.

</details>
<details>
<summary>libs/stack/next-component/postcss.config.js (1)</summary>

`1-15`: **The PostCSS configuration looks good.**

The configuration correctly sets up Tailwind CSS with a specific config path and includes the autoprefixer plugin. The comment about removing the `postcssConfig` build option is helpful for maintainers.

</details>
<details>
<summary>libs/stack/next-component/tailwind.config.js (1)</summary>

`1-14`: **Well-structured Tailwind configuration.**

The configuration properly sets up content paths for Tailwind to scan, extends the theme, and uses a preset. The use of `createGlobPatternsForDependencies` ensures that Tailwind processes styles from dependencies correctly.

</details>
<details>
<summary>libs/stack/next-component/src/theme/Link/index.ts (1)</summary>

`1-81`: **Well-structured theme implementation with comprehensive variants for link styling.**

The implementation provides a flexible theming system with good default variants and comprehensive styling options. The theme covers various visual states (default, hover, active) and provides multiple style options through variants.



However, I have a few suggestions for improvement:

1. Consider the usage of `!important` flags (e.g., `!bg-color-1-500`) which might lead to CSS specificity issues. These could make it harder to override styles in specific contexts.

2. The theme relies on custom color tokens like `color-1-500`. It would be helpful to document the required Tailwind configuration or ensure these color tokens are properly defined in your theme.

```diff
- !bg-color-1-500
+ bg-color-1-500
```

3. The `focus-ring-black` class in the base styles doesn't follow the same color token pattern used elsewhere. Consider using a consistent approach:

```diff
- focus-ring-black
+ focus-ring-color-1-900
```

</details>
<details>
<summary>libs/stack/next-component/src/components/Link/index.tsx (1)</summary>

`10-42`: **Link component implementation looks solid, with good integration of useLink hook.**

The component effectively wraps NextLink functionality while providing theme support through the Anchor component. The implementation properly forwards refs and handles props appropriately.



A minor consideration for future maintainability:

The ref type includes both HTMLButtonElement and HTMLAnchorElement, which is a bit unusual. If Anchor can render as either element type, this is fine, but if it always renders as an anchor, consider simplifying the ref type to just HTMLAnchorElement for clarity.

```diff
- ref: Ref<HTMLButtonElement & HTMLAnchorElement>
+ ref: Ref<HTMLAnchorElement>
```

</details>
<details>
<summary>libs/stack/stack-ui/src/types/next-link.ts (2)</summary>

`9-9`: **Extended prefetch type to support null values.**

This change improves flexibility by allowing explicit null values for prefetch in addition to true/false. This aligns with Next.js's API which allows disabling prefetching.

---

`15-15`: **Added 'as' property to support custom URL rendering.**

The addition of the `as` property matches Next.js's Link component API and enhances the component's flexibility in how links get rendered. This is particularly useful for dynamic routing and URL rewriting scenarios.

</details>
<details>
<summary>libs/stack/next-component/src/hooks/useHash/index.ts (2)</summary>

`6-9`: **Good browser-safe implementation of getHash.**

The function properly checks if code is running in a browser environment before accessing window.location, preventing issues during server-side rendering.

---

`16-32`: <details>
<summary>❓ Verification inconclusive</summary>

**Solid implementation of useHash hook with good documentation.**

The hook provides a clean way to access and react to URL hash changes. The implementation includes proper state initialization and event handling.



However, there's a potential browser compatibility issue:

The `URL.canParse()` method (line 21) is relatively new and might not be supported in all browsers. Consider using a more widely supported method to check URL validity:

```diff
- if (!URL.canParse(newURL)) return
+ try {
+   new URL(newURL);
+ } catch (e) {
+   return;
+ }
```

---

🌐 Web query:

```web
Is URL.canParse() supported in all major browsers?
```

💡 Result:

The `URL.canParse()` static method, which validates URLs without throwing errors, is supported in all major modern browsers as of late 2023 and early 2024. However, older browser versions lack support:

**Browser Support Overview**:
| Browser       | Supported Versions          | Release Date Window |
|---------------|-----------------------------|---------------------|
| Chrome        | 120+                        | Dec 2023 onward    |
| Edge          | 120+                        | Dec 2023 onward    |
| Firefox       | 115+                        | Jul 2023 onward    |
| Safari        | 17+ (macOS/iOS)             | Sep 2023 onward    |
| Opera         | 106+                        | 2023 onward        |

**Key details**:
- **Unsupported in older versions**: Chrome/Edge ≤119, Firefox ≤114, Safari ≤16.6, and Opera ≤105 lack support[1][4][6]
- **Mobile support**: Available in Safari iOS 17+, Chrome Android 120+, and Firefox Android 115+[4][6]
- **Node.js/TypeScript caveats**: Not natively supported in Node.js v22 or TypeScript's `@types/node` definitions[2]

Developers can verify support using feature detection:
```javascript
if ('canParse' in URL) {
  // Use native implementation
} else {
  // Fallback to try/catch or polyfill
}
```

For environments without native support, a polyfill using `try/catch` works[3][7]:
```javascript
function canParseURL(str) {
  try { new URL(str); return true }
  catch { return false }
}
```

Citations:

- 1: https://caniuse.com/?search=URL.canParse
- 2: https://davidwalsh.name/url-canparse
- 3: https://www.stefanjudis.com/blog/validate-urls-in-javascript/
- 4: https://caniuse.com/mdn-api_url_canparse_static
- 5: https://developer.mozilla.org/en-US/docs/Web/API/URL/parse_static
- 6: https://web-platform-dx.github.io/web-features-explorer/features/url-canparse/
- 7: https://kilianvalkhof.com/2024/javascript/the-problem-with-new-url-and-how-url-parse-fixes-that/
- 8: https://developer.mozilla.org/en-US/docs/Web/API/URL/canParse_static
- 9: https://webtoolsweekly.com/archives/issue-548/
- 10: https://developer.mozilla.org/en-US/docs/Web/API/URL

---



</details>

**Attention: Consider Fallback for Older Browsers**

The implementation of the `useHash` hook is solid, with clear state initialization and event handling. The documentation is well-written and the hook effectively reacts to hash changes. 

However, although modern browsers (Chrome 120+, Edge 120+, Firefox 115+, Safari 17+, and Opera 106+) support the `URL.canParse()` method, older versions (e.g., Chrome/Edge ≤119, Firefox ≤114, Safari ≤16.6, Opera ≤105) do not. If your target audience might use these older browsers, it would be prudent to implement a feature-detection fallback. For example:

```diff
- if (!URL.canParse(newURL)) return
+ if ('canParse' in URL) {
+   if (!URL.canParse(newURL)) return;
+ } else {
+   try {
+     new URL(newURL);
+   } catch (e) {
+     return;
+   }
+ }
```

This approach ensures that URL validation is handled gracefully regardless of the browser version.

</details>
<details>
<summary>apps/demo/app/[locale]/next-component/link/products/layout.tsx (1)</summary>

`7-18`: **Layout structure looks good**

The layout structure with header, content section, children, and footer is well-organized and follows a logical flow. The use of Box components with custom theming provides good visual separation and structure.

</details>
<details>
<summary>libs/stack/next-component/src/index.ts (2)</summary>

`2-5`: **Exports look good**

The new exports for the Link component and the useLink/useHash hooks are properly structured and will make these features available to consumers of the library.

---

`8-9`: **Type exports look good**

The type exports for TLinkProps and TLink are appropriately defined, ensuring type safety for consumers of the library.

</details>
<details>
<summary>libs/stack/next-component/src/components/Link/link.mdx (3)</summary>

`1-5`: **Storybook imports and configuration look good**

The imports and Meta configuration for the Storybook documentation are correct and follow best practices.

---

`12-17`: **Feature list is clear and informative**

The additional features section effectively communicates the benefits of using this Link component beyond standard next/link functionality.

---

`24-38`: **Examples are clear and helpful**

The example code blocks effectively demonstrate the component's usage and the locale functionality. The comments explain what's happening clearly.

</details>
<details>
<summary>apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx (3)</summary>

`14-16`: **Good route construction approach**

The approach to constructing the route based on locale and handling the next ID calculation is clear and effective.

---

`18-29`: **Component structure looks good**

The UI structure with typography elements for title and description, followed by links, creates a clean and understandable interface.

---

`33-44`: **Good demonstration of different scroll behaviors**

The Page component effectively demonstrates the three different scroll behaviors (default, disabled, and scroll to top) by rendering the ProductsLinks component with different props.

</details>
<details>
<summary>libs/stack/next-component/src/hooks/useLink/interface.ts (1)</summary>

`23-25`: **Good type safety enhancement for the return value**

Making `href` required and specifying it as a string ensures type safety when using the hook's return value in a component.

</details>
<details>
<summary>libs/stack/next-component/src/hooks/useLink/index.ts (4)</summary>

`9-11`: **Simple and effective utility function for scroll behavior**

This utility function correctly implements scrolling to the top with configurable behavior.

---

`13-17`: **Good handling of locale extraction**

The function correctly handles both string and array types for the locale parameter.

---

`19-33`: **Well-documented locale priority logic**

The `useLinkLocale` hook has clear documentation about the priority order for locale selection, making it easy to understand the fallback mechanism.

---

`94-107`: **Comprehensive link props return**

The return object provides all necessary props for the Next.js link component, with proper handling of custom props like `urlDecorator` and event handlers.

</details>
<details>
<summary>libs/stack/next-component/src/components/Link/link.stories.tsx (3)</summary>

`5-32`: **Good Storybook setup with visual context**

The meta configuration includes a decorator that adds content to demonstrate scrolling behavior, which is an excellent approach for visually testing the component's functionality.

---

`45-145`: **Comprehensive argTypes definitions**

The argTypes definitions provide thorough documentation for each prop, including detailed descriptions and categorization. This is excellent for developer understanding.

---

`152-164`: **Good story variations for different scroll behaviors**

The three story variations effectively demonstrate the different scroll behaviors (default, false, and 'top'). This makes it easy to understand and test the component's scrolling functionality.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-07T18:16:58Z
user: login: marie-maxime
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T20:54:25Z

Review comments:
diff_hunk: @@ -9,6 +9,9 @@
   },
   "dependencies": {
     "@okam/stack-ui": "1.32.6",
-    "next": "^14.1.1"
+    "next": "^14.1.1",
+    "react-use": "17.5.1",
+    "react": "18.3.1",
+    "tailwind-variants": "^0.3.0"
path: libs/stack/next-component/package.json
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Review dependency management approach.**

The new dependencies added seem appropriate for implementing a NextLink wrapper component. However, I have two concerns:

1. React is being added as a direct dependency with version 18.3.1, which is quite recent. In component libraries, React is typically declared as a peer dependency to avoid version conflicts with the consuming application.

2. The versioning approach is inconsistent - some dependencies use exact versions while others use caret (^) versioning.


Consider making the following changes:
```diff
-    "next": "^14.1.1",
-    "react-use": "17.5.1",
-    "react": "18.3.1",
-    "tailwind-variants": "^0.3.0"
+    "next": "^14.1.1",
+    "react-use": "^17.5.1",
+    "tailwind-variants": "^0.3.0"
```

And add React as a peer dependency:
```json
"peerDependencies": {
  "react": "^18.0.0"
}
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-07T18:16:57Z
author_association: NONE
diff_hunk: @@ -0,0 +1,108 @@
+'use client'
+
+import type { Params } from 'next/dist/shared/lib/router/utils/route-matcher'
+import { useParams, usePathname, useSearchParams } from 'next/navigation'
+import { useCallback, useEffect } from 'react'
+import { useHash } from '../useHash'
+import type { TLink, TUseLinkReturn } from './interface'
+
+function scrollToTop(behavior: ScrollBehavior) {
+  window?.scrollTo?.({ top: 0, behavior })
+}
+
+function getParamsLocale(params: Params) {
+  const { locale } = params
+  if (Array.isArray(locale)) return locale[0]
+  return locale
+}
+
+/**
+ * Tries to get the locale, in order of priority:
+ * 1. The locale prop. Still has priority even when set to `false`
+ * 2. The locale from react-aria `useLocale`
+ * 3. The locale from next/navigation `useParams`
+ * @returns The best matched locale
+ */
+export function useLinkLocale(props: TLink) {
+  const { locale } = props
+  const params = useParams()
+  const paramsLocale = getParamsLocale(params)
+  if (locale === false) return false
+
+  return locale ?? paramsLocale ?? false
+}
+
+/**
+ * @params {props.locale} - The direct locale prop always gets priority. If no `locale` prop is provided, the prop will try to fall back to react-aria `useLocale` and then next/navigation `useParams`. If a locale is found, it will be automatically prepended to the href. Otherwise, href will be returned as is.
+ */
+export function useLink(props: TLink): TUseLinkReturn {
+  const {
+    scroll = true,
+    onMouseEnter,
+    onTouchStart,
+    onClick,
+    onPathnameChange,
+    onHashChange,
+    onSearchParamsChange,
+    href,
+    urlDecorator,
+    replace,
+    prefetch,
+    shallow,
+    passHref,
+    legacyBehavior,
+    behavior = 'instant',
+  } = props
+
+  const locale = useLinkLocale(props)
+  const localizedHref = locale ? `/${locale}${href}` : href
path: libs/stack/next-component/src/hooks/useLink/index.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Potential issue with href path construction**

The current implementation assumes `href` always starts with a path character. If `href` doesn't start with a `/`, this will create an invalid URL structure.


Consider adding a check to ensure proper path formatting:

```diff
-  const localizedHref = locale ? `/${locale}${href}` : href
+  const formattedHref = href.startsWith('/') ? href : `/${href}`
+  const localizedHref = locale ? `/${locale}${formattedHref}` : formattedHref
```

Or at minimum:

```diff
-  const localizedHref = locale ? `/${locale}${href}` : href
+  const localizedHref = locale ? `/${locale}${href.startsWith('/') ? '' : '/'}${href}` : href
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  const locale = useLinkLocale(props)
  const formattedHref = href.startsWith('/') ? href : `/${href}`
  const localizedHref = locale ? `/${locale}${formattedHref}` : formattedHref
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-07T18:16:57Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-04-07T18:08:49Z
body: [vc]: #Tg8XHbEV9yq6OhC2trVMhZlz8GfDbrIgdVAGvG3D8vs=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzdGFjayIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9va2FtL3N0YWNrL0hxZUNCZUV1aHlYVXdZMUdyNE5XOVB1U2Z6aDQiLCJwcmV2aWV3VXJsIjoic3RhY2stZ2l0LXN0YWNrLTc2LWNvbXBvbmVudC1uZXh0bGluay13cmFwcGVyLW9rYW0udmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzdGFjay1naXQtc3RhY2stNzYtY29tcG9uZW50LW5leHRsaW5rLXdyYXBwZXItb2thbS52ZXJjZWwuYXBwIn0sInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **stack** | ✅ Ready ([Inspect](https://vercel.com/okam/stack/HqeCBeEuhyXUwY1Gr4NW9PuSfzh4)) | [Visit Preview](https://stack-git-stack-76-component-nextlink-wrapper-okam.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/stack-git-stack-76-component-nextlink-wrapper-okam.vercel.app?via=pr-comment-feedback-link) | Apr 29, 2025 8:58pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-07T18:08:53Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces several new React components, hooks, configuration files, and documentation updates. New locale-based providers and layouts wrap content using an I18nProvider. Multiple product page components add structured navigation with dynamic routing and scroll behaviour. A themeable Link component is implemented with supporting hooks for link management and URL hash handling, accompanied by comprehensive Storybook documentation and stories. Additional changes include dependency updates, new PostCSS/Tailwind configurations, and modifications to existing type definitions and deprecated documentation in the UI library.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `apps/demo/app/[locale]/Providers.tsx`<br>`apps/demo/app/[locale]/layout.tsx` | Added new Providers and RootLayout components that wrap children with an I18nProvider based on locale. |
| `apps/demo/app/[locale]/next-component/link/products/[id]/page.tsx`<br>`apps/demo/app/[locale]/next-component/link/products/layout.tsx`<br>`apps/demo/app/[locale]/next-component/link/products/page.tsx` | Introduced product navigation pages including ProductsLinks, Layout, and Page components for dynamic routing and scroll behaviour. |
| `libs/stack/next-component/.storybook/preview.tsx`<br>`libs/stack/next-component/tsconfig.storybook.json` | Added a Storybook preview setup to wrap stories in a BaseThemeProvider and updated TS config to include `.tsx` files. |
| `libs/stack/next-component/package.json`<br>`libs/stack/next-component/postcss.config.js`<br>`libs/stack/next-component/tailwind.config.js`<br>`libs/stack/next-component/src/tailwind.css` | Updated dependencies and introduced new PostCSS and Tailwind CSS configuration for the library. |
| `libs/stack/next-component/src/components/Link/index.tsx`<br>`libs/stack/next-component/src/components/Link/interface.ts`<br>`libs/stack/next-component/src/components/Link/link.mdx`<br>`libs/stack/next-component/src/components/Link/link.stories.tsx` | Added a new, themeable Link component integrated with NextLink, with associated TypeScript interfaces, MDX documentation, and Storybook stories. |
| `libs/stack/next-component/src/hooks/useHash/index.ts`<br>`libs/stack/next-component/src/hooks/useLink/index.ts`<br>`libs/stack/next-component/src/hooks/useLink/interface.ts` | Implemented useHash and useLink hooks to manage URL hash extraction and link localization with enhanced event handling. |
| `libs/stack/next-component/src/index.ts`<br>`libs/stack/next-component/src/theme/Link/index.ts`<br>`libs/stack/next-component/src/theme/index.ts` | Updated module exports and theme configuration, including a theme provider that integrates custom link theming. |
| `libs/stack/stack-ui/src/components/Button/interface.ts`<br>`libs/stack/stack-ui/src/types/next-link.ts` | Modified NextLinkProps to allow `prefetch` as boolean or null and added an `as` property. Added a deprecation comment to nextLinkProps in the Button interface. |
| `libs/stack/stack-ui/src/theme/index.tsx` | Added `link` property to BaseTheme as an alias to `button` styling. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant U as User
    participant L as Link Component
    participant H as useLink Hook
    participant N as NextLink Router

    U->>L: Click event
    L->>H: Process click and generate localized URL
    H->>H: Apply scroll behaviour and event handling
    H->>L: Return enhanced link props
    L->>N: Navigate to localized URL
    N->>U: Render new page
```

## Possibly related PRs
- OKAMca/stack#268: Shares identical changes introducing the Providers component and locale-based RootLayout, indicating overlap in locale handling and layout structure.

## Suggested reviewers
- poclerson
- JeromeTrottier

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3a3f6d73795d114e3f143c8caba0a4770f4c8bd4 and 4db2d74fdf23085345465c753e06168c1236a63e.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `libs/stack/next-component/package.json` (1 hunks)
* `libs/stack/stack-ui/src/theme/index.tsx` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=OKAMca/stack&utm_content=268):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE