Skip to content

Commit 12015f6

Browse files
committed
Enhance Navbar component with children prop and update documentation
- Added a `children` prop to the Navbar component for rendering arbitrary content to the right of links on desktop and at the top of the mobile dropdown. - Updated AGENTS.md to include details about the new `children` prop and provided examples for its usage. - Introduced a GitHub star component in the layout to showcase integration with the Navbar. - Improved button styles for dark mode in the button component. - Adjusted gradient colors in global styles for better visual consistency.
1 parent cfbed2e commit 12015f6

11 files changed

Lines changed: 178 additions & 19 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@pplethai/components": patch
3+
---
4+
5+
Fix `Button` rendering:
6+
7+
- `variant="outline"`: border and text now use the foreground token in dark mode so the button is visible on dark navy surfaces (previously navy-on-navy).
8+
- `variant="ghost"`: hover background and text now use the foreground token in dark mode so hover stays legible against the dark surface (previously navy text blended into the dark background).
9+
- Gradient hover animation (`.gradient-hover-animate`): overshoot `background-position` by 1px on both rest (`-1px center`) and hover end (`calc(100% + 1px) center`) so the 200% gradient image extends past the container edges throughout the animation. Eliminates a subpixel sliver where the surface beneath would bleed through (showed up as a stray white edge on destructive buttons and a white outline on the right of outline buttons).

.changeset/navbar-children-slot.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pplethai/components": minor
3+
---
4+
5+
Navbar now accepts `children` as a trailing slot — rendered to the right of the links on desktop and at the top of the mobile dropdown panel. Useful for login buttons, notification bells, theme toggles, etc. When the navbar is in `dark` variant, children are wrapped in a `.dark` scope so design tokens automatically flip to dark-mode values.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pplethai/components": patch
3+
---
4+
5+
Bumped contrast on the primary button gradient (`--gradient-primary-button`) in both light and dark modes — lighter highlight stop and darker shadow stop so the gradient feels less flat. Middle brand-orange stop is unchanged.

AGENTS.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,26 @@ function AppLayout() {
269269
| `renderHomeLink` | `(props: NavbarHomeLinkRenderProps) => ReactNode` || Custom home/brand link renderer (router) |
270270
| `renderLink` | `(props: NavbarLinkRenderProps) => ReactNode` || Custom menu link renderer (router) |
271271
| `logo` | `ReactNode` || Defaults to `<Logo size="sm" className="text-primary" />` |
272+
| `children` | `ReactNode` || Arbitrary content rendered to the right of the links on desktop; rendered at the top of the mobile dropdown panel (above the items). Use for login buttons, notification bells, theme toggles, etc. When the navbar variant is `dark`, children are wrapped in a `.dark` scope so design tokens (`bg-primary`, `text-foreground`, `border-border`, etc.) automatically flip to their dark-mode values. |
272273
| `mobileMenuAriaLabel` | `{ open: string; close: string }` || Defaults to Thai labels |
273274
| `navAriaLabel` | `string` || Defaults to `"เมนูหลัก"` |
274275

275276
`navLinkClassName(isActive)` is also exported for custom integrations.
276277

278+
**Trailing slot example** — login button to the right of nav links:
279+
280+
```tsx
281+
import { Navbar, Button, Inline } from "@pplethai/components";
282+
import { Bell, LogIn } from "lucide-react";
283+
284+
<Navbar title="ระบบ" items={items} pathname={pathname}>
285+
<Inline gap="xs" align="center">
286+
<Button variant="ghost" size="icon" aria-label="แจ้งเตือน"><Bell /></Button>
287+
<Button variant="outline" size="sm"><LogIn />เข้าสู่ระบบ</Button>
288+
</Inline>
289+
</Navbar>
290+
```
291+
277292
---
278293

279294
## 6. Form inputs
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Button } from "@pplethai/components";
2+
import { Github, Star } from "lucide-react";
3+
import * as React from "react";
4+
5+
const REPO = "PPLEThai/pple-design-system";
6+
7+
function formatStars(count: number): string {
8+
if (count >= 1000) {
9+
return `${(count / 1000).toFixed(1)}k`;
10+
}
11+
return String(count);
12+
}
13+
14+
export function GitHubStar() {
15+
const [stars, setStars] = React.useState<number | null>(null);
16+
17+
React.useEffect(() => {
18+
let cancelled = false;
19+
fetch(`https://api.github.com/repos/${REPO}`)
20+
.then((res) => (res.ok ? res.json() : null))
21+
.then((data) => {
22+
if (!cancelled && data && typeof data.stargazers_count === "number") {
23+
setStars(data.stargazers_count);
24+
}
25+
})
26+
.catch(() => {});
27+
return () => {
28+
cancelled = true;
29+
};
30+
}, []);
31+
32+
return (
33+
<Button asChild variant="outline" size="sm">
34+
<a
35+
href={`https://github.com/${REPO}`}
36+
target="_blank"
37+
rel="noreferrer"
38+
aria-label={
39+
stars !== null
40+
? `Star ${REPO} on GitHub — ${stars} stars`
41+
: `Star ${REPO} on GitHub`
42+
}
43+
>
44+
<Github />
45+
<span>Star</span>
46+
{stars !== null ? (
47+
<>
48+
<span aria-hidden="true" className="mx-1 h-4 w-px bg-current opacity-30" />
49+
<Star className="size-3.5 fill-current" />
50+
<span>{formatStars(stars)}</span>
51+
</>
52+
) : null}
53+
</a>
54+
</Button>
55+
);
56+
}

