@voltro/ui-shadcn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +57 -0
  3. package/README.md +26 -0
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +3016 -0
  6. package/dist/brand.d.ts +73 -0
  7. package/dist/brand.js +177 -0
  8. package/dist/cn.d.ts +11 -0
  9. package/dist/cn.js +6 -0
  10. package/dist/index.d.ts +1183 -0
  11. package/dist/index.js +2636 -0
  12. package/dist/tokens.css +532 -0
  13. package/package.json +64 -0
  14. package/src/brand/README.md +60 -0
  15. package/src/brand/assets/voltro-favicon.svg +30 -0
  16. package/src/brand/assets/voltro-icon-dark.svg +26 -0
  17. package/src/brand/assets/voltro-icon.svg +14 -0
  18. package/src/brand/assets/voltro-mark-mono.svg +5 -0
  19. package/src/brand/assets/voltro-mark.svg +14 -0
  20. package/src/brand/voltroLogo.tsx +244 -0
  21. package/src/cn.ts +10 -0
  22. package/src/compositions/appShell.tsx +72 -0
  23. package/src/compositions/codeCompare.tsx +88 -0
  24. package/src/compositions/docShell.tsx +112 -0
  25. package/src/compositions/docsLayout.tsx +577 -0
  26. package/src/compositions/featureBento.tsx +103 -0
  27. package/src/compositions/featureGrid.tsx +41 -0
  28. package/src/compositions/heroSection.tsx +55 -0
  29. package/src/compositions/landingCta.tsx +85 -0
  30. package/src/compositions/landingHero.tsx +174 -0
  31. package/src/compositions/landingStats.tsx +99 -0
  32. package/src/compositions/loginCard.tsx +139 -0
  33. package/src/compositions/pageHeader.tsx +58 -0
  34. package/src/compositions/profileMenu.tsx +316 -0
  35. package/src/compositions/siteFooter.tsx +250 -0
  36. package/src/compositions/themeToggle.tsx +82 -0
  37. package/src/cookies.ts +109 -0
  38. package/src/index.ts +160 -0
  39. package/src/primitives/animatedNumber.tsx +73 -0
  40. package/src/primitives/avatar.tsx +39 -0
  41. package/src/primitives/badge.tsx +39 -0
  42. package/src/primitives/button.tsx +53 -0
  43. package/src/primitives/callout.tsx +97 -0
  44. package/src/primitives/card.tsx +68 -0
  45. package/src/primitives/checkbox.tsx +55 -0
  46. package/src/primitives/codeBlock.tsx +134 -0
  47. package/src/primitives/codeWindow.tsx +84 -0
  48. package/src/primitives/dialog.tsx +43 -0
  49. package/src/primitives/docCard.tsx +109 -0
  50. package/src/primitives/docIcons.tsx +268 -0
  51. package/src/primitives/dropdownMenu.tsx +162 -0
  52. package/src/primitives/gridOverlay.tsx +51 -0
  53. package/src/primitives/highlightedCode.tsx +112 -0
  54. package/src/primitives/input.tsx +25 -0
  55. package/src/primitives/label.tsx +19 -0
  56. package/src/primitives/localeSwitcher.tsx +90 -0
  57. package/src/primitives/meshBackdrop.tsx +62 -0
  58. package/src/primitives/scrollReveal.tsx +70 -0
  59. package/src/primitives/searchModal.tsx +304 -0
  60. package/src/primitives/select.tsx +24 -0
  61. package/src/primitives/separator.tsx +24 -0
  62. package/src/primitives/skeleton.tsx +12 -0
  63. package/src/primitives/sparkles.tsx +105 -0
  64. package/src/primitives/steps.tsx +55 -0
  65. package/src/primitives/tabs.tsx +102 -0
  66. package/src/primitives/textarea.tsx +24 -0
  67. package/src/primitives/toast.tsx +44 -0
  68. package/src/primitives/tocScrollSpy.tsx +110 -0
  69. package/src/primitives/toggle.tsx +50 -0
  70. package/src/primitives/toggleGroup.tsx +62 -0
  71. package/src/tokens.css +532 -0
  72. package/src/widgets.tsx +297 -0
