Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dropdown to navbar #342

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
37 changes: 37 additions & 0 deletions demo/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,43 @@ def demo_page(*components: AnyComponent, title: str | None = None) -> list[AnyCo
on_click=GoToEvent(url='/forms/login'),
active='startswith:/forms',
),
c.LinkListDropdown(
name='All',
links=[
c.Link(
components=[c.Text(text='Components')],
on_click=GoToEvent(url='/components'),
active='startswith:/components',
),
c.Link(
components=[c.Text(text='Tables')],
on_click=GoToEvent(url='/table/cities'),
active='startswith:/table',
),
c.Link(
components=[c.Text(text='Auth')],
on_click=GoToEvent(url='/auth/login/password'),
active='startswith:/auth',
),
[
c.Link(
components=[c.Text(text='Forms Login')],
on_click=GoToEvent(url='/forms/login'),
active='startswith:/forms',
),
c.Link(
components=[c.Text(text='Forms Select')],
on_click=GoToEvent(url='/forms/select'),
active='startswith:/forms',
),
c.Link(
components=[c.Text(text='Forms Big')],
on_click=GoToEvent(url='/forms/big'),
active='startswith:/forms',
),
],
],
),
],
),
c.Page(
Expand Down
18 changes: 14 additions & 4 deletions demo/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_api_root(client: TestClient):
{
'title': 'FastUI Demo',
'titleEvent': {'url': '/', 'type': 'go-to'},
'startLinks': IsList(length=4),
'startLinks': IsList(length=5),
'endLinks': [],
'type': 'Navbar',
},
Expand Down Expand Up @@ -61,9 +61,19 @@ def get_menu_links():
r = client.get('/api/')
assert r.status_code == 200
data = r.json()
for link in data[1]['startLinks']:
url = link['onClick']['url']
yield pytest.param(f'/api{url}', id=url)
for navitem in data[1]['startLinks']:
if navitem['type'] == 'Link':
url = navitem['onClick']['url']
yield pytest.param(f'/api{url}', id=url)
elif navitem['type'] == 'LinkListDropdown':
for link in navitem['links']:
if isinstance(link, list):
for inner_link in link:
url = inner_link['onClick']['url']
yield pytest.param(f'/api{url}', id=url)
else:
url = link['onClick']['url']
yield pytest.param(f'/api{url}', id=url)


@pytest.mark.parametrize('url', get_menu_links())
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 32 additions & 5 deletions src/npm-fastui-bootstrap/src/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FC } from 'react'
import { FC, Fragment } from 'react'
import { components, useClassName, models } from 'fastui'
import BootstrapNavbar from 'react-bootstrap/Navbar'
import NavDropdown from 'react-bootstrap/NavDropdown'

