Skip to content

Commit

Permalink
feat(ui): create kbar component and provider
Browse files Browse the repository at this point in the history
  • Loading branch information
mateusfg7 committed Jul 13, 2023
1 parent 8e2da95 commit 6414509
Show file tree
Hide file tree
Showing 2 changed files with 335 additions and 0 deletions.
120 changes: 120 additions & 0 deletions src/shared/components/kbar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { ReactNode, forwardRef } from 'react'
import {
ActionImpl,
KBarAnimator,
KBarPortal,
KBarPositioner,
KBarResults,
KBarSearch,
useMatches
} from 'kbar'
import { ArrowUpRight } from '@/shared/lib/phosphor-icons'

const Content = ({ children }: { children: ReactNode }) => {
const { results } = useMatches()
return (
<div
className={`w-[97vw] rounded-2xl bg-white p-6 shadow-lg outline-none dark:bg-neutral-1000 md:w-[45vw] ${
results.length > 0 && 'space-y-4'
}`}
>
{children}
</div>
)
}

const Search = () => (
<div className="border-b border-neutral-500/20 px-3 pb-4 text-xl md:text-2xl">
<KBarSearch className="w-full border-none bg-transparent outline-none placeholder:text-neutral-500/50" />
</div>
)

const ResultItem = forwardRef<
HTMLDivElement,
{
action: ActionImpl
active: boolean
}
>(({ action, active }, ref) => {
return (
<div
ref={ref}
className={`m-0 flex cursor-pointer items-center justify-between gap-5 rounded-xl p-2 text-lg transition-colors ${
active
? 'bg-neutral-500/10 text-neutral-800 dark:bg-neutral-700/10 dark:text-neutral-400'
: 'text-neutral-500'
}`}
>
<div className="flex items-center gap-3">
{action.icon && <span className="text-xl">{action.icon}</span>}
<div className="flex flex-col items-start justify-center">
<div className="flex items-end gap-px">
<span className="leading-none">{action.name}</span>
{action.id.startsWith('out') && (
<ArrowUpRight className="text-xs" />
)}
</div>
{action.subtitle && (
<span className="text-xs text-neutral-400 dark:text-neutral-600">
{action.subtitle}
</span>
)}
</div>
</div>
{action.shortcut && (
<div className="flex gap-1">
{action.shortcut.map(shortcut => (
<kbd key={shortcut} className="keyboard opacity-50">
{shortcut}
</kbd>
))}
</div>
)}
</div>
)
})
ResultItem.displayName = 'ResultItem'

const SectionTitle = forwardRef<HTMLDivElement, { title: string }>(
({ title }, ref) => (
<div
ref={ref}
className="p-3 text-sm text-neutral-400 dark:text-neutral-600"
>
{title}
</div>
)
)
SectionTitle.displayName = 'SectionTitle'

function RenderResults() {
const { results } = useMatches()

return (
<KBarResults
items={results}
onRender={({ item, active }) =>
typeof item === 'string' ? (
<SectionTitle title={item} />
) : (
<ResultItem action={item} active={active} />
)
}
/>
)
}

export function KBar() {
return (
<KBarPortal>
<KBarPositioner className="z-50 bg-black/20 backdrop-blur-[1px] dark:bg-black/30">
<KBarAnimator>
<Content>
<Search />
<RenderResults />
</Content>
</KBarAnimator>
</KBarPositioner>
</KBarPortal>
)
}
215 changes: 215 additions & 0 deletions src/shared/components/kbar/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { ReactNode } from 'react'
import { useRouter } from 'next/navigation'
import { KBarProvider, Action } from 'kbar'
import { useTheme } from 'next-themes'
import { allPosts } from 'contentlayer/generated'
import {
Article,
Briefcase,
Code,
Desktop,
File,
FileDashed,
FolderOpen,
GithubLogo,
House,
MagnifyingGlass,
Moon,
Note,
Palette,
PencilSimpleLine,
Rss,
Sun,
Tag,
TreeStructure,
ChartLine
} from '@/shared/lib/phosphor-icons'
import { getSortedPosts } from '@/shared/lib/get-sorted-posts'
import { KBar } from '@/shared/components/kbar'