apps/docs/src/components/Layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as React from "react";
1111

1212
const footerLinkClass = "text-primary hover:underline";
1313
import { NavLink, Outlet, useLocation } from "react-router-dom";
14+
import { GitHubStar } from "./GitHubStar";
1415

1516
const navItems: NavbarItem[] = [
1617
{ href: "/", label: "หน้าแรก", end: true },
@@ -53,7 +54,9 @@ export function Layout() {
5354
{item.label}
5455
</NavLink>
5556
)}
56-
/>
57+
>
58+
<GitHubStar />
59+
</Navbar>
5760
<Stack
5861
as="main"
5962
gap="none"

apps/docs/src/pages/components/navbar.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Navbar, type NavbarItem } from "@pplethai/components";
1+
import { Button, Inline, Navbar, type NavbarItem } from "@pplethai/components";
2+
import { Bell, LogIn } from "lucide-react";
23
import { NavLink } from "react-router-dom";
34
import { ComponentPage } from "../../components/ComponentPage";
45

@@ -103,10 +104,55 @@ function MyLayout() {
103104
pathname={typeof window !== "undefined" ? window.location.pathname : ""}
104105
/>`,
105106
},
107+
{
108+
title: "แทรกคอมโพเนนต์ทางขวา (children)",
109+
description:
110+
"ส่ง children เพื่อแสดงคอนเทนต์ใด ๆ ทางขวาของลิงก์บนเดสก์ท็อป — บนมือถือจะแสดงด้านบนของเมนูดรอปดาวน์",
111+
demo: (
112+
<div className="overflow-hidden rounded-md border">
113+
<Navbar
114+
title="พร้อม actions"
115+
items={demoItems}
116+
pathname="/components"
117+
>
118+
<Inline gap="xs" align="center">
119+
<Button variant="ghost" size="icon" aria-label="แจ้งเตือน">
120+
<Bell />
121+
</Button>
122+
<Button variant="outline" size="sm">
123+
<LogIn />
124+
เข้าสู่ระบบ
125+
</Button>
126+
</Inline>
127+
</Navbar>
128+
</div>
129+
),
130+
code: `<Navbar
131+
title="พร้อม actions"
132+
items={navItems}
133+
pathname={pathname}
134+
>
135+
<Inline gap="xs" align="center">
136+
<Button variant="ghost" size="icon" aria-label="แจ้งเตือน">
137+
<Bell />
138+
</Button>
139+
<Button variant="outline" size="sm">
140+
<LogIn />
141+
เข้าสู่ระบบ
142+
</Button>
143+
</Inline>
144+
</Navbar>`,
145+
},
106146
]}
107147
props={[
108148
{ prop: "title", type: "string", required: true, description: "ชื่อระบบที่แสดงข้างโลโก้ (คลิกได้ร่วมกับโลโก้เมื่อเปิด home link)" },
109149
{ prop: "items", type: "NavbarItem[]", required: true, description: "รายการลิงก์ในเมนู" },
150+
{
151+
prop: "children",
152+
type: "ReactNode",
153+
description:
154+
"คอมโพเนนต์ใด ๆ ที่จะแสดงทางขวาของลิงก์บนเดสก์ท็อป และด้านบนของดรอปดาวน์บนมือถือ (เช่น ปุ่ม login, ตัวแสดงแจ้งเตือน, ตัวสลับธีม) — เมื่อ navbar เป็น dark variant children จะถูกห่อด้วย .dark scope ให้ token สีตรงกับพื้นหลังเข้มอัตโนมัติ",
155+
},
110156
{
111157
prop: "home",
112158
type: "NavbarHome | false",

apps/docs/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/codeblock.tsx","./src/components/componentpage.tsx","./src/components/docslayout.tsx","./src/components/layout.tsx","./src/components/proptable.tsx","./src/lib/shiki.ts","./src/pages/formspage.tsx","./src/pages/guidelinespage.tsx","./src/pages/homepage.tsx","./src/pages/iconspage.tsx","./src/pages/layoutpage.tsx","./src/pages/patternspage.tsx","./src/pages/tokenspage.tsx","./src/pages/components/accordion.tsx","./src/pages/components/alert.tsx","./src/pages/components/autocomplete.tsx","./src/pages/components/badge.tsx","./src/pages/components/breadcrumb.tsx","./src/pages/components/button.tsx","./src/pages/components/card.tsx","./src/pages/components/catalog.ts","./src/pages/components/checkbox.tsx","./src/pages/components/dialog.tsx","./src/pages/components/dropdown-menu.tsx","./src/pages/components/index.tsx","./src/pages/components/input.tsx","./src/pages/components/label.tsx","./src/pages/components/multi-select.tsx","./src/pages/components/navbar.tsx","./src/pages/components/navigation-menu.tsx","./src/pages/components/popover.tsx","./src/pages/components/progress.tsx","./src/pages/components/radio-group.tsx","./src/pages/components/select.tsx","./src/pages/components/separator.tsx","./src/pages/components/sheet.tsx","./src/pages/components/skeleton.tsx","./src/pages/components/slider.tsx","./src/pages/components/sonner.tsx","./src/pages/components/spinner.tsx","./src/pages/components/stepper.tsx","./src/pages/components/switch.tsx","./src/pages/components/tabs.tsx","./src/pages/components/textarea.tsx"],"version":"5.9.3"}
1+
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/codeblock.tsx","./src/components/componentpage.tsx","./src/components/docslayout.tsx","./src/components/githubstar.tsx","./src/components/layout.tsx","./src/components/proptable.tsx","./src/lib/shiki.ts","./src/pages/formspage.tsx","./src/pages/guidelinespage.tsx","./src/pages/homepage.tsx","./src/pages/iconspage.tsx","./src/pages/layoutpage.tsx","./src/pages/patternspage.tsx","./src/pages/tokenspage.tsx","./src/pages/components/accordion.tsx","./src/pages/components/alert.tsx","./src/pages/components/autocomplete.tsx","./src/pages/components/badge.tsx","./src/pages/components/breadcrumb.tsx","./src/pages/components/button.tsx","./src/pages/components/card.tsx","./src/pages/components/catalog.ts","./src/pages/components/checkbox.tsx","./src/pages/components/dialog.tsx","./src/pages/components/dropdown-menu.tsx","./src/pages/components/index.tsx","./src/pages/components/input.tsx","./src/pages/components/label.tsx","./src/pages/components/multi-select.tsx","./src/pages/components/navbar.tsx","./src/pages/components/navigation-menu.tsx","./src/pages/components/popover.tsx","./src/pages/components/progress.tsx","./src/pages/components/radio-group.tsx","./src/pages/components/select.tsx","./src/pages/components/separator.tsx","./src/pages/components/sheet.tsx","./src/pages/components/skeleton.tsx","./src/pages/components/slider.tsx","./src/pages/components/sonner.tsx","./src/pages/components/spinner.tsx","./src/pages/components/stepper.tsx","./src/pages/components/switch.tsx","./src/pages/components/tabs.tsx","./src/pages/components/textarea.tsx"],"version":"5.9.3"}

packages/components/src/components/navbar.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ export interface NavbarHomeLinkRenderProps {
6868
export interface NavbarProps extends React.HTMLAttributes<HTMLElement> {
6969
title: string;
7070
items: NavbarItem[];
71+
/**
72+
* Arbitrary content rendered to the right of the links on desktop,
73+
* and at the top of the mobile dropdown panel (above the items).
74+
*/
75+
children?: React.ReactNode;
7176
/**
7277
* Logo + title link target. Defaults to `{ href: "/", end: true }`.
7378
* Pass `false` to render a non-interactive brand area.
@@ -112,6 +117,7 @@ export function Navbar({
112117
navAriaLabel = "เมนูหลัก",
113118
variant: variantProp,
114119
className,
120+
children,
115121
...props
116122
}: NavbarProps) {
117123
const [menuOpen, setMenuOpen] = React.useState(false);
@@ -231,13 +237,18 @@ export function Navbar({
231237
<Menu className="h-7 w-7" aria-hidden />
232238
)}
233239
</button>
234-
<nav className="hidden md:block" aria-label={navAriaLabel}>
235-
<Inline gap="sm">
236-
{items.map((item) => (
237-
<React.Fragment key={item.href}>{renderNavItem(item, false)}</React.Fragment>
238-
))}
239-
</Inline>
240-
</nav>
240+
<Inline gap="md" align="center" className="hidden md:flex">
241+
<nav aria-label={navAriaLabel}>
242+
<Inline gap="sm">
243+
{items.map((item) => (
244+
<React.Fragment key={item.href}>{renderNavItem(item, false)}</React.Fragment>
245+
))}
246+
</Inline>
247+
</nav>
248+
{children ? (
249+
<div className={cn("contents", !isLight && "dark")}>{children}</div>
250+
) : null}
251+
</Inline>
241252
</Inline>
242253
</Container>
243254
<nav
@@ -253,8 +264,11 @@ export function Navbar({
253264
)}
254265
>
255266
<div className="overflow-hidden">
256-
<Container className="max-md:pb-2 max-md:pt-1 md:pb-4 md:pt-2">
267+
<Container className="max-md:py-4 md:pb-4 md:pt-2">
257268
<Stack gap="xs">
269+
{children ? (
270+
<div className={cn(!isLight && "dark")}>{children}</div>
271+
) : null}
258272
{items.map((item) => (
259273
<React.Fragment key={item.href}>{renderNavItem(item, true)}</React.Fragment>
260274
))}

packages/components/src/components/ui/button.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export const buttonVariants = cva(
2020
),
2121
outline: cn(
2222
"border border-secondary bg-background text-secondary",
23+
"dark:border-foreground dark:bg-transparent dark:text-foreground",
2324
"hover:border-transparent hover:bg-gradient-secondary-button hover:text-secondary-foreground hover:shadow-md",
25+
"dark:hover:border-transparent dark:hover:text-secondary-foreground",
2426
gradientButton,
2527
"active:brightness-95",
2628
),
@@ -29,7 +31,10 @@ export const buttonVariants = cva(
2931
gradientButton,
3032
"active:brightness-95",
3133
),
32-
ghost: "transition-colors hover:bg-secondary/10 hover:text-secondary",
34+
ghost: cn(
35+
"transition-colors hover:bg-secondary/10 hover:text-secondary",
36+
"dark:hover:bg-foreground/10 dark:hover:text-foreground",
37+
),
3338
link: "text-primary underline-offset-4 transition-colors hover:underline",
3439
},
3540
size: {

0 commit comments

Comments
 (0)