Skip to content

Commit 2953bb0

Browse files
authored
fix(navbar): drop the Babelize default for githubUrl and accept owner/repo shorthand (#79)
1 parent 4a19d10 commit 2953bb0

5 files changed

Lines changed: 99 additions & 28 deletions

File tree

‎content/docs/components/navigation/navbar.mdx‎

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -112,32 +112,33 @@ export function SiteNav() {
112112
onValueChange={setLocale}
113113
cta={{ label: "Get Started", href: "/signup" }}
114114
showGitHub
115+
githubUrl="your-org/your-repo" // or a full URL
115116
/>
116117
);
117118
}
118119
```
119120

120121
## Props
121122

122-
| Prop | Type | Required | Description |
123-
| --------------- | --------------------------------- | -------- | ---------------------------------------------------------------- |
124-
| `logo` | `React.ReactNode` | No | Logo element (renders before nav links) |
125-
| `links` | `NavLink[]` | No | Navigation links |
126-
| `activeHref` | `string` | No | `href` of the current page — gets `aria-current` and a highlight |
127-
| `locales` | `Locale[]` | No | Available locales for language selector |
128-
| `value` | `string` | No | Selected locale code — pass this to control the component |
129-
| `defaultValue` | `string` | No | Initial locale code when uncontrolled (default: first locale) |
130-
| `onValueChange` | `(code: string) => void` | No | Callback when locale changes |
131-
| `cta` | `{ label: string; href: string }` | No | CTA button |
132-
| `showGitHub` | `boolean` | No | Show GitHub icon button |
133-
| `githubUrl` | `string` | No | GitHub repository URL |
134-
| `sticky` | `boolean` | No | Make navbar sticky at top (default: `true`) |
135-
| `variant` | `"default" \| "floating"` | No | Full-width bar or inset floating card (default: `"default"`) |
136-
| `showFlags` | `boolean` | No | Show flags in language selector (default: `true`) |
137-
| `maxHeight` | `number \| string` | No | Max height of the scrolling language menu (default: `"15rem"`) |
138-
| `linkComponent` | `React.ElementType` | No | Component used for internal links (default: `"a"`) |
139-
| `classNames` | `NavBarClassNames` | No | Per-part class overrides (see below) |
140-
| `className` | `string` | No | Additional CSS classes |
123+
| Prop | Type | Required | Description |
124+
| --------------- | --------------------------------- | -------- | ----------------------------------------------------------------------------- |
125+
| `logo` | `React.ReactNode` | No | Logo element (renders before nav links) |
126+
| `links` | `NavLink[]` | No | Navigation links |
127+
| `activeHref` | `string` | No | `href` of the current page — gets `aria-current` and a highlight |
128+
| `locales` | `Locale[]` | No | Available locales for language selector |
129+
| `value` | `string` | No | Selected locale code — pass this to control the component |
130+
| `defaultValue` | `string` | No | Initial locale code when uncontrolled (default: first locale) |
131+
| `onValueChange` | `(code: string) => void` | No | Callback when locale changes |
132+
| `cta` | `{ label: string; href: string }` | No | CTA button |
133+
| `showGitHub` | `boolean` | No | Show the GitHub icon button. Needs `githubUrl` — without one, nothing renders |
134+
| `githubUrl` | `string` | No | Full URL, or `"owner"` / `"owner/repo"` shorthand for github.com |
135+
| `sticky` | `boolean` | No | Make navbar sticky at top (default: `true`) |
136+
| `variant` | `"default" \| "floating"` | No | Full-width bar or inset floating card (default: `"default"`) |
137+
| `showFlags` | `boolean` | No | Show flags in language selector (default: `true`) |
138+
| `maxHeight` | `number \| string` | No | Max height of the scrolling language menu (default: `"15rem"`) |
139+
| `linkComponent` | `React.ElementType` | No | Component used for internal links (default: `"a"`) |
140+
| `classNames` | `NavBarClassNames` | No | Per-part class overrides (see below) |
141+
| `className` | `string` | No | Additional CSS classes |
141142

142143
Any other prop is spread onto the underlying `<nav>` element, and the ref is
143144
forwarded to it.
@@ -244,6 +245,7 @@ export default function App() {
244245
}}
245246
cta={{ label: "Get Started", href: "/signup" }}
246247
showGitHub
248+
githubUrl="your-org/your-repo"
247249
/>
248250
);
249251
}
@@ -329,6 +331,18 @@ export default function App() {
329331
}
330332
```
331333