export const Navbar: FC<models.Navbar> = (props) => {
const startLinks = props.startLinks.map((link) => {
Expand All @@ -18,16 +19,16 @@ export const Navbar: FC<models.Navbar> = (props) => {
<BootstrapNavbar.Toggle aria-controls="navbar-collapse" />
<BootstrapNavbar.Collapse id="navbar-collapse">
<ul className="navbar-nav me-auto">
{startLinks.map((link, i) => (
{startLinks.map((item, i) => (
<li key={i} className="nav-item">
<components.LinkComp {...link} />
<NavBarItem {...item} />
</li>
))}
</ul>
<ul className="navbar-nav ms-auto">
{endLinks.map((link, i) => (
{endLinks.map((item, i) => (
<li key={i} className="nav-item">
<components.LinkComp {...link} />
<NavBarItem {...item} />
</li>
))}
</ul>
Expand All @@ -37,6 +38,32 @@ export const Navbar: FC<models.Navbar> = (props) => {
)
}

const NavBarItem = (props: models.Link | models.LinkListDropdown) => {
if (props.type === 'LinkListDropdown') {
return (
<NavDropdown title={props.name}>
{props.links.map((link, j) =>
Array.isArray(link) ? (
link.map((innerLink, jj) => (
<Fragment key={`${j}-${jj}`}>
<components.LinkComp {...innerLink} className="dropdown-item" />
{j !== props.links.length - 1 && <NavDropdown.Divider />}
</Fragment>
))
) : (
<Fragment key={`${j}`}>
<components.LinkComp {...link} className="dropdown-item" />
{j !== props.links.length - 1 && <NavDropdown.Divider />}
</Fragment>
),
)}
</NavDropdown>
)
} else {
return <components.LinkComp {...props} />
}
}

const NavbarTitle = (props: models.Navbar) => {
const { title, titleEvent } = props
const className = useClassName(props, { el: 'title' })
Expand Down
24 changes: 18 additions & 6 deletions src/npm-fastui/src/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,24 @@ export const NavbarComp = (props: Navbar) => {
<nav className={useClassName(props)}>
<div className={useClassName(props, { el: 'contents' })}>
<NavbarTitle {...props} />
{startLinks.map((link, i) => (
<LinkComp key={i} {...link} />
))}
{endLinks.map((link, i) => (
<LinkComp key={i} {...link} />
))}
{startLinks.map((link, i) =>
link.type === 'LinkListDropdown' ? (
<div key={i} className="alert-message">
{'`Note: dropdowns for Navbars are not implemented by pure FastUI.`'}
</div>
) : (
<LinkComp key={i} {...link} />
),
)}
{endLinks.map((link, i) =>
link.type === 'LinkListDropdown' ? (
<div key={i} className="alert-message">
{'`Note: dropdowns for Navbars are not implemented by pure FastUI.`'}
</div>
) : (
<LinkComp key={i} {...link} />
),
)}
</div>
</nav>
)
Expand Down
14 changes: 12 additions & 2 deletions src/npm-fastui/src/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,21 @@ export interface LinkList {
export interface Navbar {
title?: string
titleEvent?: PageEvent | GoToEvent | BackEvent | AuthEvent
startLinks: Link[]
endLinks: Link[]
startLinks: (Link | LinkListDropdown)[]
endLinks: (Link | LinkListDropdown)[]
className?: ClassName
type: 'Navbar'
}
/**
* List of Link components for dropdowns used in the `Navbar` component.
*/
export interface LinkListDropdown {
name: string
links: (Link | Link[])[]
mode: 'navbar'
className?: ClassName
type: 'LinkListDropdown'
}
/**
* Footer component.
*/
Expand Down
23 changes: 21 additions & 2 deletions src/python-fastui/fastui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,25 @@ class LinkList(BaseModel, extra='forbid'):
"""The type of the component. Always 'LinkList'."""


class LinkListDropdown(BaseModel, extra='forbid'):
"""List of Link components for dropdowns used in the `Navbar` component."""

name: str
"""Name of the link list."""

links: _t.List[_t.Union[Link, _t.List[Link]]]
"""List of links to render."""

mode: _t.Literal['navbar'] = 'navbar'
"""Mode can only be navbar due its sole purpose of serving as dropdown."""

class_name: _class_name.ClassNameField = None
"""Optional class name to apply to the link list's HTML component."""

type: _t.Literal['LinkListDropdown'] = 'LinkListDropdown'
"""The type of the component. Always 'LinkListDropdown'."""


class Navbar(BaseModel, extra='forbid'):
"""Navbar component used for moving between pages."""

Expand All @@ -290,10 +309,10 @@ class Navbar(BaseModel, extra='forbid'):
title_event: _t.Union[events.AnyEvent, None] = None
"""Optional event to trigger when the title is clicked. Often used to navigate to the home page."""

start_links: _t.List[Link] = []
start_links: _t.List[_t.Union[Link, LinkListDropdown]] = []
"""List of links to render at the start of the navbar."""

end_links: _t.List[Link] = []
end_links: _t.List[_t.Union[Link, LinkListDropdown]] = []
"""List of links to render at the end of the navbar."""

class_name: _class_name.ClassNameField = None
Expand Down