Chandra
view release on metacpan or search on metacpan
lib/Chandra/Nav.pm view on Meta::CPAN
}));
}
$li->add_child($a);
$ul->add_child($li);
}
$nav->add_child($ul);
$wrap->add_child($nav);
return $wrap->render;
}
sub on_navigate {
my ($self, $route) = @_;
nav__active $self, $route;
my $app = $self->_app;
if ($app && $app->can('navigate')) {
$app->navigate($route);
}
$self->update;
}
sub on_toggle {
my ($self) = @_;
nav_collapsed $self, !nav_collapsed($self);
$self->update;
}
sub css {
return <<'CSS';
.chandra-nav-wrap { position: relative; display: flex; flex-direction: column; flex-shrink: 0; height: 100%; }
.chandra-nav { font-family: inherit; }
.chandra-nav-list { list-style: none; margin: 0; padding: 0; }
/* Sidebar */
.chandra-nav-sidebar {
width: 220px;
min-width: 220px;
background: var(--chandra-surface, #f5f5f5);
border-right: 1px solid var(--chandra-border, #e0e0e0);
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 8px 0;
transition: width 0.2s, min-width 0.2s;
}
.chandra-nav-sidebar.chandra-nav-collapsed {
width: 52px;
min-width: 52px;
padding: 8px 0;
}
.chandra-nav-sidebar.chandra-nav-collapsed .chandra-nav-label,
.chandra-nav-sidebar.chandra-nav-collapsed .chandra-nav-badge { display: none; }
.chandra-nav-sidebar.chandra-nav-collapsed .chandra-nav-link {
justify-content: center;
padding: 10px 0;
}
.chandra-nav-sidebar .chandra-nav-link {
display: flex; align-items: center; gap: 10px;
padding: 10px 16px; color: var(--chandra-text, #212121);
text-decoration: none; cursor: pointer;
transition: background 0.15s;
}
.chandra-nav-sidebar .chandra-nav-link:hover { background: var(--chandra-hover, #e8e8e8); }
.chandra-nav-sidebar .chandra-nav-active .chandra-nav-link {
background: var(--chandra-selected, #e3f2fd);
color: var(--chandra-primary, #2196F3);
font-weight: 600;
}
.chandra-nav-separator { border-top: 1px solid var(--chandra-border, #e0e0e0); margin: 4px 12px; }
.chandra-nav-toggle {
width: 52px; border: none;
background: transparent;
font-size: 1.1em; cursor: pointer;
color: var(--chandra-text-muted, #757575);
flex-shrink: 0;
text-align: center;
}
.chandra-nav-icon { font-size: 1.1em; flex-shrink: 0; width: 24px; text-align: center; }
.chandra-nav-badge {
margin-left: auto; background: var(--chandra-primary, #2196F3);
color: #fff; padding: 1px 8px; border-radius: 10px; font-size: 0.75em;
}
/* Topbar */
.chandra-nav-topbar {
background: var(--chandra-surface, #f5f5f5);
border-bottom: 1px solid var(--chandra-border, #e0e0e0);
padding: 0 8px;
}
.chandra-nav-topbar .chandra-nav-list { display: flex; gap: 0; }
.chandra-nav-topbar .chandra-nav-link {
display: flex; align-items: center; gap: 6px;
padding: 12px 16px; color: var(--chandra-text, #212121);
text-decoration: none; cursor: pointer;
border-bottom: 2px solid transparent;
transition: border-color 0.15s, color 0.15s;
}
.chandra-nav-topbar .chandra-nav-link:hover { color: var(--chandra-primary, #2196F3); }
.chandra-nav-topbar .chandra-nav-active .chandra-nav-link {
color: var(--chandra-primary, #2196F3);
border-bottom-color: var(--chandra-primary, #2196F3);
font-weight: 600;
}
.chandra-nav-topbar .chandra-nav-separator { display: none; }
CSS
}
1;
__END__
=head1 NAME
Chandra::Nav - Navigation component (sidebar/topbar)
=head1 SYNOPSIS
use Chandra::Nav;
my $nav = Chandra::Nav->new(
type => 'sidebar',
items => [
{ label => 'Dashboard', icon => "\x{1F4CA}", route => '/' },
{ label => 'Users', icon => "\x{1F465}", route => '/users' },
{ separator => 1 },
{ label => 'Settings', icon => "\x{2699}", route => '/settings' },
],
collapsible => 1,
);
$app->theme('dark');
$app->css(Chandra::Nav->css);
$app->set_content('<div style="display:flex;height:100vh;"><div id="nav"></div><div id="content" style="flex:1;padding:20px;"></div></div>');
$nav->mount($app, '#nav');
=head1 SEE ALSO
L<Chandra::Tabs>, L<Chandra::Component>, L<Chandra::App>
=cut
( run in 0.481 second using v1.01-cache-2.11-cpan-140bd7fdf52 )