From ec06a027ca654d6ba119457b3f726a61e5186aaf Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Mon, 20 Apr 2026 09:04:35 +0200 Subject: [PATCH 1/5] feat(marketing): replace comparator table with 3-card options block The old Tailark comparator-7 rendered a 3-column feature table with tooltip-gated descriptions. It was the only table on a page of cards \u2014 the visual language mismatched the rest of the narrative scroll (hero \u2192 bento cards \u2192 how-it-works \u2192 feature cards \u2192 pricing cards \u2192 FAQ). Reader's eye hit the tabular row and snagged. Rewritten as a 3-col OptionCard grid. Each card: - option name (small label) - headline metric (tabular-nums large number) - time-to-site subtitle - short paragraph explaining the option - 3 signal bullets (Check/X icon in rounded pill, emerald for positive, muted for negative) - cost footer with border-t separator Helpbase card is emphasized: - heavier shadow, opaque bg-card (the other two are bg-card/40) - 'Recommended' badge top-right - all positive signals No tooltips required \u2014 the signal text is short enough to read inline, and more detail is available in the other sections below. Behavior preserved: same 3-way comparison (roll your own / hosted SaaS / helpbase), same 'to first site' metric, same cost ladder. Just rendered as cards that sit naturally next to the pricing section. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/comparator-7.tsx | 294 +++++++++++++-------------- 1 file changed, 137 insertions(+), 157 deletions(-) diff --git a/apps/web/components/comparator-7.tsx b/apps/web/components/comparator-7.tsx index 7c2cea6..ebc87c7 100644 --- a/apps/web/components/comparator-7.tsx +++ b/apps/web/components/comparator-7.tsx @@ -1,189 +1,169 @@ -import { cn } from '@workspace/ui/lib/utils' -import { TooltipProvider, Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { Check, X } from "lucide-react" -const plans = ['rollYourOwn', 'hostedSaas', 'helpbase'] as const +import { cn } from "@workspace/ui/lib/utils" -type Plan = (typeof plans)[number] - -type Cell = boolean | string - -type Feature = { +type Option = { + key: "roll-your-own" | "hosted-saas" | "helpbase" name: string - description?: string - plans: Record -} - -const planLabels: Record = { - rollYourOwn: 'Roll your own', - hostedSaas: 'Hosted SaaS', - helpbase: 'Helpbase', + metric: string + metricSubtitle: string + body: string + signals: Array<{ positive: boolean; text: string }> + cost: string + costNote?: string + emphasized?: boolean } -const features: Feature[] = [ - { - name: 'Time to first site', - description: 'From zero to a live docs site on your domain.', - plans: { rollYourOwn: '2-3 days', hostedSaas: '1 day', helpbase: '3 min' }, - }, +const options: Option[] = [ { - name: 'You own every file', - description: 'Code lives in your repo, commits land in your git history.', - plans: { rollYourOwn: true, hostedSaas: false, helpbase: true }, + key: "roll-your-own", + name: "Roll your own", + metric: "2–3 days", + metricSubtitle: "to first site", + body: "Build a docs site in Next.js from scratch. Burn the weekend writing the framework, then another maintaining it.", + signals: [ + { positive: true, text: "You own every file" }, + { positive: false, text: "No MCP, no llms.txt" }, + { positive: false, text: "No hosted option" }, + ], + cost: "Your weekend", }, { - name: 'MCP server', - description: 'Model Context Protocol endpoint Claude / Cursor / ChatGPT can query.', - plans: { rollYourOwn: 'DIY', hostedSaas: 'Limited', helpbase: 'Built in' }, + key: "hosted-saas", + name: "Hosted docs SaaS", + metric: "1 day", + metricSubtitle: "to first site", + body: "Pay a hosted vendor to run your docs. Fast to start. Your content lives in their database. Migrating off is a project.", + signals: [ + { positive: false, text: "Your docs, their database" }, + { positive: false, text: "MCP is a Pro-tier add-on" }, + { positive: false, text: "Vendor-locked hosting" }, + ], + cost: "$200–1k", + costNote: "/ month", }, { - name: 'llms.txt + typed output', - description: 'Discoverability manifest + machine-readable content, by default.', - plans: { rollYourOwn: false, hostedSaas: false, helpbase: true }, - }, - { - name: 'Deploy anywhere', - description: 'No platform lock-in. Ship your docs on whatever infra you already use \u2014 Vercel, Fly, self-host, your call.', - plans: { rollYourOwn: true, hostedSaas: false, helpbase: true }, - }, - { - name: 'Cost', - description: 'Sticker price for a working help center at seed stage.', - plans: { rollYourOwn: 'Your weekend', hostedSaas: '$200-1k/mo', helpbase: 'Free or hosted' }, - }, - { - name: 'Maintained without you', - description: 'Hosted tier handles updates, caching, MCP scaling.', - plans: { rollYourOwn: false, hostedSaas: true, helpbase: 'Hosted tier' }, - }, - { - name: 'Agents can read it', - description: 'Your editor autocompletes from your real docs instead of guessing.', - plans: { rollYourOwn: 'DIY', hostedSaas: 'Limited', helpbase: true }, + key: "helpbase", + name: "Helpbase", + metric: "3 min", + metricSubtitle: "to first site", + body: "Open source. One command drops a Next.js help center into your repo. MCP server and llms.txt built in. Host it yourself, or with us.", + signals: [ + { positive: true, text: "Own every file" }, + { positive: true, text: "MCP + llms.txt built in" }, + { positive: true, text: "Host anywhere, or with us" }, + ], + cost: "Free or hosted", + emphasized: true, }, ] -const renderPlanColumn = (plan: Plan) => { - const isPrimary = plan === 'helpbase' - const header = ( -
- {planLabels[plan]} -
- ) - - return ( -
- {header} - -
- {features.map((feature, index) => { - const value = feature.plans[plan] - return ( -
-
- {value === true ? ( - - ) : value === false ? ( - - ) : ( - {value} - )} -
-
- ) - })} -
-
-
- ) -} - export default function Comparator() { return (
-
-
-
-

- Two options today. Both are compromises. -

-

- Build your own in Next.js and burn a weekend. Pay a hosted docs SaaS and get locked in. Helpbase is the third option: free as open source, paid only when you want us to host it. -

-
-
- -
-
-
-
Feature
-
- - {features.map((feature, index) => ( -
-
{feature.name}
- {feature.description && ( - - - - ? - - {feature.description} - - - )} -
- ))} -
+
+

+ Two options today. Both are compromises. +

+

+ Helpbase is the third option. +

+
- {plans.map((plan) => ( -
- {renderPlanColumn(plan)} -
- ))} -
+
+ {options.map((option) => ( + + ))}
) } -const Indicator = ({ checked = false }: { checked?: boolean }) => { +function OptionCard({ option }: { option: Option }) { return ( - - {checked ? : '✗'} - - ) -} + {option.emphasized && ( + + Recommended + + )} -const CheckIcon = () => { - return ( - - - +
+ {option.name} +
+ +
+
+ {option.metric} +
+
+ {option.metricSubtitle} +
+
+ +

+ {option.body} +

+ +
    + {option.signals.map((signal, i) => ( +
  • + {signal.positive ? ( + + + + ) : ( + + + + )} + + {signal.text} + +
  • + ))} +
+ +
+ + {option.cost} + + {option.costNote && ( + + {option.costNote} + + )} +
+ ) } From b1230105deb76091f29cdf864aa30f297527f21d Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Mon, 20 Apr 2026 10:01:13 +0200 Subject: [PATCH 2/5] style(design): remove hero decorative frame The hero's content wrapper carried 'corner-t-notch rounded-t-[2rem] border-x border-t' \u2014 a Tailark hero-section/six poster frame at max-w-6xl that wrapped the headline + sub + CTAs + terminal. Three reasons it had to go: 1. The frame's bottom edge floated orphan in the viewport when you scrolled past it. The frame ended at the hero's bottom with nothing visually continuing it below \u2014 a horizontal line that pointed at empty space. 2. The page already has section frames where they pay rent (how-it-works grid, demo cross-link card, faqs sidebar+accordion). Each of those frames groups a content cluster. The hero frame wrapped one large content block but didn't group anything that wasn't already visually unified by typography and the terminal. 3. The 6xl frame width didn't align with anything below \u2014 every other framed section sits at 5xl. Two different widths, two different visual languages on one page. Mintlify, Stripe, and Anthropic don't frame their heroes. The hero is the hero because of the headline, the terminal, and the install command \u2014 not because of a border. Three classes removed (corner-t-notch, rounded-t-[2rem], border-x, border-t) plus the corner-t-notch decoration. Hero typography and content untouched. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/marketing/hero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/marketing/hero.tsx b/apps/web/components/marketing/hero.tsx index 1a7a639..e6458d4 100644 --- a/apps/web/components/marketing/hero.tsx +++ b/apps/web/components/marketing/hero.tsx @@ -41,7 +41,7 @@ export function Hero() { -
+

Date: Mon, 20 Apr 2026 10:10:30 +0200 Subject: [PATCH 3/5] style(design): swap Tailark hub icon for helpbase 'h' + upgrade MDX preview to source+rendered Two improvements in the 'Every file is yours' row. FlowIllustration hub: The center node was the Tailark logomark (LogoIcon \u2014 a purple/teal gradient geometric shape that ships with their illustration kit). That's their brand bleeding through ours. Replaced with a clean white 'h' letterform on the dark circle, matching the helpbase wordmark in the header. Bumped the circle bg from black/75 to black/85 so the letterform reads cleanly in light mode. Removed the LogoIcon import. MdxSourcePreview: Was source-only \u2014 a single panel of fake .mdx code. The card's copy promises 'Import React components, version control every change, diff in PR review' but the visual didn't show the moment that matters most: MDX source rendering as actual docs. Rebuilt as a stacked card: - Header tab: filename + 'SOURCE' label - Source panel: syntax-highlighted MDX (violet frontmatter keys, amber numbers, emerald strings, sky-blue JSX tags) \u2014 a real-feeling code block, not flat mono text - Divider - Rendered panel: 'RENDERED' label, then the h1 styled as a real heading, body text, and the shown as an actual amber-tinted callout box with a lucide Lightbulb icon This makes the abstract claim concrete: MDX is code AND it's docs. The visual literally shows the transformation the platform performs. Both files are in components/illustrations/ which is in HOSTED_TIER_EXCLUDES \u2014 no template drift. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/illustrations/flow.tsx | 7 +- .../illustrations/mdx-source-preview.tsx | 79 ++++++++++++++----- 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/apps/web/components/illustrations/flow.tsx b/apps/web/components/illustrations/flow.tsx index 1008e7e..707ff68 100644 --- a/apps/web/components/illustrations/flow.tsx +++ b/apps/web/components/illustrations/flow.tsx @@ -1,6 +1,5 @@ 'use client' -import { LogoIcon } from "@/components/logo" import { Vercel } from '@/components/ui/svgs/vercel' import { Supabase } from '@/components/ui/svgs/supabase' import { Firebase } from '@/components/ui/svgs/firebase' @@ -184,8 +183,10 @@ export const FlowIllustration = () => {

-
- +
+ + h +
diff --git a/apps/web/components/illustrations/mdx-source-preview.tsx b/apps/web/components/illustrations/mdx-source-preview.tsx index 31dd286..f1d35b7 100644 --- a/apps/web/components/illustrations/mdx-source-preview.tsx +++ b/apps/web/components/illustrations/mdx-source-preview.tsx @@ -1,36 +1,73 @@ +import { FileText, Lightbulb } from "lucide-react" + export const MdxSourcePreview = () => (
-
- content/getting-started.mdx + className="ring-border bg-card relative z-10 mx-auto w-full max-w-sm overflow-hidden rounded-xl border border-transparent ring-1"> +
+
+ + + getting-started.mdx + +
+ + Source +
-
-
---
+ +
+
---
- title:{" "} - Getting started + title:{" "} + Install helpbase
- order:{" "} - 1 + order:{" "} + 1
-
---
-
-
# Install helpbase
-
-
- Run one command in your repo to +
---
+
+
+ # Install helpbase
- create a full Next.js help center. + Run one command to get started. +
+
+
+ {"{" "} + type + = + "tip" + {">"} +
+
+ Edit and ship. +
+
{""}
+
+ +
+
+ Rendered +
+
+ Install helpbase +
+
+ Run one command to get started.
-
-
{``}
-
- Edit this file to add your own content. +
+ + + Edit and ship. +
-
{``}
) From b06556f664740ad7c053180e12865e0853b0576c Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Mon, 20 Apr 2026 12:25:47 +0200 Subject: [PATCH 4/5] style(anim): add tasteful press feedback to button component The marketing page button (used by header CTAs, hero 'See live demo', pricing tier CTAs, and demo cross-link CTAs) had only transition-colors \u2014 hover worked but clicks had zero visual feedback. The shared packages/ui button had active:translate-y-px so CopyButton already felt right, but everything else didn't. Applied Emil Kowalski's micro-interaction blueprint from animations.dev: - Transition widened from colors-only to transition-[color,background-color,transform,box-shadow] so transform and shadow can animate together. - duration-150 ease-out \u2014 micro-interaction range (100\u2013150ms), ease-out for the element entering its pressed state. - active:scale-[0.98] \u2014 2% squeeze on press. Buttons are h-9/h-10, 3% would read as cartoonish; 2% feels like a tap, not a gimmick. - active:shadow-sm (default, destructive) / active:shadow-xs (outline) \u2014 shadow reduces on press, mimicking a physical button being depressed. Subtle but readable. - motion-reduce:transition-none motion-reduce:active:scale-100 \u2014 respects prefers-reduced-motion, disables animation for users who've opted out. - Removed the stray duration-200 override on outline (now inherits the base duration-150 for consistency across variants). Tailwind v4's hover: modifier already guards against mobile tap-flicker via @media (hover: hover), so no extra work needed there. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/ui/button.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/components/ui/button.tsx b/apps/web/components/ui/button.tsx index b6c7104..d0a0dbf 100644 --- a/apps/web/components/ui/button.tsx +++ b/apps/web/components/ui/button.tsx @@ -3,12 +3,12 @@ import { Slot } from '@radix-ui/react-slot' import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@workspace/ui/lib/utils' -const buttonVariants = cva('cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', { +const buttonVariants = cva('cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,background-color,transform,box-shadow] duration-150 ease-out focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98] motion-reduce:transition-none motion-reduce:active:scale-100 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', { variants: { variant: { - default: 'shadow-md border-[0.5px] border-white/10 shadow-black/15 [&_svg]:drop-shadow-sm text-shadow-sm bg-primary ring-1 ring-(--ring-color) [--ring-color:color-mix(in_oklab,black_15%,var(--color-primary))] dark:border-transparent dark:[--ring-color:color-mix(in_oklab,white_15%,var(--color-primary))] text-primary-foreground hover:bg-primary/90', - destructive: 'shadow-md border-[0.5px] border-white/10 shadow-black/25 [&_svg]:drop-shadow-sm text-shadow-sm bg-destructive ring-1 ring-(--ring-color) [--ring-color:color-mix(in_oklab,black_15%,var(--color-destructive))] text-destructive-foreground hover:bg-destructive/90', - outline: 'shadow-sm shadow-black/15 border border-transparent bg-card ring-1 ring-foreground/10 duration-200 hover:bg-muted/50 dark:ring-foreground/15 dark:hover:bg-muted/50', + default: 'shadow-md border-[0.5px] border-white/10 shadow-black/15 [&_svg]:drop-shadow-sm text-shadow-sm bg-primary ring-1 ring-(--ring-color) [--ring-color:color-mix(in_oklab,black_15%,var(--color-primary))] dark:border-transparent dark:[--ring-color:color-mix(in_oklab,white_15%,var(--color-primary))] text-primary-foreground hover:bg-primary/90 active:shadow-sm', + destructive: 'shadow-md border-[0.5px] border-white/10 shadow-black/25 [&_svg]:drop-shadow-sm text-shadow-sm bg-destructive ring-1 ring-(--ring-color) [--ring-color:color-mix(in_oklab,black_15%,var(--color-destructive))] text-destructive-foreground hover:bg-destructive/90 active:shadow-sm', + outline: 'shadow-sm shadow-black/15 border border-transparent bg-card ring-1 ring-foreground/10 hover:bg-muted/50 active:shadow-xs dark:ring-foreground/15 dark:hover:bg-muted/50', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-foreground/5 hover:text-foreground', link: 'text-primary underline-offset-4 hover:underline', From fffeeaf0df61313108b8fe9e1fb03220ada9dc47 Mon Sep 17 00:00:00 2001 From: meglerhagen Date: Mon, 20 Apr 2026 13:54:55 +0200 Subject: [PATCH 5/5] fix(ci): inline Button into scaffold + drop dead UI primitives PR #20 added components/footer.tsx with an import of @/components/ui/button, but apps/web's button.tsx is in HOSTED_TIER_EXCLUDES because it carries marketing-only press feedback styling. The synced footer landed in the scaffold templates and registry without a Button to resolve, breaking both smoke:install and smoke:registry on Turbopack with 'Module not found: @/components/ui/button'. CI has been red on main since fb004ed; this fix restores green. Three coordinated changes: 1. inlineShadcnPrimitivesToTemplates now inlines BOTH Badge and Button from packages/ui/src/components/ into templates/components/ui/. Button uses the umbrella radix-ui import (Slot.Root pattern), works cleanly with @workspace import rewrites. 2. registry.json adds 'button' to registryDependencies and '@radix-ui/react-slot' to dependencies, so consumers running 'shadcn add helpbase' get Button auto-installed from the official shadcn registry and the slot primitive resolves for any other shadcn primitive that needs it. 3. Templates package.json now ships @radix-ui/react-slot directly. The umbrella radix-ui package re-exports it under Slot.Root with a different API, so primitives that import { Slot } from '@radix-ui/react-slot' need the individual package. Plus: deleted 7 dead UI primitives from templates AND registry that were copied from apps/web by the default sync rule. None of them were used by anything in the scaffold path: - card.tsx, chart.tsx, infinite-slider.tsx, input.tsx, label.tsx, text-scramble.tsx, tooltip.tsx Their only consumers in apps/web were components in HOSTED_TIER_EXCLUDES (pricing, bento, chart-illustration). Shipping them as dead code dragged in recharts, motion/react, and other marketing-only deps the scaffold doesn't need. Added all 7 to HOSTED_TIER_EXCLUDES so future syncs don't recreate them. Scaffold consumers can shadcn-add card / tooltip / etc. on demand. Locally verified: - pnpm smoke:install passes (templates path) - pnpm smoke:registry passes (shadcn add path) - pnpm registry:build regenerates public/r/*.json clean Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/public/r/help-center.json | 6 +- apps/web/public/r/registry.json | 5 +- .../templates/components/ui/button.tsx | 65 +++ .../templates/components/ui/card.tsx | 70 ---- .../templates/components/ui/chart.tsx | 373 ------------------ .../components/ui/infinite-slider.tsx | 91 ----- .../templates/components/ui/input.tsx | 20 - .../templates/components/ui/label.tsx | 24 -- .../templates/components/ui/text-scramble.tsx | 86 ---- .../templates/components/ui/tooltip.tsx | 55 --- .../create-helpbase/templates/package.json | 1 + registry.json | 5 +- registry/helpbase/components/ui/card.tsx | 70 ---- registry/helpbase/components/ui/chart.tsx | 373 ------------------ .../components/ui/infinite-slider.tsx | 91 ----- registry/helpbase/components/ui/input.tsx | 20 - registry/helpbase/components/ui/label.tsx | 24 -- .../helpbase/components/ui/text-scramble.tsx | 86 ---- registry/helpbase/components/ui/tooltip.tsx | 55 --- scripts/sync-templates.mjs | 39 +- 20 files changed, 109 insertions(+), 1450 deletions(-) create mode 100644 packages/create-helpbase/templates/components/ui/button.tsx delete mode 100644 packages/create-helpbase/templates/components/ui/card.tsx delete mode 100644 packages/create-helpbase/templates/components/ui/chart.tsx delete mode 100644 packages/create-helpbase/templates/components/ui/infinite-slider.tsx delete mode 100644 packages/create-helpbase/templates/components/ui/input.tsx delete mode 100644 packages/create-helpbase/templates/components/ui/label.tsx delete mode 100644 packages/create-helpbase/templates/components/ui/text-scramble.tsx delete mode 100644 packages/create-helpbase/templates/components/ui/tooltip.tsx delete mode 100644 registry/helpbase/components/ui/card.tsx delete mode 100644 registry/helpbase/components/ui/chart.tsx delete mode 100644 registry/helpbase/components/ui/infinite-slider.tsx delete mode 100644 registry/helpbase/components/ui/input.tsx delete mode 100644 registry/helpbase/components/ui/label.tsx delete mode 100644 registry/helpbase/components/ui/text-scramble.tsx delete mode 100644 registry/helpbase/components/ui/tooltip.tsx diff --git a/apps/web/public/r/help-center.json b/apps/web/public/r/help-center.json index a85231a..a2e2b4f 100644 --- a/apps/web/public/r/help-center.json +++ b/apps/web/public/r/help-center.json @@ -12,11 +12,13 @@ "rehype-pretty-code", "shiki", "zod", - "lucide-react" + "lucide-react", + "@radix-ui/react-slot" ], "registryDependencies": [ "badge", "accordion", + "button", "tabs" ], "files": [ @@ -52,7 +54,7 @@ }, { "path": "registry/helpbase/components/footer.tsx", - "content": "import Link from \"next/link\"\n\nexport function Footer() {\n return (\n
\n
\n

\n Built with{\" \"}\n \n helpbase\n \n

\n
\n \n GitHub\n \n |\n \n Twitter\n \n
\n
\n
\n )\n}\n", + "content": "import { Button } from '@/components/ui/button'\nimport Link from 'next/link'\n\nconst links = [\n {\n group: 'Product',\n items: [\n { title: 'Pricing', href: '/#pricing' },\n { title: 'Demo', href: 'https://demo.helpbase.dev' },\n { title: 'FAQ', href: '/#faq' },\n ],\n },\n {\n group: 'Resources',\n items: [\n { title: 'Docs', href: '/docs' },\n { title: 'GitHub', href: 'https://github.com/Codehagen/helpbase' },\n { title: 'MCP', href: '/docs/mcp' },\n ],\n },\n {\n group: 'Company',\n items: [\n { title: 'Changelog', href: 'https://github.com/Codehagen/helpbase/releases' },\n { title: 'License', href: 'https://github.com/Codehagen/helpbase/blob/main/LICENSE' },\n { title: 'Privacy', href: '/docs/privacy' },\n ],\n },\n]\n\nexport default function FooterSection() {\n return (\n \n
\n
\n
\n \n helpbase\n \n\n

\n Open-source help centers with an MCP server and llms.txt built in. Self-host free, or host it with us.\n

\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n
\n
\n \n
\n
\n {links.map((group) => (\n \n {group.group}\n\n
\n {group.items.map((item) => (\n \n {item.title}\n \n ))}\n
\n
\n ))}\n
\n\n
\n
\n
Updates, every release
\n
\n \n \n Follow on GitHub Releases\n \n ↗\n \n \n \n
\n

\n Release notes and shipped features, straight from the repo. No newsletter inbox to opt out of.\n

\n
\n
\n
\n\n
\n \n © {new Date().getFullYear()} helpbase. Built with shadcn/ui, Next.js, Supabase, Vercel.\n \n
\n
\n \n \n
\n Open source, live\n
\n
\n
\n \n )\n}\n\nexport { FooterSection as Footer }\n", "type": "registry:component", "target": "components/footer.tsx" }, diff --git a/apps/web/public/r/registry.json b/apps/web/public/r/registry.json index 31f11ff..1d1c936 100644 --- a/apps/web/public/r/registry.json +++ b/apps/web/public/r/registry.json @@ -17,9 +17,10 @@ "rehype-pretty-code", "shiki", "zod", - "lucide-react" + "lucide-react", + "@radix-ui/react-slot" ], - "registryDependencies": ["badge", "accordion", "tabs"], + "registryDependencies": ["badge", "accordion", "button", "tabs"], "files": [ { "path": "registry/helpbase/app/(docs)/layout.tsx", diff --git a/packages/create-helpbase/templates/components/ui/button.tsx b/packages/create-helpbase/templates/components/ui/button.tsx new file mode 100644 index 0000000..59e8eb9 --- /dev/null +++ b/packages/create-helpbase/templates/components/ui/button.tsx @@ -0,0 +1,65 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/80", + outline: + "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-transparent dark:hover:bg-input/30", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", + ghost: + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: + "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5", + xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", + icon: "size-9", + "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot.Root : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/packages/create-helpbase/templates/components/ui/card.tsx b/packages/create-helpbase/templates/components/ui/card.tsx deleted file mode 100644 index 96681a2..0000000 --- a/packages/create-helpbase/templates/components/ui/card.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import * as React from 'react' -import { cn } from '@/lib/utils' -import { Slot } from '@radix-ui/react-slot' - -export interface CardProps extends React.HTMLAttributes { - asChild?: boolean -} - -function Card({ className, asChild = false, ...props }: CardProps) { - const Comp = asChild ? Slot : 'div' - return ( - - ) -} - -function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
- ) -} - -function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
- ) -} - -function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
- ) -} - -function CardContent({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
- ) -} - -function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { - return ( -
- ) -} - -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } \ No newline at end of file diff --git a/packages/create-helpbase/templates/components/ui/chart.tsx b/packages/create-helpbase/templates/components/ui/chart.tsx deleted file mode 100644 index 17a5927..0000000 --- a/packages/create-helpbase/templates/components/ui/chart.tsx +++ /dev/null @@ -1,373 +0,0 @@ -"use client" - -import * as React from "react" -import * as RechartsPrimitive from "recharts" -import type { TooltipValueType } from "recharts" - -import { cn } from "@/lib/utils" - -// Format: { THEME_NAME: CSS_SELECTOR } -const THEMES = { light: "", dark: ".dark" } as const - -const INITIAL_DIMENSION = { width: 320, height: 200 } as const -type TooltipNameType = number | string - -export type ChartConfig = Record< - string, - { - label?: React.ReactNode - icon?: React.ComponentType - } & ( - | { color?: string; theme?: never } - | { color?: never; theme: Record } - ) -> - -type ChartContextProps = { - config: ChartConfig -} - -const ChartContext = React.createContext(null) - -function useChart() { - const context = React.useContext(ChartContext) - - if (!context) { - throw new Error("useChart must be used within a ") - } - - return context -} - -function ChartContainer({ - id, - className, - children, - config, - initialDimension = INITIAL_DIMENSION, - ...props -}: React.ComponentProps<"div"> & { - config: ChartConfig - children: React.ComponentProps< - typeof RechartsPrimitive.ResponsiveContainer - >["children"] - initialDimension?: { - width: number - height: number - } -}) { - const uniqueId = React.useId() - const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}` - - return ( - -
- - - {children} - -
-
- ) -} - -const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { - const colorConfig = Object.entries(config).filter( - ([, config]) => config.theme ?? config.color - ) - - if (!colorConfig.length) { - return null - } - - return ( -