Skip to content

Commit

Permalink
feat(link): add support for client side navigation (#16)
Browse files Browse the repository at this point in the history
* refactor: move useDomRef into hooks directory

* feat(link): use @react-aria/utils router to handle client side navigation

* fix(hooks): export use design system context
  • Loading branch information
PHILLIPS71 authored Apr 16, 2024
1 parent dba68fa commit f7b4fc5
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 32 deletions.
2 changes: 2 additions & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
},
"dependencies": {
"@giantnodes/theme": "workspace:*",
"@react-aria/utils": "^3.23.2",
"clsx": "^2.1.0",
"react-aria": "^3.32.1",
"react-aria-components": "^1.1.1",
"tailwindcss-react-aria-components": "^1.1.1"
},
"devDependencies": {
"@jest/types": "^29.6.3",
"@react-types/shared": "^3.22.1",
"@storybook/addon-backgrounds": "^8.0.1",
"@storybook/addon-essentials": "8.0.1",
"@storybook/addon-interactions": "8.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/form/FormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from 'react'
import { useField } from 'react-aria'

import { FormGroupContext, useFormGroup } from '@/components/form/use-form-group.hook'
import { useDomRef } from '@/utilities/dom'
import { useDomRef } from '@/hooks/use-dom-ref'

export type FormGroupProps = ComponentWithoutAs<'input'> &
Omit<UseFormGroupProps, 'ref'> & {
Expand Down
30 changes: 18 additions & 12 deletions packages/react/src/components/link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import type { ComponentWithoutAs } from '@/utilities/types'
import type { LinkProps as ComponentProps } from 'react-aria-components'
import type { LinkProps } from 'react-aria-components'

import { link } from '@giantnodes/theme'
import React from 'react'
import { Link as Component } from 'react-aria-components'
import { Link } from 'react-aria-components'

import { useLink } from '@/components/link/use-link.hook'
import { useDomRef } from '@/hooks/use-dom-ref'
import { useLink } from '@/hooks/use-link.hook'

export type LinkProps = ComponentWithoutAs<'a'> & ComponentProps
type ComponentProps = ComponentWithoutAs<'a'> & LinkProps

const Link = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const { children, className, ...rest } = props
const Component = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const { children, className } = props

const { slots } = useLink()
const dom = useDomRef(ref)
const { ...rest } = useLink(props, dom)

const getProps = React.useCallback(
const slots = React.useMemo(() => link({}), [])

const component = React.useMemo<LinkProps>(
() => ({
ref,
className: slots.base({ className }),
className: slots.link({ class: className?.toString() }),
...rest,
}),
[ref, slots, className, rest]
)

return <Component {...getProps()}>{children}</Component>
return <Link {...component}>{children}</Link>
})

Link.displayName = 'Link'
Component.displayName = 'Link'

export default Link
export { ComponentProps as LinkProps }
export default Component
16 changes: 0 additions & 16 deletions packages/react/src/components/link/use-link.hook.ts

This file was deleted.

29 changes: 29 additions & 0 deletions packages/react/src/hooks/use-design-system.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { RouterProvider } from '@react-aria/utils'

import { createContext } from '@/utilities/context'

export type UseDesignSystemProps = React.PropsWithChildren & {
/**
* Provides a client side router to all components that contain links
*/
navigate?: (path: string) => void
}

export type UseDesignSystemReturn = ReturnType<typeof useDesignSystem>

export const useDesignSystem = ({ navigate, children }: UseDesignSystemProps) => {
let contents = children

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

return contents
}

export const [DesignSystemContext, useDesignSystemContext] = createContext<UseDesignSystemReturn>({
name: 'DesignSystemContext',
strict: true,
errorMessage:
'useDesignSystemContext: `context` is undefined. Seems you forgot to wrap component within <DesignSystem.Provider />',
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import type { Ref, RefObject } from 'react'

import { useImperativeHandle, useRef } from 'react'

// eslint-disable-next-line import/prefer-default-export
export const useDomRef = <T extends HTMLElement = HTMLElement>(ref?: RefObject<T | null> | Ref<T | null>) => {
const domRef = useRef<T>(null)

useImperativeHandle(ref, () => domRef.current)

return domRef
}

export type UseDomRefReturn = ReturnType<typeof useDomRef>
25 changes: 25 additions & 0 deletions packages/react/src/hooks/use-link.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { FocusableElement, PressEvent } from '@react-types/shared'
import type React from 'react'
import type { AriaLinkOptions } from 'react-aria'

import { shouldClientNavigate, useRouter } from '@react-aria/utils'
import { useLink as useAriaLink } from 'react-aria'

export const useLink = (props: AriaLinkOptions, ref: React.RefObject<FocusableElement>) => {
const { onPress: onAriaPress, ...rest } = props
const router = useRouter()

const onPress = (event: PressEvent) => {
const { target } = event

if (!(target instanceof HTMLAnchorElement)) return

if (!router.isNative && target.href && shouldClientNavigate(target, event)) {
router.open(target, event)
}
}

return useAriaLink({ ...rest, onPress }, ref)
}

export type UseLinkReturn = ReturnType<typeof useLink>
5 changes: 5 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Components
export * from '@/components/alert'
export * from '@/components/avatar'
export * from '@/components/breadcrumb'
Expand All @@ -18,4 +19,8 @@ export * from '@/components/switch'
export * from '@/components/table'
export * from '@/components/typography'

// Hooks
export * from '@/hooks/use-design-system'

// Utilities
export * from '@/utilities/types'
2 changes: 1 addition & 1 deletion packages/theme/src/components/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tv } from 'tailwind-variants'

export const link = tv({
slots: {
base: [
link: [
'text-sm',
'text-inherit',
'hover:underline hover:text-sky-600',
Expand Down
7 changes: 6 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f7b4fc5

Please sign in to comment.