@logickernel/frame
v0.8.2
Published
Shared navigation and layout components for microfrontends
Downloads
904
Maintainers
Readme
@logickernel/frame
Shared navigation and layout components for microfrontends and core applications.
Overview
This library provides reusable navigation and layout components that can be shared across multiple microfrontends. The library supports both props-based and API-based navigation configuration with role-based filtering.
Features
- Shared Layout Components: AppSidebar, NavMain, NavUser, TeamSwitcher
- Dual Navigation Modes:
- Props-based: Pass navigation items as props
- API-based: Fetch navigation from API with role-based filtering
- Role-Based Filtering: Server-side filtering of navigation items based on user roles
- Type-Safe: Full TypeScript support with exported types
- Framework Agnostic: Works with any React framework (Next.js, Remix, etc.)
Installation
npm install @logickernel/frame
# or
yarn add @logickernel/frame
# or
pnpm add @logickernel/framePeer Dependencies
This library requires the following peer dependencies to be installed in your project:
npm install react react-dom lucide-react
npm install @radix-ui/react-avatar @radix-ui/react-collapsible @radix-ui/react-dropdown-menu @radix-ui/react-slot @radix-ui/react-tooltipFor Next.js users, you'll also need:
npm install next next-authUI Components Requirement
This library uses shadcn/ui components that must be available in your application. You need to have the following components installed and accessible via the @/components/ui/* path alias:
@/components/ui/avatar@/components/ui/dropdown-menu@/components/ui/collapsible
Important: Do NOT install the sidebar component from shadcn/ui. The library provides all sidebar components (SidebarProvider, Sidebar, SidebarTrigger, SidebarInset, etc.) that share the same React Context. Import them from @logickernel/frame instead.
To install the required components, use shadcn/ui:
npx shadcn@latest add avatar dropdown-menu collapsibleMake sure your tsconfig.json has the path alias configured:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}Usage
Framework Adapter
The library is framework-agnostic and requires a framework adapter to be provided. For Next.js applications, use the provided adapter hook:
"use client" // Required: adapter hook must be used in a Client Component
import { AppLayout, useNextJSAdapter } from "@logickernel/frame"
import type { User, SidebarData } from "@logickernel/frame"
export default function Layout({ children }: { children: React.ReactNode }) {
// Create the adapter using the hook (must be called in a Client Component)
const adapter = useNextJSAdapter()
return (
<AppLayout adapter={adapter} user={user} data={sidebarData}>
{children}
</AppLayout>
)
}Important:
- The
adapterprop is now required (previously optional) useNextJSAdapter()must be called in a Client Component (add"use client"directive)- For other frameworks, you'll need to implement the
FrameworkAdapterinterface (see Types section)
Props Mode vs API Mode
The component automatically detects which mode to use:
- Props Mode: Provide
data→ organizations and navigation come from props - API Mode: Omit
data→ organizations and navigation come from the API
Props Mode
Pass a data object containing organizations and navigation items. The {organizationId} placeholder in URLs is automatically replaced.
"use client" // Required: adapter hook must be used in a Client Component
import { AppLayout, useNextJSAdapter } from "@logickernel/frame"
import { Home, Users, GalleryVerticalEnd } from "lucide-react"
import type { SidebarData, User } from "@logickernel/frame"
const user: User = {
name: "John Doe",
email: "[email protected]",
image: null,
}
const data: SidebarData = {
organizations: [
{ id: "org-1", name: "Acme Corp", plan: "Pro" },
{ id: "org-2", name: "Another Org", plan: "Free" },
],
navigationItems: [
{ title: "Organization", icon: GalleryVerticalEnd },
{ title: "Home", url: "/app/{organizationId}/home", icon: Home },
{ title: "Members", url: "/app/{organizationId}/members", icon: Users },
],
}
export default function Layout({ children }: { children: React.ReactNode }) {
const adapter = useNextJSAdapter()
return (
<AppLayout adapter={adapter} user={user} data={data}>
{children}
</AppLayout>
)
}Using SidebarProvider and Sidebar Components
Important: Always import sidebar components (SidebarProvider, SidebarTrigger, SidebarInset, etc.) from @logickernel/frame, not from your local @/components/ui/sidebar. This ensures all components share the same React Context.
"use client" // Required: adapter hook must be used in a Client Component
import {
AppLayout,
useNextJSAdapter,
} from "@logickernel/frame"
import type { User, SidebarData } from "@logickernel/frame"
const user: User = { name: "John Doe", email: "[email protected]", image: null }
const data: SidebarData = { /* ... */ }
export default function Layout({ children }: { children: React.ReactNode }) {
const adapter = useNextJSAdapter()
return (
<AppLayout adapter={adapter} user={user} data={data}>
{children}
</AppLayout>
)
}Available Sidebar Components:
SidebarProvider- Context provider (required wrapper)SidebarTrigger- Button to toggle sidebarSidebarInset- Main content area that adjusts for sidebaruseSidebar- Hook for accessing sidebar state (optional, for advanced use cases)
Note: Other sidebar components (Sidebar, SidebarRail, SidebarMenu, etc.) are internal implementation details used by AppSidebar and are not exported. You don't need to import them.
API Mode
Omit data — the component fetches organizations and navigation from the API.
"use client" // Required: adapter hook must be used in a Client Component
import { AppLayout, useNextJSAdapter } from "@logickernel/frame"
export default function Layout({ children }: { children: React.ReactNode }) {
const adapter = useNextJSAdapter()
return (
<>
<AppSidebar
user={user}
adapter={adapter}
// apiBaseUrl="/api" // Optional, defaults to "/api"
/>
<main>{children}</main>
</>
)
}The API endpoint (/api/navigation/[organization_id]) should:
- Authenticate the user via shared cookies
- Fetch user roles in the organization
- Filter navigation items based on role requirements
- Return filtered navigation items and optionally organizations
Expected API Response:
{
"items": [
{
"title": "Organization",
"icon": "GalleryVerticalEnd"
},
{
"title": "Home",
"url": "/app/{organizationId}/home",
"icon": "Home"
}
],
"organizationId": "org-1",
"organizations": [
{
"id": "org-1",
"name": "Acme Corp",
"plan": "Pro"
},
{
"id": "org-2",
"name": "Another Org",
"plan": "Free"
}
]
}Note: In API mode, the organizations field is required in the API response. The API determines which organizations the user belongs to. The logo field in organizations is optional - if omitted, a default icon will be used.
Components
AppSidebar
Main sidebar component that combines TeamSwitcher, NavMain, and NavUser.
Props:
user: User- User information (name, email, image)adapter: FrameworkAdapter- Framework adapter (usecreateNextJSAdapter()for Next.js)data?: SidebarData- Sidebar data with organizations and navigation items (props mode)organizationId?: string- Current organization ID (optional, extracted from pathname)apiBaseUrl?: string- Custom API base URL (API mode only, defaults to "/api")- All other props from
Sidebarcomponent
Mode Detection:
- If
datais provided → Props Mode - If
datais omitted → API Mode
NavMain
Renders navigation menu items with support for groups and nested items.
Props:
items: NavigationItem[]- Array of navigation itemsadapter: FrameworkAdapter- Framework adapter
NavUser
User profile dropdown with sign-out functionality.
Props:
user: User- User informationadapter: FrameworkAdapter- Framework adapter
TeamSwitcher
Organization switcher dropdown.
Props:
teams: Organization[]- List of organizationsorganizationId?: string- Current organization IDadapter: FrameworkAdapter- Framework adapter
Hooks
useNavigation
Hook to fetch navigation items and organizations from API.
import { useNavigation } from "@logickernel/frame"
const { items, organizations, loading, error } = useNavigation({
organizationId: "org-1",
apiBaseUrl: "/api",
enabled: true,
})Options:
organizationId?: string- Organization ID to fetch navigation forapiBaseUrl?: string- API base URL (default: "/api")enabled?: boolean- Enable/disable fetching (default: true). Useful for conditional fetching, e.g.,enabled: !!userto only fetch when a user is logged in
Returns:
items: NavigationItem[]- Navigation itemsorganizations: Organization[]- Organizations (if provided by API)loading: boolean- Loading stateerror: Error | null- Error state
Types
NavigationItem
interface NavigationItem {
title: string
url?: string
icon?: string | LucideIcon
isActive?: boolean
items?: {
title: string
url: string
}[]
}User
interface User {
name: string | null
email: string
image: string | null
}Organization
interface Organization {
id: string
name: string
logo?: LucideIcon | React.ComponentType<{ className?: string }>
plan?: string
}SidebarData
interface SidebarData {
organizations: Organization[]
navigationItems: NavigationItem[]
}NavigationConfig
interface NavigationConfig {
items: NavigationItem[]
organizationId?: string
}FrameworkAdapter
interface FrameworkAdapter {
usePathname: () => string
useRouter: () => Router
Link: ComponentType<LinkProps>
signOut?: (options?: { redirect?: boolean }) => Promise<void>
}Router
interface Router {
push: (path: string) => void
refresh?: () => void
}LinkProps
interface LinkProps {
href: string
children: ReactNode
className?: string
}Utilities
getIconComponent
Utility to convert icon name strings to Lucide React icon components.
import { getIconComponent } from "@logickernel/frame"
const Icon = getIconComponent("Home") // Returns Home icon componentAdapters
createNextJSAdapter
Creates a Next.js adapter for the framework. Use this when using the library in a Next.js application.
import { createNextJSAdapter } from "@logickernel/frame"
const adapter = createNextJSAdapter()Note: This requires next and next-auth to be installed.
Custom Adapters
For other frameworks, implement the FrameworkAdapter interface:
import type { FrameworkAdapter } from "@logickernel/frame"
const myAdapter: FrameworkAdapter = {
usePathname: () => {
// Return current pathname
return window.location.pathname
},
useRouter: () => ({
push: (path: string) => {
// Navigate to path
window.history.pushState({}, "", path)
},
refresh: () => {
// Refresh the page
window.location.reload()
},
}),
Link: ({ href, children, className }) => (
<a href={href} className={className}>
{children}
</a>
),
signOut: async () => {
// Handle sign out
},
}Role-Based Filtering
Navigation items can specify requiredRoles in the service layer configuration. Items are filtered server-side based on the user's roles in the organization.
Example API-side configuration:
{
title: "Settings",
url: "/app/{organizationId}/settings",
icon: "Settings",
requiredRoles: ["organization.owner", "organization.editor"]
}Cross-Origin Setup (Microfrontends)
If your microfrontends are on different domains, configure CORS in your navigation API:
// In your navigation API route
const response = NextResponse.json(data)
response.headers.set("Access-Control-Allow-Origin", "https://microfrontend.example.com")
response.headers.set("Access-Control-Allow-Credentials", "true")
return responseFor cross-origin requests, use the full API URL:
<AppSidebar
user={user}
organizationId={orgId}
apiBaseUrl="https://kernel.example.com/api"
/>Development
# Build the library
npm run build
# Watch mode for development
npm run dev
# Type check
npm run type-checkLicense
MIT