334+
### GitHub link
335+
336+
`showGitHub` adds an icon button that links to `githubUrl`. Pass `"owner/repo"` for a
337+
repository, `"owner"` for a profile or organisation, or any full URL.
338+
339+
```tsx
340+
<NavBar showGitHub githubUrl="acme/website" />
341+
<NavBar showGitHub githubUrl="https://github.com/acme" />
342+
```
343+
344+
`showGitHub` without `githubUrl` renders no button and logs a warning.
345+
332346
### Non-sticky
333347

334348
```tsx

‎content/docs/customization/interfaces.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ export interface NavBarProps {
151151
linkComponent?: React.ElementType;
152152
/** CTA button */
153153
cta?: { label: string; href: string };
154-
/** Show GitHub icon button */
154+
/** Show GitHub icon button. Needs `githubUrl`; without one, nothing renders */
155155
showGitHub?: boolean;
156-
/** GitHub repository URL */
156+
/** Full URL, or "owner" / "owner/repo" shorthand for github.com */
157157
githubUrl?: string;
158158
/** Make navbar sticky at top (default: true) */
159159
sticky?: boolean;

‎src/components/docs/navbar-demo.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function NavBarDemo() {
1717
onValueChange={setLocale}
1818
cta={{ label: navCopy[locale].cta, href: "#get-started" }}
1919
showGitHub
20+
githubUrl="babelize/babelize-elements" // "owner/repo" or a full URL
2021
variant="floating" // default, floating
2122
sticky={false}
2223
/>

‎src/registry/components/navbar.tsx‎

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,12 @@ export interface NavBarProps extends Omit<
6868
onValueChange?: (code: string) => void;
6969
/** Primary call-to-action button */
7070
cta?: { label: string; href: string };
71-
/** Show a GitHub icon link */
71+
/** Show a GitHub icon link. Needs `githubUrl`; without one, nothing renders. */
7272
showGitHub?: boolean;
73-
/** Target of the GitHub icon link */
73+
/**
74+
* Target of the GitHub icon link: a full URL, or `"owner"` / `"owner/repo"`
75+
* shorthand for a github.com profile or repository.
76+
*/
7477
githubUrl?: string;
7578
/** Pin the bar to the top of the viewport (default: true) */
7679
sticky?: boolean;
@@ -131,6 +134,18 @@ const GHOST_BUTTON = cn(
131134

132135
const EASE = [0.16, 1, 0.3, 1] as const;
133136

137+
/**
138+
* Expands `"owner"` / `"owner/repo"` shorthand to a github.com URL. Anything else,
139+
* full URLs included, passes through unchanged.
140+
*/
141+
function resolveGitHubUrl(value: string | undefined): string | undefined {
142+
const trimmed = value?.trim();
143+
if (!trimmed) return undefined;
144+
if (/^[a-z][a-z\d+.-]*:/i.test(trimmed)) return trimmed;
145+
const path = trimmed.replace(/^(www\.)?github\.com\//i, "").replace(/^\/+|\/+$/g, "");
146+
return /^[A-Za-z\d-]+(\/[\w.-]+)?$/.test(path) ? `https://github.com/${path}` : trimmed;
147+
}
148+
134149
export const NavBar = React.forwardRef<HTMLElement, NavBarProps>(function NavBar(
135150
{
136151
logo,
@@ -142,7 +157,7 @@ export const NavBar = React.forwardRef<HTMLElement, NavBarProps>(function NavBar
142157
onValueChange,
143158
cta,
144159
showGitHub = false,
145-
githubUrl = "https://github.com/babelize/babelize-elements",
160+
githubUrl,
146161
sticky = true,
147162
variant = "default",
148163
className,
@@ -308,9 +323,22 @@ export const NavBar = React.forwardRef<HTMLElement, NavBarProps>(function NavBar
308323
</Panel>
309324
);
310325

326+
const githubHref = resolveGitHubUrl(githubUrl);
327+
// `showGitHub` alone used to link to this library's own repository. Now it
328+
// renders nothing, so say why instead of failing silently.
329+
const missingGitHubUrl = showGitHub && !githubHref;
330+
React.useEffect(() => {
331+
if (missingGitHubUrl) {
332+
console.warn(
333+
'NavBar: `showGitHub` needs `githubUrl` (a URL or "owner/repo"); the GitHub link is hidden.',
334+
);
335+
}
336+
}, [missingGitHubUrl]);
337+
const hasGitHub = showGitHub && !!githubHref;
338+
311339
const githubLink = (
312340
<a
313-
href={githubUrl}
341+
href={githubHref}
314342
target="_blank"
315343
rel="noopener noreferrer"
316344
className={cn(GHOST_BUTTON, "size-9")}
@@ -436,7 +464,7 @@ export const NavBar = React.forwardRef<HTMLElement, NavBarProps>(function NavBar
436464
</div>
437465
)}
438466

439-
{showGitHub && githubLink}
467+
{hasGitHub && githubLink}
440468

441469
{cta && (
442470
<Link href={cta.href} className={cn(ctaClass, "ms-1")}>
@@ -539,7 +567,7 @@ export const NavBar = React.forwardRef<HTMLElement, NavBarProps>(function NavBar
539567
</Link>
540568
</motion.div>
541569
))}
542-
{(cta || showGitHub) && (
570+
{(cta || hasGitHub) && (
543571
<motion.div
544572
className="mt-2 flex items-center gap-2 border-t pt-3"
545573
initial={{ opacity: 0, y: -6 }}
@@ -556,7 +584,7 @@ export const NavBar = React.forwardRef<HTMLElement, NavBarProps>(function NavBar
556584
{ctaArrow}
557585
</Link>
558586
)}
559-
{showGitHub && githubLink}
587+
{hasGitHub && githubLink}
560588
</motion.div>
561589
)}
562590
</div>

‎test/navbar.test.tsx‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,31 @@ describe("NavBar customization", () => {
420420
expect(screen.getAllByRole("link", { name: "Docs" })).toHaveLength(1);
421421
});
422422
});
423+
424+
describe("NavBar GitHub link", () => {
425+
const githubLinks = () => screen.queryAllByRole("link", { name: "GitHub" });
426+
427+
it.each([
428+
["acme/website", "https://github.com/acme/website"],
429+
["acme", "https://github.com/acme"],
430+
["github.com/acme/website/", "https://github.com/acme/website"],
431+
["https://github.com/acme/website", "https://github.com/acme/website"],
432+
["https://gitlab.com/acme/website", "https://gitlab.com/acme/website"],
433+
])("resolves githubUrl=%s to %s", (githubUrl, href) => {
434+
render(<NavBar showGitHub githubUrl={githubUrl} />);
435+
expect(githubLinks()[0]).toHaveAttribute("href", href);
436+
});
437+
438+
it("renders nothing and warns when showGitHub has no githubUrl", () => {
439+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
440+
render(<NavBar showGitHub />);
441+
expect(githubLinks()).toHaveLength(0);
442+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("`showGitHub` needs `githubUrl`"));
443+
warn.mockRestore();
444+
});
445+
446+
it("stays hidden without showGitHub, even with a githubUrl", () => {
447+
render(<NavBar githubUrl="acme/website" />);
448+
expect(githubLinks()).toHaveLength(0);
449+
});
450+
});

0 commit comments

Comments
 (0)