# `hb-sidenav-link` (sidenav-link)

**Category:** layout  
**Tags:** layout, navigation  
**Package:** `@htmlbricks/hb-sidenav-link`

## Overview

`hb-sidenav-link` is a single sidebar navigation row for hierarchical menus. It renders a Bootstrap Icons label row, an optional Bulma-style tag badge (outlined pill: transparent fill, border and text from the tag color), and either:

- a **leaf** link that dispatches `pageChange` when the user activates it, or  
- a **parent** row with `subLinks` that expands to show nested links.

Active styling applies when the current page key (`navpage`) matches a link key, or when the `selected` flag is enabled. The component is intended for use inside `hb-sidebar-desktop` lists (and shares the `INavLink` shape with related layout components).

Bootstrap Icons are loaded from the component (`bootstrap-icons` CSS on jsDelivr). Icon names are the `bi-*` suffix only (for example `house-door` → `bi-house-door`).

## Custom element

```html
<hb-sidenav-link …></hb-sidenav-link>
```

## Props (TypeScript `Component`)

| Property    | Type        | Required | Notes |
|------------|-------------|----------|--------|
| `navlink`  | `INavLink`  | yes      | From HTML, pass a **JSON string** (see below). |
| `navpage`  | `string`    | no       | Current page key; drives active row and expanded groups. |
| `selected` | `boolean`   | no       | When true, forces the “current page” appearance. From HTML use string encodings (see [Attributes](#attributes-snake_case-string-values-in-html)). |
| `id`       | `string`    | no       | Optional identifier (typed on the component API). |
| `style`    | `string`    | no       | Optional inline style (typed on the component API). |

## `INavLink` shape

Used for the root `navlink` and, recursively, for each entry in `subLinks`.

| Field       | Type           | Required | Description |
|------------|----------------|----------|-------------|
| `key`      | `string`       | yes      | Stable id for the link; sent as `page` in `pageChange` for leaf clicks. |
| `label`    | `string`       | yes      | Visible label. |
| `icon`     | `string`       | no       | Bootstrap Icons name without the `bi-` prefix. |
| `group`    | `string`       | no       | Present in the shared nav typings for interoperability. |
| `badge`    | object         | no       | `text` (string), optional Bulma modifier classes `class` and `classcolor` (for example `is-rounded`, `is-light`). |
| `subLinks` | `INavLink[]`   | no       | If non-empty, the row becomes an expandable group instead of a single leaf. |
| `active`   | `boolean`      | no       | Present on the shared type; not read by this component’s markup. |
| `open`     | `boolean`      | no       | When `navlink` is provided as a JSON string, `open: true` initializes the group as expanded. |

### Badges

`badge` supports:

- `text` — badge label.  
- `class` — optional Bulma tag class (defaults to `is-rounded` in the template).  
- `classcolor` — optional color modifier (defaults to `is-light`).

## Attributes (snake_case; string values in HTML)

Reflects the web component contract: attributes are strings.

| Attribute   | Required | Description |
|------------|----------|-------------|
| `navlink`  | yes      | JSON string representing `INavLink`. |
| `navpage`  | no       | Current page key string. |
| `selected` | no       | Boolean-as-string: `yes` / `no` per project conventions. The implementation also treats the literal strings `true` / `yes` as true, **and treats an empty string as true**; any other value becomes false unless already boolean true inside the app. Prefer explicit `yes` or `no` from HTML. |
| `id`       | no       | String. |
| `style`    | no       | String. |

If `navlink` arrives as a string, the component parses it as JSON and applies `open` from the parsed object when `open === true`.

## Events

| Event        | `detail`              | When it fires |
|-------------|------------------------|----------------|
| `pageChange` | `{ page: string }`    | User activates a **non-active** leaf: either the root row (no `subLinks`) or a sub-link whose `key` does not match `navpage` and is not forced selected. **Active** rows (matching `navpage` or `selected`) are static for accessibility (`aria-current="page"`) and do **not** dispatch `pageChange` on click. |

Listen in plain DOM:

```js
document.querySelector('hb-sidenav-link').addEventListener('pageChange', (e) => {
  console.log(e.detail.page);
});
```

## Behavior

### Leaf row (no `subLinks`)

- If `navlink.key === navpage` **or** `selected` is true: one full-width “current” button (link styling), no click navigation event.  
- Otherwise: ghost button; click dispatches `pageChange` with `detail.page === navlink.key`.

### Parent row (non-empty `subLinks`)

- The parent row is a control that toggles expand/collapse and shows **▼** when open or when any child `key` equals `navpage`, otherwise **►**.  
- The nested list is shown when the group is open **or** when some child has `key === navpage`.  
- Each child follows the same active vs ghost rules as a leaf; inactive children dispatch `pageChange` with `detail.page ===` that child’s `key`.

### Layout

Labels use a flexible middle column; a fixed-width trail column (`--hb-sidenav-trail-width`) keeps titles aligned when some rows omit a badge or use the expand chevron. Long badge text is truncated with an ellipsis in the component styles.

## Styling (Bulma and component variables)

Buttons and tags live in the **shadow root**. Theme with public **`--bulma-*`** variables on `body`, `:root`, or an ancestor; see [Bulma CSS variables](https://bulma.io/documentation/features/css-variables/). Additional tokens are listed in `extra/docs.ts`.

| CSS custom property | Default (from docs) | Purpose |
|--------------------|---------------------|---------|
| `--bulma-link` | `#485fc7` | Active row stripe, chevrons, and interactive accents. |
| `--bulma-link-text` | `#ffffff` | Text color on the active row background mix. |
| `--bulma-text` | `#363636` | Default label color for inactive rows. |
| `--bulma-scheme-main` | `#ffffff` | Row surface behind ghost buttons. |
| `--bulma-radius` | `0.375rem` | Row rounding for list items. |
| `--hb-sidenav-trail-width` | `3.5rem` | Width of the right column (badges / expand chevron); keeps labels aligned when some rows omit a badge. |

### CSS parts

| Part | Description |
|------|-------------|
| `li` | Bulma menu list row (`li`) wrapping the ghost or link button, label column, badge or chevron trail, and nested sub-links. |

**Slots:** none (`htmlSlots` is empty).

## Examples

### Simple leaf

```html
<hb-sidenav-link
  navpage="home"
  navlink='{"label":"Home","key":"home","icon":"house-door"}'
></hb-sidenav-link>
```

### Leaf with badge

```html
<hb-sidenav-link
  navlink='{"label":"Home","key":"home","icon":"house-door","badge":{"text":"3","classcolor":"is-danger"}}'
></hb-sidenav-link>
```

### Expandable group with sub-links

```html
<hb-sidenav-link
  navpage="dash"
  navlink='{"label":"Workspace","key":"workspace","icon":"folder2-open","subLinks":[{"label":"Dashboard","key":"dash","icon":"speedometer2"},{"label":"Projects","key":"projects","icon":"kanban"}]}'
></hb-sidenav-link>
```

### Force selected appearance

```html
<hb-sidenav-link
  selected="yes"
  navlink='{"label":"Home","key":"home","icon":"house-door"}'
></hb-sidenav-link>
```

## Related types

`INavLink` matches the structure used by **`hb-offcanvas`** and **`hb-sidebar-desktop`** for `navlinks` arrays, so the same serialized objects can be reused across layout components.

## License

Package metadata references **Apache-2.0** (see `LICENSE.md` in the published package when consuming from npm).