export function CustomKBarProvider({ children }: { children: ReactNode }) {
const { push } = useRouter()
const { setTheme } = useTheme()

// TODO: List projects on command bar (https://github.com/mateusfg7/mateusf.com/issues/572)

const navigationActions: Action[] = [
{
id: 'home',
name: 'Home',
shortcut: ['n', 'h'],
keywords: 'homepage main',
icon: <House weight="duotone" />,
perform: () => push('/')
},
{
id: 'portifolio',
name: 'Portfolio',
shortcut: ['n', 'p'],
keywords: 'portfolio projects',
icon: <Briefcase weight="duotone" />,
perform: () => push('/portifolio')
}
]

const getIconByStatus = (status: 'published' | 'draft' | 'planned') => {
if (status === 'published') return <Article weight="duotone" />
if (status === 'draft') return <FileDashed weight="duotone" />
if (status === 'planned') return <PencilSimpleLine weight="duotone" />
}
const postsAsAction: Action[] = getSortedPosts(
allPosts.filter(post => post.status !== 'planned')
).map(({ id, title, status, test, tags, description }) => ({
id,
name: title,
icon: test ? <Code weight="duotone" /> : getIconByStatus(status),
keywords: tags
.split(',')
.map(tag => tag.trim())
.toString()
.replaceAll(',', ' '),
parent: 'search-posts',
subtitle: description,
perform: () => push(`/post/${id}`)
}))

const blogActions: Action[] = [
{
id: 'blog',
name: 'Blog',
shortcut: ['b'],
section: 'Blog',
keywords: 'posts writing',
icon: <Note weight="duotone" />,
perform: () => push('/')
},
{
id: 'categories',
name: 'Categories',
shortcut: ['b', 'c'],
section: 'Blog',
keywords: 'posts writing',
icon: <FolderOpen weight="duotone" />,
perform: () => push('/categories')
},
{
id: 'tags',
name: 'Tags',
shortcut: ['b', 't'],
section: 'Blog',
keywords: 'posts writing',
icon: <Tag weight="duotone" />,
perform: () => push('/tag')
},
{
id: 'rss',
name: 'Rss',
section: 'Blog',
keywords: 'feed rss atom',
icon: <Rss weight="duotone" />,
perform: () => push('/rss')
},
{
id: 'search-posts',
name: 'Search posts...',
section: 'Blog',
keywords: 'search posts write writing blog',
shortcut: ['b', 's'],
icon: <MagnifyingGlass weight="duotone" />
},
...postsAsAction
]

const websiteInformationActions: Action[] = [
{
id: 'out-repo',
name: 'Source code',
section: 'Website',
keywords: 'repo source github código fonte',
icon: <GithubLogo weight="duotone" />,
perform: () =>
window.open('https://github.com/mateusfg7/mateusf.com', '_blank')
},
{
id: 'out-license',
name: 'License',
section: 'Website',
keywords: 'mit gpl',
icon: <File weight="duotone" />,
perform: () =>
window.open(
'https://github.com/mateusfg7/mateusf.com/blob/main/LICENSE',
'_blank'
)
},
{
id: 'out-analytics',
name: 'Analytics',
section: 'Website',
keywords: 'stats graph traffic',
icon: <ChartLine weight="duotone" />,
perform: () =>
window.open(
'https://analytics.umami.is/share/IV950FFonuZg4Rbn/mateusf.com',
'_blank'
)
},
{
id: 'sitemap',
name: 'Sitemap',
section: 'Website',
keywords: 'map links crawler',
icon: <TreeStructure weight="duotone" />,
perform: () => push('/sitemap.xml')
}
]

const themeActions: Action[] = [
{
id: 'set-theme',
name: 'Change theme',
icon: <Palette weight="duotone" />,
keywords: 'theme dark light',
shortcut: ['c', 't'],
section: 'Configurations'
},
{
id: 'system-theme',
name: 'System defined',
icon: <Desktop weight="duotone" />,
parent: 'set-theme',
keywords: 'theme dark light',
perform: () => setTheme('system')
},
{
id: 'dark-theme',
name: 'Dark mode',
icon: <Moon weight="duotone" />,
parent: 'set-theme',
keywords: 'theme dark light',
perform: () => setTheme('dark')
},
{
id: 'light-theme',
name: 'Light mode',
icon: <Sun weight="duotone" />,
parent: 'set-theme',
keywords: 'theme dark light',
perform: () => setTheme('light')
}
]

const actions: Action[] = [
...navigationActions,
...blogActions,
...websiteInformationActions,
...themeActions
]

return (
<KBarProvider actions={actions}>
<KBar />
{children}
</KBarProvider>
)
}

0 comments on commit 6414509

Please sign in to comment.