@@ -0,0 +1,55 @@
1
+ // shadcn-style Checkbox — native `<input type=checkbox>` (no Radix dep)
2
+ // with indeterminate support. Drives the check / dash glyphs purely via
3
+ // CSS peer variants so it can't desync from the input's real state.
4
+
5
+ import { useEffect, useRef, type InputHTMLAttributes, type ReactNode } from 'react'
6
+ import { cn } from '../cn'
7
+
8
+ export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
9
+ /** Tri-state middle: renders a dash. Wired to the DOM `indeterminate`
10
+ * property (not an attribute) via a ref effect. */
11
+ readonly indeterminate?: boolean
12
+ }
13
+
14
+ export const Checkbox = ({ className, indeterminate, ...props }: CheckboxProps): ReactNode => {
15
+ const ref = useRef<HTMLInputElement>(null)
16
+ useEffect(() => {
17
+ if (ref.current) ref.current.indeterminate = indeterminate === true
18
+ }, [indeterminate])
19
+
20
+ return (
21
+ <span className="relative inline-flex size-4 shrink-0 items-center justify-center">
22
+ <input
23
+ ref={ref}
24
+ type="checkbox"
25
+ data-slot="checkbox"
26
+ className={cn(
27
+ 'peer size-4 appearance-none rounded-[4px] border shadow-xs outline-none',
28
+ 'border-input bg-transparent dark:bg-input/30',
29
+ 'checked:bg-primary checked:border-primary',
30
+ 'indeterminate:bg-primary indeterminate:border-primary',
31
+ 'transition-[color,box-shadow]',
32
+ 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
33
+ 'disabled:cursor-not-allowed disabled:opacity-50',
34
+ 'cursor-pointer',
35
+ className,
36
+ )}
37
+ {...props}
38
+ />
39
+ {/* checkmark — visible only when checked (and not indeterminate) */}
40
+ <svg
41
+ viewBox="0 0 24 24"
42
+ fill="none"
43
+ stroke="currentColor"
44
+ strokeWidth={3}
45
+ strokeLinecap="round"
46
+ strokeLinejoin="round"
47
+ className="pointer-events-none absolute hidden size-3 text-primary-foreground peer-checked:peer-indeterminate:hidden peer-checked:block"
48
+ >
49
+ <path d="M20 6 9 17l-5-5" />
50
+ </svg>
51
+ {/* indeterminate dash */}
52
+ <span className="pointer-events-none absolute hidden h-[2px] w-2.5 rounded-full bg-primary-foreground peer-indeterminate:block" />
53
+ </span>
54
+ )
55
+ }
@@ -0,0 +1,134 @@
1
+ // CodeBlock — pretty wrapper for a `<pre><code>` fragment with an
2
+ // optional filename header, language label, and a one-click copy
3
+ // button. Three rendering modes, in priority order:
4
+ // 1. `html` set → render pre-highlighted Shiki HTML verbatim.
5
+ // (The docs app's MDX pipeline pre-renders code
6
+ // fences at build time and feeds them here.)
7
+ // 2. `code` + `language` matching SHIKI_LANGS → lazy-load Shiki and
8
+ // highlight at runtime via <HighlightedCode>.
9
+ // Until the WASM lands, the body falls back to
10
+ // plain escaped code so layout doesn't jump.
11
+ // 3. `code` only → plain <pre><code>, no highlighting.
12
+ //
13
+ // Browser-only behaviour (copy button) is opt-in: when `interactive:
14
+ // 'islands'` strips the rest of the JS, the button stays a static
15
+ // <button> that doesn't do anything until JS lands; visually still
16
+ // looks right, just non-functional. That's the same compromise the
17
+ // rest of the kit makes.
18
+ //
19
+ // For Fumadocs-style tabs around a CodeBlock, use the `Tabs` primitive
20
+ // in this folder.
21
+
22
+ import { useState, type ReactNode } from 'react'
23
+ import { cn } from '../cn'
24
+ import { HighlightedCode, SHIKI_LANGS, type ShikiLang } from './highlightedCode'
25
+
26
+ const SHIKI_LANG_SET = SHIKI_LANGS as ReadonlyArray<string>
27
+ const asShikiLang = (s: string | undefined): ShikiLang | null =>
28
+ s && SHIKI_LANG_SET.includes(s) ? (s as ShikiLang) : null
29
+
30
+ interface CodeBlockProps {
31
+ /** Filename label shown in the header. Optional. */
32
+ readonly filename?: string
33
+ /** Language label shown in the top-right. Optional. */
34
+ readonly language?: string
35
+ /** Pre-highlighted HTML (e.g. from Shiki) — rendered via
36
+ * dangerouslySetInnerHTML. Pick this OR `code`, not both. */
37
+ readonly html?: string
38
+ /** Plain code text — renders inside a styled <pre><code>. */
39
+ readonly code?: string
40
+ readonly className?: string
41
+ /** Copy-button caption + `aria-label` before a copy. Default `'Copy'` /
42
+ * `'Copy code'`. */
43
+ readonly copyLabel?: string
44
+ readonly copyAriaLabel?: string
45
+ /** Copy-button caption + `aria-label` after a successful copy. Default
46
+ * `'Copied'`. */
47
+ readonly copiedLabel?: string
48
+ readonly copiedAriaLabel?: string
49
+ }
50
+
51
+ export const CodeBlock = ({
52
+ filename, language, html, code, className,
53
+ copyLabel = 'Copy', copyAriaLabel = 'Copy code',
54
+ copiedLabel = 'Copied', copiedAriaLabel = 'Copied',
55
+ }: CodeBlockProps): ReactNode => {
56
+ const [copied, setCopied] = useState(false)
57
+ // Text used by the copy button: prefer the raw `code`; when only
58
+ // pre-rendered HTML is available, fall back to stripping tags at
59
+ // copy time.
60
+ const copyText = (): string => {
61
+ if (code) return code
62
+ if (html) {
63
+ const tmp = typeof document !== 'undefined' ? document.createElement('div') : null
64
+ if (!tmp) return ''
65
+ tmp.innerHTML = html
66
+ return tmp.textContent ?? ''
67
+ }
68
+ return ''
69
+ }
70
+ const doCopy = (): void => {
71
+ const text = copyText()
72
+ if (!text || typeof navigator === 'undefined' || !navigator.clipboard) return
73
+ navigator.clipboard.writeText(text).then(() => {
74
+ setCopied(true)
75
+ setTimeout(() => setCopied(false), 1400)
76
+ }).catch(() => { /* clipboard denied; ignore */ })
77
+ }
78
+
79
+ return (
80
+ <figure className={cn('not-prose my-5 rounded-lg overflow-hidden border border-border bg-[oklch(0.18_0.005_280)]', className)}>
81
+ {(filename || language) ? (
82
+ <header className="flex items-center justify-between border-b border-border/60 px-4 py-2 bg-card/40">
83
+ {filename ? (
84
+ <span className="text-xs text-muted-foreground font-mono">{filename}</span>
85
+ ) : <span />}
86
+ {language ? (
87
+ <span className="text-[0.65rem] uppercase tracking-wider text-muted-foreground/80 font-semibold">
88
+ {language}
89
+ </span>
90
+ ) : null}
91
+ </header>
92
+ ) : null}
93
+ <div className="relative">
94
+ <button
95
+ type="button"
96
+ onClick={doCopy}
97
+ aria-label={copied ? copiedAriaLabel : copyAriaLabel}
98
+ className="absolute top-3 right-3 inline-flex items-center gap-1 rounded-md border border-border/60 bg-background/70 backdrop-blur px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
99
+ >
100
+ {copied ? (
101
+ <>
102
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
103
+ <path d="M3 6.5 L5 8.5 L9 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
104
+ </svg>
105
+ {copiedLabel}
106
+ </>
107
+ ) : (
108
+ <>
109
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
110
+ <rect x="3" y="3" width="6" height="6" rx="1" stroke="currentColor" strokeWidth="1.5" />
111
+ <path d="M5 3 V2.5 A0.5 0.5 0 0 1 5.5 2 H8.5 A0.5 0.5 0 0 1 9 2.5 V5.5 A0.5 0.5 0 0 1 8.5 6 H8" stroke="currentColor" strokeWidth="1.5" />
112
+ </svg>
113
+ {copyLabel}
114
+ </>
115
+ )}
116
+ </button>
117
+ {html ? (
118
+ <div
119
+ className="text-sm leading-relaxed [&_pre]:!m-0 [&_pre]:!bg-transparent [&_pre]:p-4 overflow-x-auto"
120
+ dangerouslySetInnerHTML={{ __html: html }}
121
+ />
122
+ ) : code && asShikiLang(language) ? (
123
+ <HighlightedCode
124
+ code={code}
125
+ lang={asShikiLang(language)!}
126
+ className="[&_pre]:!p-4 [&_pre]:!m-0 [&_pre]:!bg-transparent"
127
+ />
128
+ ) : (
129
+ <pre className="p-4 text-sm leading-relaxed overflow-x-auto"><code>{code}</code></pre>
130
+ )}
131
+ </div>
132
+ </figure>
133
+ )
134
+ }
@@ -0,0 +1,84 @@
1
+ // CodeWindow — chrome'd code preview for the landing hero / feature
2
+ // callouts. Looks like a terminal/editor surface without claiming a
3
+ // specific platform (no fake macOS dots).
4
+ //
5
+ // Two states:
6
+ // - Pass `code` (string) + `lang` for a plain (no-syntax) preview
7
+ // - Pass `html` for pre-highlighted (e.g. Shiki) output
8
+ //
9
+ // A subtle blinking caret is appended at the end of the last line
10
+ // when `caret` is true — adds the "live, just typed" feel without
11
+ // real animation cost.
12
+
13
+ import type { ReactNode } from 'react'
14
+ import { cn } from '../cn'
15
+ import { HighlightedCode, type ShikiLang } from './highlightedCode'
16
+
17
+ interface CodeWindowProps {
18
+ /** Title shown in the chrome — usually a filename. */
19
+ readonly title?: string
20
+ /** Tag in the corner — language label, e.g. "TypeScript". */
21
+ readonly tag?: string
22
+ /** Plain code text. When `lang` is also provided, the component
23
+ * runs Shiki syntax highlighting automatically. */
24
+ readonly code?: string
25
+ /** Shiki language. Triggers syntax highlighting on `code`. */
26
+ readonly lang?: ShikiLang
27
+ /** Pre-highlighted HTML (skips Shiki). */
28
+ readonly html?: string
29
+ /** Show a blinking caret after the code. Default false. Only
30
+ * applies to plain (non-highlighted) rendering. */
31
+ readonly caret?: boolean
32
+ /** Outer wrapper class — useful for sizing. */
33
+ readonly className?: string
34
+ }
35
+
36
+ export const CodeWindow = ({
37
+ title, tag, code, lang, html, caret = false, className,
38
+ }: CodeWindowProps): ReactNode => (
39
+ <div
40
+ className={cn(
41
+ 'relative overflow-hidden rounded-xl border border-border bg-card/60 backdrop-blur-sm shadow-2xl',
42
+ // Hairline highlight along the top — sells the "lit from above" feel
43
+ 'before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-gradient-to-r before:from-transparent before:via-primary/60 before:to-transparent',
44
+ className,
45
+ )}
46
+ >
47
+ {(title || tag) ? (
48
+ <div className="flex items-center justify-between px-4 py-2 border-b border-border/60 bg-background/40">
49
+ <div className="flex items-center gap-2 text-xs text-muted-foreground font-mono">
50
+ {/* Three soft indicator dots — not platform-specific colours */}
51
+ <span className="inline-flex gap-1.5" aria-hidden="true">
52
+ <span className="w-2.5 h-2.5 rounded-full bg-muted-foreground/30" />
53
+ <span className="w-2.5 h-2.5 rounded-full bg-muted-foreground/30" />
54
+ <span className="w-2.5 h-2.5 rounded-full bg-muted-foreground/30" />
55
+ </span>
56
+ {title ? <span className="ml-2">{title}</span> : null}
57
+ </div>
58
+ {tag ? (
59
+ <span className="text-[0.65rem] uppercase tracking-[0.08em] text-muted-foreground/80 font-semibold">
60
+ {tag}
61
+ </span>
62
+ ) : null}
63
+ </div>
64
+ ) : null}
65
+ <div className="relative">
66
+ {html ? (
67
+ <div
68
+ className="text-sm leading-relaxed [&_pre]:!m-0 [&_pre]:!bg-transparent [&_pre]:p-5 overflow-x-auto"
69
+ dangerouslySetInnerHTML={{ __html: html }}
70
+ />
71
+ ) : code && lang ? (
72
+ // `code + lang` → run Shiki transparently.
73
+ <HighlightedCode code={code} lang={lang} />
74
+ ) : (
75
+ <pre className="p-5 text-sm leading-relaxed overflow-x-auto font-mono">
76
+ <code>{code}</code>
77
+ {caret ? (
78
+ <span className="ml-0.5 inline-block w-[7px] h-[1.1em] -mb-[3px] align-text-bottom bg-primary motion-safe:animate-caret-blink" />
79
+ ) : null}
80
+ </pre>
81
+ )}
82
+ </div>
83
+ </div>
84
+ )
@@ -0,0 +1,43 @@
1
+ // shadcn Dialog — uses the native HTML <dialog> element. Honest V1:
2
+ // no Radix focus-trap, no portal magic. <dialog> handles modal stacking
3
+ // + ESC dismissal + backdrop click natively in modern browsers; we
4
+ // add the shadcn styling on top.
5
+ //
6
+ // Caller controls open state via the ref or via `open` prop. The
7
+ // imperative API (`dialogRef.current?.showModal()`) is the native
8
+ // path; we surface it as a uncontrolled wrapper for now.
9
+
10
+ import type { DialogHTMLAttributes, HTMLAttributes, ReactNode } from 'react'
11
+ import { cn } from '../cn'
12
+
13
+ export const Dialog = ({ className, children, ...props }: DialogHTMLAttributes<HTMLDialogElement>): ReactNode => (
14
+ <dialog
15
+ data-slot="dialog"
16
+ className={cn(
17
+ 'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2',
18
+ 'w-full max-w-lg rounded-lg border bg-background text-foreground p-6 shadow-lg',
19
+ 'backdrop:bg-black/50 backdrop:backdrop-blur-sm',
20
+ 'open:animate-in open:fade-in-0 open:zoom-in-95',
21
+ className,
22
+ )}
23
+ {...props}
24
+ >
25
+ {children}
26
+ </dialog>
27
+ )
28
+
29
+ export const DialogHeader = ({ className, ...props }: HTMLAttributes<HTMLDivElement>): ReactNode => (
30
+ <div data-slot="dialog-header" className={cn('flex flex-col gap-1.5 text-left mb-4', className)} {...props} />
31
+ )
32
+
33
+ export const DialogTitle = ({ className, ...props }: HTMLAttributes<HTMLHeadingElement>): ReactNode => (
34
+ <h2 data-slot="dialog-title" className={cn('text-lg font-semibold leading-none tracking-tight', className)} {...props} />
35
+ )
36
+
37
+ export const DialogDescription = ({ className, ...props }: HTMLAttributes<HTMLParagraphElement>): ReactNode => (
38
+ <p data-slot="dialog-description" className={cn('text-sm text-muted-foreground', className)} {...props} />
39
+ )
40
+
41
+ export const DialogFooter = ({ className, ...props }: HTMLAttributes<HTMLDivElement>): ReactNode => (
42
+ <div data-slot="dialog-footer" className={cn('mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)} {...props} />
43
+ )
@@ -0,0 +1,109 @@
1
+ // DocCard + DocCards — grid of clickable cards for docs landing pages
2
+ // (Fumadocs calls these `<Cards>` / `<Card>`). The wrapper handles the
3
+ // grid; each card is a Link with a hover indicator.
4
+ //
5
+ // Usage:
6
+ // <DocCards>
7
+ // <DocCard
8
+ // title="Getting started"
9
+ // description="Scaffold your first project and boot it locally."
10
+ // href="/docs/intro/getting-started"
11
+ // icon="🚀"
12
+ // />
13
+ // <DocCard … />
14
+ // </DocCards>
15
+
16
+ import type { ComponentType, ReactNode } from 'react'
17
+ import { cn } from '../cn'
18
+ import type { ShellLinkProps } from '../compositions/docShell'
19
+
20
+ interface DocCardsProps {
21
+ readonly children: ReactNode
22
+ readonly className?: string
23
+ /** Override the grid columns. Default: 1 on mobile, 2 on md+. */
24
+ readonly cols?: 1 | 2 | 3
25
+ /** Stagger the first paint — each card fades + slides up with a
26
+ * ~40ms delay. Respects prefers-reduced-motion. Default true. */
27
+ readonly stagger?: boolean
28
+ }
29
+
30
+ interface DocCardProps {
31
+ readonly title: ReactNode
32
+ readonly description?: ReactNode
33
+ readonly href: string
34
+ readonly icon?: ReactNode
35
+ readonly className?: string
36
+ /** Optional client-router Link; falls back to plain <a>. */
37
+ readonly LinkComponent?: ComponentType<ShellLinkProps>
38
+ }
39
+
40
+ const PlainLink = ({ to, className, children }: ShellLinkProps): ReactNode => (
41
+ <a href={to} className={className}>{children}</a>
42
+ )
43
+
44
+ const colClass: Record<NonNullable<DocCardsProps['cols']>, string> = {
45
+ 1: 'grid-cols-1',
46
+ 2: 'grid-cols-1 md:grid-cols-2',
47
+ 3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
48
+ }
49
+
50
+ export const DocCards = ({
51
+ children, className, cols = 2, stagger = true,
52
+ }: DocCardsProps): ReactNode => (
53
+ <div
54
+ className={cn(
55
+ 'not-prose my-6 grid gap-4',
56
+ colClass[cols],
57
+ stagger && 'stagger-in',
58
+ className,
59
+ )}
60
+ >
61
+ {children}
62
+ </div>
63
+ )
64
+
65
+ export const DocCard = ({
66
+ title, description, href, icon, className, LinkComponent = PlainLink,
67
+ }: DocCardProps): ReactNode => {
68
+ const Link = LinkComponent
69
+ return (
70
+ <Link
71
+ to={href}
72
+ prefetch
73
+ className={cn(
74
+ 'group relative block p-5 rounded-lg border border-border bg-card/30',
75
+ 'hover:bg-card/60 hover:border-primary/40 hover:-translate-y-0.5',
76
+ 'active:translate-y-0 active:scale-[0.99]',
77
+ 'transition-[transform,background-color,border-color] duration-150 ease-out',
78
+ // Subtle gradient accent on the top edge — fades in on hover.
79
+ 'before:absolute before:inset-x-4 before:top-0 before:h-px before:bg-gradient-to-r before:from-transparent before:via-primary/50 before:to-transparent before:opacity-0 before:transition-opacity group-hover:before:opacity-100',
80
+ className,
81
+ )}
82
+ >
83
+ <div className={cn('flex items-center gap-4', description ? 'mb-4' : '')}>
84
+ {icon ? (
85
+ <div
86
+ className="flex shrink-0 items-center justify-center w-9 h-9 rounded-md bg-primary/10 text-primary [&_svg]:w-5 [&_svg]:h-5"
87
+ aria-hidden="true"
88
+ >
89
+ {icon}
90
+ </div>
91
+ ) : null}
92
+ <div className="min-w-0 font-semibold text-foreground group-hover:text-primary transition-colors flex items-center gap-2">
93
+ <span className="min-w-0">{title}</span>
94
+ <span
95
+ className="shrink-0 text-muted-foreground group-hover:text-primary group-hover:translate-x-0.5 transition-all"
96
+ aria-hidden="true"
97
+ >
98
+
99
+ </span>
100
+ </div>
101
+ </div>
102
+ {description ? (
103
+ <div className="text-sm text-muted-foreground leading-relaxed">
104
+ {description}
105
+ </div>
106
+ ) : null}
107
+ </Link>
108
+ )
109
+ }