Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 71 additions & 11 deletions apps/www/src/app/examples/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
IconButton,
Indicator,
InputField,
Navbar,
Popover,
RangePicker,
Search,
Expand Down Expand Up @@ -153,6 +154,76 @@ const Page = () => {
<!-- -->
/* comment */ /* ---------- __ */`}</code>

{/* Navbar Examples */}
<Text
size='large'
weight='medium'
style={{ marginTop: '32px', marginBottom: '16px' }}
>
Navbar Examples
</Text>

<Flex direction='column' gap={4} style={{ maxWidth: '100%' }}>
<Flex direction='column' gap={2}>
<Text size='small'>Basic Navbar:</Text>
<Navbar>
<Navbar.Start>
<Text size='regular' weight='medium'>
Explore
</Text>
</Navbar.Start>
<Navbar.End>
<Search
placeholder='Search an AOI'
value={search1}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setSearch1(e.target.value)
}
onClear={() => setSearch1('')}
size='small'
style={{ width: '200px' }}
/>
<Button
variant='outline'
size='small'
leadingIcon={<FilterIcon />}
>
Draw AOI
</Button>
<Button
variant='outline'
size='small'
leadingIcon={<OrganizationIcon />}
>
Upload AOI
</Button>
</Navbar.End>
</Navbar>
</Flex>

<Flex direction='column' gap={2}>
<Text size='small'>Sticky Navbar:</Text>
<Navbar sticky>
<Navbar.Start>
<Text size='regular' weight='medium'>
Sticky Navigation
</Text>
</Navbar.Start>
<Navbar.End>
<Button variant='ghost' size='small'>
Home
</Button>
<Button variant='ghost' size='small'>
About
</Button>
<Button variant='ghost' size='small'>
Contact
</Button>
</Navbar.End>
</Navbar>
</Flex>
</Flex>

<Flex direction='column' gap={4} style={{ maxWidth: '550px' }}>
<Search
placeholder='Default large search'
Expand Down Expand Up @@ -1439,17 +1510,6 @@ const Page = () => {
<Text size='small'>
You can filter team members by:
</Text>
<ul style={{ margin: 0, paddingLeft: '16px' }}>
<li>
<Text size='small'>Name</Text>
</li>
<li>
<Text size='small'>Role</Text>
</li>
<li>
<Text size='small'>Department</Text>
</li>
</ul>
</Flex>
</Popover.Content>
</Popover>
Expand Down
137 changes: 137 additions & 0 deletions apps/www/src/content/docs/components/navbar/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
'use client';

export const preview = {
type: 'code',
code: `
<Navbar>
<Navbar.Start>
<Text size="regular" weight="medium">Explore</Text>
</Navbar.Start>
<Navbar.End>
<Search placeholder="Search an AOI" size="small" style={{ width: '200px' }} />
<Button variant="outline" size="small" color="neutral" leadingIcon={<FilterIcon />}>
Draw AOI
</Button>
<Button variant="outline" size="small" color="neutral" leadingIcon={<OrganizationIcon />}>
Upload AOI
</Button>
</Navbar.End>
</Navbar>`
};

export const stickyDemo = {
type: 'code',
tabs: [
{
name: 'Default',
code: `
<Navbar>
<Navbar.Start>
<Text size="regular" weight="medium">Navigation</Text>
</Navbar.Start>
<Navbar.End>
<Button variant="ghost" size="small">Home</Button>
<Button variant="ghost" size="small">About</Button>
<Button variant="ghost" size="small">Contact</Button>
</Navbar.End>
</Navbar>`
},
{
name: 'Sticky',
code: `
<Navbar sticky>
<Navbar.Start>
<Text size="regular" weight="medium">Navigation</Text>
</Navbar.Start>
<Navbar.End>
<Button variant="ghost" size="small">Home</Button>
<Button variant="ghost" size="small">About</Button>
<Button variant="ghost" size="small">Contact</Button>
</Navbar.End>
</Navbar>`
}
]
};

export const sectionsDemo = {
type: 'code',
tabs: [
{
name: 'Start Only',
code: `
<Navbar>
<Navbar.Start>
<Text size="regular" weight="medium">Brand Name</Text>
</Navbar.Start>
</Navbar>`
},
{
name: 'End Only',
code: `
<Navbar>
<Navbar.End>
<Button variant="outline" size="small">Login</Button>
<Button size="small">Sign Up</Button>
</Navbar.End>
</Navbar>`
},
{
name: 'Both Sections',
code: `
<Navbar>
<Navbar.Start>
<Text size="regular" weight="medium">Explore</Text>
</Navbar.Start>
<Navbar.End>
<Search placeholder="Search..." size="small" style={{ width: '200px' }} />
<Button variant="outline" size="small">Action</Button>
</Navbar.End>
</Navbar>`
}
]
};

export const accessibilityDemo = {
type: 'code',
tabs: [
{
name: 'Custom aria-label',
code: `
<Navbar aria-label="Primary navigation">
<Navbar.Start>
<Text size="regular" weight="medium">Brand</Text>
</Navbar.Start>
<Navbar.End>
<Button size="small">Menu</Button>
</Navbar.End>
</Navbar>`
},
{
name: 'With aria-labelledby',
code: `
<>
<Navbar aria-labelledby="nav-heading">
<Navbar.Start>
<Text size="regular" weight="medium">Brand</Text>
</Navbar.Start>
<Navbar.End>
<Button size="small">Menu</Button>
</Navbar.End>
</Navbar>
</>`
},
{
name: 'Section Labels',
code: `
<Navbar>
<Navbar.Start aria-label="Brand and navigation links">
<Text size="regular" weight="medium">Brand</Text>
</Navbar.Start>
<Navbar.End aria-label="User actions and settings">
<Button size="small">Settings</Button>
<Button size="small">Profile</Button>
</Navbar.End>
</Navbar>`
}
]
};
82 changes: 82 additions & 0 deletions apps/www/src/content/docs/components/navbar/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Navbar
description: A horizontal navigation bar component with flexible left and right sections for building site headers and navigation.
---

import {
preview,
stickyDemo,
sectionsDemo,
accessibilityDemo,
} from "./demo.ts";

<Demo data={preview} />

## Usage

```tsx
import { Navbar } from "@raystack/apsara";
```

## Component Structure

The Navbar component uses a composite pattern, providing modular sub-components for flexible navigation layouts:

- `Navbar` - Root container that wraps the navigation bar
- `Navbar.Start` - Left section container for brand, logo, or primary navigation
- `Navbar.End` - Right section container for actions, search, or secondary navigation

## Navbar Props

<auto-type-table path="./props.ts" name="NavbarRootProps" />

### Navbar.Start Props

The start section is a container component that accepts all `div` props. It's commonly used for brand logos, primary navigation links, or page titles.

<auto-type-table path="./props.ts" name="NavbarStartProps" />

### Navbar.End Props

The end section is a container component that accepts all `div` props. It's commonly used for search inputs, action buttons, user menus, or secondary navigation.

<auto-type-table path="./props.ts" name="NavbarEndProps" />

## Examples

### Sticky Navigation

The Navbar can be made sticky to remain visible at the top of the viewport when scrolling.

<Demo data={stickyDemo} />

### Section Layouts

You can use either or both sections depending on your needs. The sections automatically position themselves with proper spacing.

<Demo data={sectionsDemo} />

### Accessibility

The Navbar supports custom ARIA labels for better screen reader support. You can provide descriptive labels for the entire navbar or individual sections.

<Demo data={accessibilityDemo} />

## Accessibility

The Navbar implements the following accessibility features:

- Proper ARIA roles and attributes
- `role="navigation"` for the main navbar
- `role="group"` for Start and End sections when `aria-label` is provided
- Customizable `aria-label` and `aria-labelledby` support

- Semantic HTML
- Uses `<nav>` element for proper navigation landmark
- Maintains proper heading hierarchy when using `aria-labelledby`

- Screen reader support
- Meaningful labels for all sections
- Clear structure for assistive technologies
- Support for linking to visible headings via `aria-labelledby`

37 changes: 37 additions & 0 deletions apps/www/src/content/docs/components/navbar/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export interface NavbarRootProps {
/**
* Makes the navbar stick to the top of the viewport when scrolling.
* @default false
*/
sticky?: boolean;

/**
* Accessible label for the navigation.
* Use this to provide a description of the navbar's purpose.
*/
'aria-label'?: string;

/**
* ID of an element that labels the navigation. Use this when you have a visible heading
* that describes the navbar.
*/
'aria-labelledby'?: string;
}

export interface NavbarStartProps {
/**
* Accessible label for the start section. Use this to describe the purpose
* of the content in the start section (e.g., "Brand and navigation links").
* When provided, the section will have `role="group"`.
*/
'aria-label'?: string;
}

export interface NavbarEndProps {
/**
* Accessible label for the end section. Use this to describe the purpose
* of the content in the end section (e.g., "User actions and settings").
* When provided, the section will have `role="group"`.
*/
'aria-label'?: string;
}
Loading