Skip to content

Commit

Permalink
Make design system router platform agnostic (#32)
Browse files Browse the repository at this point in the history
# Make design system router platform agnostic

## ♻️ Current situation & Problem
Closes #31 
Closes #30 

## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
arkadiuszbachorski authored Jan 7, 2025
1 parent 95f81a6 commit 0fed267
Show file tree
Hide file tree
Showing 12 changed files with 7,124 additions and 7,948 deletions.
14,760 changes: 6,950 additions & 7,810 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 18 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.3",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-router": "^1.77.5",
"@tanstack/react-table": "^8.20.5",
"class-variance-authority": "^0.7.0",
"date-fns": "^3.6.0",
Expand All @@ -259,14 +258,14 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@storybook/addon-essentials": "^8.3.5",
"@storybook/addon-interactions": "^8.3.5",
"@storybook/addon-links": "^8.3.5",
"@storybook/addon-onboarding": "^8.3.5",
"@storybook/blocks": "^8.3.5",
"@storybook/react": "^8.3.5",
"@storybook/react-vite": "^8.3.5",
"@storybook/test": "^8.3.5",
"@storybook/addon-essentials": "^8.4.7",
"@storybook/addon-interactions": "^8.4.7",
"@storybook/addon-links": "^8.4.7",
"@storybook/addon-onboarding": "^8.4.7",
"@storybook/blocks": "^8.4.7",
"@storybook/react": "^8.4.7",
"@storybook/react-vite": "^8.4.7",
"@storybook/test": "^8.4.7",
"@testing-library/jest-dom": "^6",
"@testing-library/react": "^16",
"@total-typescript/ts-reset": "^0.6.1",
Expand All @@ -276,29 +275,28 @@
"@types/react-helmet": "^6.1.11",
"@typescript-eslint/eslint-plugin": "^8",
"@typescript-eslint/parser": "^8",
"@vitejs/plugin-react": "^4.3.3",
"@vitest/coverage-v8": "^2.1.4",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^2.1.8",
"autoprefixer": "^10",
"esbuild": "^0.24.0",
"eslint": "^8",
"eslint-config-next": "^14",
"eslint-config-next": "^15",
"eslint-config-prettier": "^9",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "^2",
"eslint-plugin-prettier": "^5",
"jsdom": "^25.0.1",
"postcss": "^8",
"postcss-import": "^16.1.0",
"prettier": "^3",
"prettier-plugin-tailwindcss": "^0.6.8",
"storybook": "^8.3.5",
"prettier-plugin-tailwindcss": "^0.6.9",
"storybook": "^8.4.7",
"tailwindcss": "^3",
"tailwindcss-animate": "^1.0.7",
"typedoc": "^0.26",
"typedoc": "^0.27",
"typescript": "^5",
"vite": "^5.4.10",
"vite-plugin-dts": "^4.3.0",
"vitest": "^2.1.4"
"vite": "^6.0.7",
"vite-plugin-dts": "^4.4.0",
"vitest": "^2.1.8"
},
"peerDependencies": {
"next-intl": "^3",
Expand Down
46 changes: 43 additions & 3 deletions src/SpeziProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,47 @@
// SPDX-License-Identifier: MIT

import { NextIntlClientProvider } from 'next-intl'
import { type ReactNode, useLayoutEffect, useMemo } from 'react'
import {
type ComponentProps,
createContext,
type ReactNode,
useContext,
useLayoutEffect,
useMemo,
} from 'react'
import { messages as defaultMessages, type AllMessages } from '@/messages'
import { lightTheme } from '@/theme/light'
import { type Theme } from '@/theme/utils'

interface SpeziProviderProps {
/**
* Allows injecting necessary router-related components.
* Projects can have different routers:
* Tanstack Router, React Router, Next router
* */
interface SpeziContextRouter {
/**
* Link component. Make sure to provide your router's Link component.
* */
Link: (props: ComponentProps<'a'>) => ReactNode
}

export interface SpeziContextType {
router: SpeziContextRouter
}

export const SpeziContext = createContext<SpeziContextType | null>(null)

export const useSpeziContext = () => {
const value = useContext(SpeziContext)
if (!value) {
throw new Error(
'useSpeziContext must be used within SpeziProvider. Make sure to wrap your application with SpeziProvider',
)
}
return value
}

interface SpeziProviderProps extends SpeziContextType {
children?: ReactNode
theme?: Partial<Theme>
messages?: Partial<AllMessages>
Expand All @@ -24,6 +59,7 @@ export const SpeziProvider = ({
children,
messages,
theme,
router,
}: SpeziProviderProps) => {
useLayoutEffect(() => {
const resolvedTheme = { ...lightTheme, ...theme }
Expand All @@ -39,9 +75,13 @@ export const SpeziProvider = ({
[messages],
)

const speziContextValue = useMemo(() => ({ router }), [router])

return (
<NextIntlClientProvider messages={resolvedMessages} locale="en">
{children}
<SpeziContext.Provider value={speziContextValue}>
{children}
</SpeziContext.Provider>
</NextIntlClientProvider>
)
}
3 changes: 1 addition & 2 deletions src/components/Async/Async.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
// SPDX-License-Identifier: MIT
//

import { omit } from 'es-toolkit'
import { isBoolean, isUndefined } from 'lodash'
import { omit, isBoolean, isUndefined } from 'es-toolkit'
import { type ReactNode } from 'react'
import { ErrorState, type ErrorStateProps } from '@/components/ErrorState'
import { Spinner } from '@/components/Spinner'
Expand Down
47 changes: 29 additions & 18 deletions src/components/Breadcrumb/Breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

import { Slot } from '@radix-ui/react-slot'
import { Link } from '@tanstack/react-router'
import { ChevronRight, MoreHorizontal } from 'lucide-react'
import {
type ComponentProps,
Expand All @@ -21,6 +20,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/DropdownMenu'
import { useSpeziContext } from '@/SpeziProvider'
import { cn } from '@/utils/className'

export const Breadcrumb = forwardRef<
Expand Down Expand Up @@ -64,7 +64,10 @@ export const BreadcrumbLink = forwardRef<
HTMLAnchorElement,
BreadcrumbLinkProps
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : 'a'
const {
router: { Link },
} = useSpeziContext()
const Comp = asChild ? Slot : Link

return (
<Comp
Expand Down Expand Up @@ -136,21 +139,26 @@ const BreadcrumbCompleteItem = ({
href,
isActive,
label,
}: BreadcrumbCompleteItemProps) => (
<>
<BreadcrumbItem>
{isActive ?
<BreadcrumbPage className="max-w-25 truncate md:max-w-none">
{label}
</BreadcrumbPage>
: <BreadcrumbLink asChild className="max-w-25 truncate md:max-w-none">
<Link to={href}>{label}</Link>
</BreadcrumbLink>
}
</BreadcrumbItem>
{!isActive && <BreadcrumbSeparator />}
</>
)
}: BreadcrumbCompleteItemProps) => {
const {
router: { Link },
} = useSpeziContext()
return (
<>
<BreadcrumbItem>
{isActive ?
<BreadcrumbPage className="max-w-25 truncate md:max-w-none">
{label}
</BreadcrumbPage>
: <BreadcrumbLink asChild className="max-w-25 truncate md:max-w-none">
<Link href={href}>{label}</Link>
</BreadcrumbLink>
}
</BreadcrumbItem>
{!isActive && <BreadcrumbSeparator />}
</>
)
}

interface BreadcrumbsProps {
breadcrumbs: Array<{ href: string; label: ReactNode }>
Expand All @@ -170,6 +178,9 @@ export const Breadcrumbs = ({
breadcrumbs,
maxToDisplay = 3,
}: BreadcrumbsProps) => {
const {
router: { Link },
} = useSpeziContext()
const firstBreadcrumb = breadcrumbs.at(0)
const hasTruncatedBreadcrumbs = breadcrumbs.length > maxToDisplay
const remainingBreadcrumbs =
Expand Down Expand Up @@ -201,7 +212,7 @@ export const Breadcrumbs = ({
.slice(1, -maxToDisplay + 1)
.map((breadcrumb, index) => (
<DropdownMenuItem key={index}>
<Link to={breadcrumb.href}>{breadcrumb.label}</Link>
<Link href={breadcrumb.href}>{breadcrumb.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
Expand Down
11 changes: 7 additions & 4 deletions src/components/Pagination/LinkPagination/LinkPagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
usePagination,
type UsePaginationProps,
} from '@nextui-org/use-pagination'
import { Link } from '@tanstack/react-router'
import { useSpeziContext } from '@/SpeziProvider'
import {
Pagination,
PaginationContent,
Expand Down Expand Up @@ -43,6 +43,9 @@ export const LinkPagination = ({
showControls = true,
...props
}: LinkPaginationProps) => {
const {
router: { Link },
} = useSpeziContext()
const { activePage, range } = usePagination({
total,
page,
Expand All @@ -59,7 +62,7 @@ export const LinkPagination = ({
return (
<PaginationItemContainer key={rangePage}>
<PaginationPrevious asChild>
<Link to={getHref(activePage - 1)}>
<Link href={getHref(activePage - 1)}>
<PaginationPreviousIcon />
</Link>
</PaginationPrevious>
Expand All @@ -71,7 +74,7 @@ export const LinkPagination = ({
return (
<PaginationItemContainer key={rangePage}>
<PaginationNext asChild>
<Link to={getHref(activePage + 1)}>
<Link href={getHref(activePage + 1)}>
<PaginationNextIcon />
</Link>
</PaginationNext>
Expand All @@ -87,7 +90,7 @@ export const LinkPagination = ({
return (
<PaginationItemContainer key={rangePage}>
<PaginationItem isActive={rangePage === page}>
<Link to={getHref(rangePage)}>{rangePage}</Link>
<Link href={getHref(rangePage)}>{rangePage}</Link>
</PaginationItem>
</PaginationItemContainer>
)
Expand Down
61 changes: 33 additions & 28 deletions src/molecules/DashboardLayout/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
// SPDX-License-Identifier: MIT
//

import { Link } from '@tanstack/react-router'
import { type ReactNode } from 'react'
import { useSpeziContext } from '@/SpeziProvider'
import { Tooltip } from '../../components/Tooltip'
import { cn } from '../../utils/className'

Expand All @@ -25,31 +25,36 @@ export const MenuItem = ({
isActive,
label,
isHighlighted,
}: MenuItemProps) => (
<Tooltip
key={href}
tooltip={label}
sideOffset={8}
side="right"
className="hidden lg:block xl:hidden"
>
<Link
to={href}
className={cn(
'focus-ring relative flex items-center gap-3 rounded-lg p-2 font-medium no-underline transition xl:w-full xl:self-start',
isActive ?
'bg-accent/50 text-primary hover:opacity-60'
: 'text-foreground/60 hover:bg-accent hover:text-foreground',
)}
}: MenuItemProps) => {
const {
router: { Link },
} = useSpeziContext()
return (
<Tooltip
key={href}
tooltip={label}
sideOffset={8}
side="right"
className="hidden lg:block xl:hidden"
>
{icon}
<span className="grow lg:hidden xl:block">{label}</span>
{isHighlighted && (
<i
aria-hidden
className="size-2.5 rounded-full bg-destructive lg:absolute lg:right-1 lg:top-1 lg:size-1.5 xl:static xl:size-2.5"
/>
)}
</Link>
</Tooltip>
)
<Link
href={href}
className={cn(
'focus-ring relative flex items-center gap-3 rounded-lg p-2 font-medium no-underline transition xl:w-full xl:self-start',
isActive ?
'bg-accent/50 text-primary hover:opacity-60'
: 'text-foreground/60 hover:bg-accent hover:text-foreground',
)}
>
{icon}
<span className="grow lg:hidden xl:block">{label}</span>
{isHighlighted && (
<i
aria-hidden
className="size-2.5 rounded-full bg-destructive lg:absolute lg:right-1 lg:top-1 lg:size-1.5 xl:static xl:size-2.5"
/>
)}
</Link>
</Tooltip>
)
}
Loading

0 comments on commit 0fed267

Please sign in to comment.