Skip to content

Commit

Permalink
feat: add ui-tab-routes
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Nov 30, 2023
1 parent 5e42482 commit da9cdf4
Show file tree
Hide file tree
Showing 20 changed files with 617 additions and 8 deletions.
4 changes: 2 additions & 2 deletions apps/web/src/app/app-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ActionIcon, Anchor, Box, Card, Group, Text } from '@mantine/core'
import { UiContainer, useUiColorScheme } from '@pubkey-ui/core'
import { UiContainer, useUiColorScheme, useUiTheme } from '@pubkey-ui/core'
import { IconMoon, IconSun } from '@tabler/icons-react'
import { ReactNode } from 'react'
import { Link } from 'react-router-dom'

export function AppLayout({ children }: { children: ReactNode }) {
const { Link } = useUiTheme()
return (
<Box>
<Card p="0" display="block">
Expand Down
7 changes: 5 additions & 2 deletions apps/web/src/app/app-routes.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Navigate, RouteObject, useRoutes } from 'react-router-dom'
import { UiThemeLink } from '@pubkey-ui/core'
import { Link, Navigate, RouteObject, useRoutes } from 'react-router-dom'
import { DemoFeature } from './features'
import { DashboardFeature } from './features/dashboard/dashboard-feature'
import { DevFeature } from './features/dev/dev-feature'

const routes: RouteObject[] = [
{ path: '/', element: <Navigate to="/dashboard" replace /> },
{ path: '/dashboard', element: <DashboardFeature /> },
{ path: '/demo', element: <DemoFeature /> },
{ path: '/demo/*', element: <DemoFeature /> },
{ path: '/dev', element: <DevFeature /> },
]

export function AppRoutes() {
return useRoutes(routes)
}

export const ThemeLink: UiThemeLink = ({ children, ...props }) => <Link {...props}>{children}</Link>
6 changes: 2 additions & 4 deletions apps/web/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { UiThemeProvider } from '@pubkey-ui/core'
import { Link } from 'react-router-dom'
import { AppLayout } from './app-layout'

import { AppRoutes } from './app-routes'
import { AppRoutes, ThemeLink } from './app-routes'

export function App() {
return (
<UiThemeProvider link={({ children, ...props }) => <Link {...props}>{children}</Link>}>
<UiThemeProvider link={ThemeLink}>
<AppLayout>
<AppRoutes />
</AppLayout>
Expand Down
42 changes: 42 additions & 0 deletions apps/web/src/app/features/demo/demo-feature-tab-routes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { SimpleGrid } from '@mantine/core'
import { UiCard, UiTabRoutes } from '@pubkey-ui/core'
import { DemoCard } from './demo-card'

export function DemoFeatureTabRoutes() {
return (
<DemoCard title="Time">
<UiTabRoutes
baseUrl="/demo"
tabs={[
{
value: 'overview',
label: 'Overview',
component: (
<SimpleGrid cols={2} spacing="md">
<UiCard title="Overview">Overview</UiCard>
</SimpleGrid>
),
},
{
value: 'content',
label: 'Content',
component: (
<SimpleGrid cols={2} spacing="md">
<UiCard title="Content">Content</UiCard>
</SimpleGrid>
),
},
{
value: 'settings',
label: 'Settings',
component: (
<SimpleGrid cols={2} spacing="md">
<UiCard title="Settings">Settings</UiCard>
</SimpleGrid>
),
},
]}
/>
</DemoCard>
)
}
2 changes: 2 additions & 0 deletions apps/web/src/app/features/demo/demo-feature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DemoFeatureDebug } from './demo-feature-debug'
import { DemoFeatureGroup } from './demo-feature-group'
import { DemoFeatureSearchInput } from './demo-feature-search-input'
import { DemoFeatureStack } from './demo-feature-stack'
import { DemoFeatureTabRoutes } from './demo-feature-tab-routes'
import { DemoFeatureTime } from './demo-feature-time'
import { DemoFeatureToast } from './demo-feature-toast'

Expand All @@ -20,6 +21,7 @@ export function DemoFeature() {
<DemoFeatureGroup />
<DemoFeatureSearchInput />
<DemoFeatureStack />
<DemoFeatureTabRoutes />
<DemoFeatureTime />
<DemoFeatureToast />
</UiStack>
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './ui-debug'
export * from './ui-group'
export * from './ui-search-input'
export * from './ui-stack'
export * from './ui-tab-routes'
export * from './ui-theme'
export * from './ui-time'
export * from './ui-toast'
1 change: 1 addition & 0 deletions packages/core/src/lib/ui-tab-routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ui-tab-routes'
54 changes: 54 additions & 0 deletions packages/core/src/lib/ui-tab-routes/ui-tab-routes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Box, Tabs, TabsProps, Text } from '@mantine/core'
import { ReactElement, ReactNode } from 'react'
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'

export interface UiTabRoute {
component: ReactNode
label: ReactElement | string
value: string
}

export function UiTabRoutes({
grow = false,
tabs,
baseUrl,
...props
}: Omit<TabsProps, 'children'> & {
children?: ReactNode
baseUrl?: string
grow?: boolean
tabs: UiTabRoute[]
}) {
const navigate = useNavigate()
const location = useLocation()
// Set the active tab based on matching the location pathname with the tab value
const activeTab = tabs.find((tab) => location.pathname.includes(`/${tab.value}`))?.value
// Set default redirect route to the first tab
const redirect = tabs.length && tabs[0].value !== '' ? tabs[0].value : undefined

return (
<Box>
<Tabs
value={activeTab}
onChange={(value) => navigate(`${baseUrl ? `${baseUrl}/${value}` : value}`)}
mb="md"
{...props}
>
<Tabs.List grow={grow}>
{tabs.map((tab) => (
<Tabs.Tab key={tab.value} value={tab.value}>
<Text>{tab.label}</Text>
</Tabs.Tab>
))}
</Tabs.List>
</Tabs>
<Routes>
{redirect ? <Route index element={<Navigate replace to={`./${redirect}`} />} /> : null}
{tabs.map((tab) => (
<Route key={tab.value} path={`${tab.value}/*`} element={tab.component} />
))}
<Route path="*" element={<Navigate replace to={`./${redirect}`} />} />
</Routes>
</Box>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,140 @@ exports[`component generator should create files for stack 1`] = `
}
`;

exports[`component generator should create files for tab-routes 1`] = `
{
".prettierrc": {
"content": [
"{ "singleQuote": true }",
],
"isBinary": false,
"path": "./.prettierrc",
},
"nx.json": {
"content": [
"{",
""affected": { "defaultBase": "main" },",
""targetDefaults": {",
""build": { "cache": true },",
""lint": { "cache": true },",
""e2e": { "cache": true }",
"}",
"}",
],
"isBinary": false,
"path": "./nx.json",
},
"package.json": {
"content": [
"{",
""name": "@proj/source",",
""dependencies": {},",
""devDependencies": {}",
"}",
],
"isBinary": false,
"path": "./package.json",
},
"test": {
"children": {
"index.ts": {
"content": [
"export * from './ui-tab-routes';",
],
"isBinary": false,
"path": "./test/index.ts",
},
"ui-tab-routes.tsx": {
"content": [
"import { Box, Tabs, TabsProps, Text } from '@mantine/core';",
"import { ReactElement, ReactNode } from 'react';",
"import {",
"Navigate,",
"Route,",
"Routes,",
"useLocation,",
"useNavigate,",
"} from 'react-router-dom';",
"export interface UiTabRoute {",
"component: ReactNode;",
"label: ReactElement | string;",
"value: string;",
"}",
"export function UiTabRoutes({",
"grow = false,",
"tabs,",
"baseUrl,",
"...props",
"}: Omit<TabsProps, 'children'> & {",
"children?: ReactNode;",
"baseUrl?: string;",
"grow?: boolean;",
"tabs: UiTabRoute[];",
"}) {",
"const navigate = useNavigate();",
"const location = useLocation();",
"// Set the active tab based on matching the location pathname with the tab value",
"const activeTab = tabs.find((tab) =>",
"location.pathname.includes(\`/\${tab.value}\`)",
")?.value;",
"// Set default redirect route to the first tab",
"const redirect =",
"tabs.length && tabs[0].value !== '' ? tabs[0].value : undefined;",
"return (",
"<Box>",
"<Tabs",
"value={activeTab}",
"onChange={(value) =>",
"navigate(\`\${baseUrl ? \`\${baseUrl}/\${value}\` : value}\`)",
"}",
"mb="md"",
"{...props}",
">",
"<Tabs.List grow={grow}>",
"{tabs.map((tab) => (",
"<Tabs.Tab key={tab.value} value={tab.value}>",
"<Text>{tab.label}</Text>",
"</Tabs.Tab>",
"))}",
"</Tabs.List>",
"</Tabs>",
"<Routes>",
"{redirect ? (",
"<Route index element={<Navigate replace to={\`./\${redirect}\`} />} />",
") : null}",
"{tabs.map((tab) => (",
"<Route",
"key={tab.value}",
"path={\`\${tab.value}/*\`}",
"element={tab.component}",
"/>",
"))}",
"<Route path="*" element={<Navigate replace to={\`./\${redirect}\`} />} />",
"</Routes>",
"</Box>",
");",
"}",
],
"isBinary": false,
"path": "./test/ui-tab-routes.tsx",
},
},
"path": "./test",
},
"tsconfig.base.json": {
"content": [
"{",
""compilerOptions": {",
""paths": {}",
"}",
"}",
],
"isBinary": false,
"path": "./tsconfig.base.json",
},
}
`;

exports[`component generator should create files for theme 1`] = `
{
".prettierrc": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ComponentGeneratorSchema {
| 'group'
| 'search-input'
| 'stack'
| 'tab-routes'
| 'theme'
| 'time'
| 'toast'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"group",
"search-input",
"stack",
"tab-routes",
"theme",
"time",
"toast"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Box, Tabs, TabsProps, Text } from '@mantine/core'
import { ReactElement, ReactNode } from 'react'
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'

export interface <%= prefix.className %>TabRoute {
component: ReactNode
label: ReactElement | string
value: string
}

export function <%= prefix.className %>TabRoutes({
grow = false,
tabs,
baseUrl,
...props
}: Omit<TabsProps, 'children'> & {
children?: ReactNode
baseUrl?: string
grow?: boolean
tabs: <%= prefix.className %>TabRoute[]
}) {
const navigate = useNavigate()
const location = useLocation()
// Set the active tab based on matching the location pathname with the tab value
const activeTab = tabs.find((tab) => location.pathname.includes(`/${tab.value}`))?.value
// Set default redirect route to the first tab
const redirect = tabs.length && tabs[0].value !== '' ? tabs[0].value : undefined

return (
<Box>
<Tabs
value={activeTab}
onChange={(value) => navigate(`${baseUrl ? `${baseUrl}/${value}` : value}`)}
mb="md"
{...props}
>
<Tabs.List grow={grow}>
{tabs.map((tab) => (
<Tabs.Tab key={tab.value} value={tab.value}>
<Text>{tab.label}</Text>
</Tabs.Tab>
))}
</Tabs.List>
</Tabs>
<Routes>
{redirect ? <Route index element={<Navigate replace to={`./${redirect}`} />} /> : null}
{tabs.map((tab) => (
<Route key={tab.value} path={`${tab.value}/*`} element={tab.component} />
))}
<Route path="*" element={<Navigate replace to={`./${redirect}`} />} />
</Routes>
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './<%= prefixFileName %>-tab-routes'
Loading

0 comments on commit da9cdf4

Please sign in to comment.