# SidebarCustomize

> Lets users reorder and hide sidebar items - a persisted layout hook, drag-to-reorder in the sidebar, and an accessible customize dialog.

Source: https://docs.nasaqui.com/components/sidebar-layout

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/sidebar-layout.json
```

Personalisation for the sidebar. Three pieces share one `SidebarLayout` object per list of items:

- **`useSidebarLayout(storageKey, ids, { defaultHidden? })`**: order and hidden ids, persisted in `localStorage`. A saved value of the wrong shape, or blocked storage, falls back to the defaults. `defaultHidden` lists ids that start switched off but stay available in Customize (for example, products the user hasn't pinned). Ids added later start hidden if they are in it.
- **`SidebarSortable` / `SidebarSortableItem`**: drag items into a new order right in the sidebar.
- **`SidebarCustomize`**: a dialog with a drag handle and a show/hide switch per item. This is the accessible path: the handle also reorders with the keyboard.

Nasaq does not know your routes. You describe items (`id`, `label`, `icon`), keep the render function
for each, and let the layout decide order and visibility.

## When to use

- Products with many nav items where users want their own order or want to hide rarely used ones.
- A "Favourites"/"Pinned" group the user arranges.

## When not to use

- A fixed sidebar with few items: render `SidebarItem`s directly in [`AppShell`](https://docs.nasaqui.com/components/app-shell).
- Reordering arbitrary content lists (boards, tables): this is scoped to the sidebar's vertical lists.
- Switching products or workspaces: use [`ProductSwitcher`](https://docs.nasaqui.com/components/product-switcher) or [`WorkspaceSwitcher`](https://docs.nasaqui.com/components/workspace-switcher).

## Import

```tsx
import {
  useSidebarLayout, SidebarSortable, SidebarSortableItem, SidebarCustomize,
  type SidebarLayout, type SidebarCustomizeSection,
} from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import {
  AppShell, Sidebar, SidebarContent, SidebarGroup, SidebarItem, SidebarFooter, Button,
  SidebarCustomize, SidebarSortable, SidebarSortableItem, useSidebarLayout,
} from "@fadymondy/nasaq/web";
import { Inbox, LayoutDashboard, ListTodo } from "lucide-react";
import { type ReactNode, useState } from "react";

type Entry = { id: string; label: string; icon: ReactNode; required?: boolean; href: string };

const ENTRIES: Entry[] = [
  { id: "dashboard", label: "Dashboard", icon: <LayoutDashboard />, required: true, href: "/" },
  { id: "inbox", label: "Inbox", icon: <Inbox />, href: "/inbox" },
  { id: "issues", label: "My issues", icon: <ListTodo />, href: "/issues" },
];

function AppSidebar() {
  const [customizing, setCustomizing] = useState(false);
  const layout = useSidebarLayout("my-app-nav", ENTRIES.map((e) => e.id));
  const byId = new Map(ENTRIES.map((e) => [e.id, e]));

  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarSortable ids={layout.visible} onMove={layout.move}>
            {layout.visible.map((id) => {
              const entry = byId.get(id)!;
              return (
                <SidebarSortableItem key={id} id={id}>
                  <SidebarItem href={entry.href} icon={entry.icon}>{entry.label}</SidebarItem>
                </SidebarSortableItem>
              );
            })}
          </SidebarSortable>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter>
        <Button variant="ghost" size="sm" onClick={() => setCustomizing(true)}>Customize sidebar</Button>
      </SidebarFooter>
      <SidebarCustomize
        open={customizing}
        onOpenChange={setCustomizing}
        sections={[{ id: "main", items: ENTRIES, layout }]}
      />
    </Sidebar>
  );
}

export const Root = ({ children }: { children: ReactNode }) => <AppShell sidebar={<AppSidebar />}>{children}</AppShell>;
```

## Anatomy

```
SidebarSortable                    (DndContext + SortableContext, vertical)
└─ SidebarSortableItem × n         data-slot="sidebar-sortable-item", data-dragging

SidebarCustomize                   Dialog (max-w-md)
├─ header                          title + description
├─ section × n                     <section aria-label={section.label}>, optional <h3>
│  └─ row × n                      <li>: drag handle <button>, icon, label, Switch
├─ live region                     aria-live="polite", sr-only: keyboard-move announcement
└─ footer                          "Reset to default" (ghost) · "Done" (primary)
```

## API

### `useSidebarLayout`

```ts
useSidebarLayout(storageKey: string, ids: readonly string[]): SidebarLayout
```

Pass the default order. Saved state is read after mount (so SSR and the first client render match) and
normalised: ids you removed are dropped, ids you added are appended. Changing `storageKey` or the set of
`ids` reloads.

### `SidebarLayout`

| Field | Type | Description |
| --- | --- | --- |
| `order` | `string[]` | Every id in the user's order. |
| `hidden` | `string[]` | Ids the user switched off. |
| `visible` | `string[]` | `order` without hidden ids: what to render. |
| `move` | `(activeId: string, overId: string) => void` | Moves `activeId` to the position of `overId`. Ignores unknown ids. |
| `setVisible` | `(id: string, visible: boolean) => void` | Show or hide one item. |
| `reset` | `() => void` | Clears storage and restores the default order. |
| `isDefault` | `boolean` | `true` when nothing is hidden and the order matches `ids`. |

### `SidebarSortable`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `ids` | `string[]` | required | The visible ids in order (`layout.visible`). |
| `onMove` | `(activeId: string, overId: string) => void` | required | Pass `layout.move`. |
| `children` | `ReactNode` | required | `SidebarSortableItem`s. |

Mouse drags start after 4px; touch drags after a 250ms press (so the list still scrolls). The click that
ends a drag is suppressed for 100ms so it does not open the link under the pointer. It has no keyboard
sensor by design: keyboard reordering lives in `SidebarCustomize`.

### `SidebarSortableItem`

Extends `ComponentProps<"div">`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | required | Must match an entry in `SidebarSortable`'s `ids`. |

### `SidebarCustomizeItem`

| Field | Type | Description |
| --- | --- | --- |
| `id` | `string` | Same id as in the layout. |
| `label` | `string` | Row text and the switch's accessible name. |
| `icon?` | `ReactNode` | Shown before the label. |
| `required?` | `boolean` | Can be reordered but not hidden (its switch is disabled). |

### `SidebarCustomizeSection`

| Field | Type | Description |
| --- | --- | --- |
| `id` | `string` | Unique section key. |
| `label?` | `string` | Heading and the section's `aria-label`. |
| `items` | `SidebarCustomizeItem[]` | Descriptions for the layout's ids. Ids with no matching item are skipped. |
| `layout` | `SidebarLayout` | The hook result for this list. |

### `SidebarCustomize`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | required | Dialog state. |
| `onOpenChange` | `(open: boolean) => void` | required | |
| `sections` | `SidebarCustomizeSection[]` | required | One per list. "Reset to default" resets all and is disabled when all are default. |
| `labels?` | `{ title?; description?; reset?; done?; reorder?(label); moved?(label, position, total); close? }` | EN/AR by provider locale | See below. |

Default labels follow the provider locale (English, or Arabic for `ar*`; pass `labels` to override). English: title "Customize sidebar", description
"Drag to reorder. Switch items off to hide them.", reset "Reset to default", done "Done", reorder
`Reorder ${label}`, moved `${label}, position ${position} of ${total}`. `close` goes to the dialog's close button.

## Examples

### Two lists in one dialog, Arabic copy

```tsx
import { SidebarCustomize, useSidebarLayout } from "@fadymondy/nasaq/web";

const MAIN = [{ id: "home", label: "الرئيسية", required: true }, { id: "inbox", label: "الوارد" }];
const TOOLS = [{ id: "time", label: "تتبع الوقت" }, { id: "teams", label: "الفرق" }];

export function Customize({ open, onOpenChange }: { open: boolean; onOpenChange: (o: boolean) => void }) {
  const main = useSidebarLayout("app-nav-main", MAIN.map((i) => i.id));
  const tools = useSidebarLayout("app-nav-tools", TOOLS.map((i) => i.id));
  return (
    <SidebarCustomize
      open={open}
      onOpenChange={onOpenChange}
      sections={[
        { id: "main", items: MAIN, layout: main },
        { id: "tools", label: "الأدوات", items: TOOLS, layout: tools },
      ]}
      labels={{
        title: "تخصيص الشريط الجانبي",
        description: "اسحب لإعادة الترتيب، وأوقف العناصر لإخفائها.",
        reset: "استعادة الافتراضي",
        done: "تم",
        close: "إغلاق",
        reorder: (label) => `إعادة ترتيب ${label}`,
        moved: (label, n, total) => `${label}، الموضع ${n} من ${total}`,
      }}
    />
  );
}
```

### Open it from the user menu

```tsx
import { DropdownMenuItem, UserMenu } from "@fadymondy/nasaq/web";
import { SlidersHorizontal } from "lucide-react";

export const Menu = ({ onCustomize }: { onCustomize: () => void }) => (
  <UserMenu user={{ name: "Fady Mondy", email: "hello@example.com" }}>
    <DropdownMenuItem onClick={onCustomize}>
      <SlidersHorizontal />
      Customize sidebar
    </DropdownMenuItem>
  </UserMenu>
);
```

## Accessibility

Keys on a row's drag handle in `SidebarCustomize` (the handle has `aria-keyshortcuts="ArrowUp ArrowDown Home End"`):

| Key | Action |
| --- | --- |
| `↑` / `↓` | Move the item one place up / down. |
| `Home` / `End` | Move to the first / last position. |
| `Tab` | Handle, then the item's Switch. |
| `Space` on the Switch | Show / hide the item. |
| `Esc` | Close the dialog. |

- After a keyboard move the new place is announced in an `aria-live="polite"` region (`labels.moved`) and focus stays on the handle.
- Each handle is named by `labels.reorder(label)`; each Switch by the item's `label`.
- Drag-to-reorder in the sidebar itself is pointer/touch only. The dialog is the keyboard and screen-reader route, so always offer it.
- Required items keep a disabled switch.

## RTL & i18n

- Rows and dialog use logical spacing; the handle sits at the inline start.
- Keyboard moves are vertical, so they are direction-independent.
- Every string is a prop; nothing is localised for you. Localise all `labels` and every `SidebarCustomizeItem.label`.

## Styling & tokens

- Row: `bg-popover`, `rounded-control`, dragging adds `shadow-floating` (`data-dragging`).
- Sidebar items being dragged get `bg-nq-surface-overlay` and `shadow-floating`.
- Target `[data-slot=sidebar-sortable-item][data-dragging]`. Extend with `className` on `SidebarSortableItem`.

## Do / Don't

- **Do** give every item a stable `id`; ids persist in storage. Prefix `storageKey` with your product (`"mahaam-nav-main"`).
- **Do** mark the home/dashboard item `required`.
- **Do** put the dialog trigger in the user menu, not in the sidebar body.
- **Don't** use display labels as ids (they change with locale).
- **Don't** add or remove ids per render; derive them from a stable list.

## Related

- [app-shell](https://docs.nasaqui.com/components/app-shell) · [user-menu](https://docs.nasaqui.com/components/user-menu)

## Lab

https://docs.nasaqui.com/?path=/docs/components-navigation-sidebar-customize--docs

## Code

### React

```tsx
import {
  AppShell, Sidebar, SidebarContent, SidebarGroup, SidebarItem, SidebarFooter, Button,
  SidebarCustomize, SidebarSortable, SidebarSortableItem, useSidebarLayout,
} from "@fadymondy/nasaq/web";
import { Inbox, LayoutDashboard, ListTodo } from "lucide-react";
import { type ReactNode, useState } from "react";

type Entry = { id: string; label: string; icon: ReactNode; required?: boolean; href: string };

const ENTRIES: Entry[] = [
  { id: "dashboard", label: "Dashboard", icon: <LayoutDashboard />, required: true, href: "/" },
  { id: "inbox", label: "Inbox", icon: <Inbox />, href: "/inbox" },
  { id: "issues", label: "My issues", icon: <ListTodo />, href: "/issues" },
];

function AppSidebar() {
  const [customizing, setCustomizing] = useState(false);
  const layout = useSidebarLayout("my-app-nav", ENTRIES.map((e) => e.id));
  const byId = new Map(ENTRIES.map((e) => [e.id, e]));

  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarSortable ids={layout.visible} onMove={layout.move}>
            {layout.visible.map((id) => {
              const entry = byId.get(id)!;
              return (
                <SidebarSortableItem key={id} id={id}>
                  <SidebarItem href={entry.href} icon={entry.icon}>{entry.label}</SidebarItem>
                </SidebarSortableItem>
              );
            })}
          </SidebarSortable>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter>
        <Button variant="ghost" size="sm" onClick={() => setCustomizing(true)}>Customize sidebar</Button>
      </SidebarFooter>
      <SidebarCustomize
        open={customizing}
        onOpenChange={setCustomizing}
        sections={[{ id: "main", items: ENTRIES, layout }]}
      />
    </Sidebar>
  );
}

export const Root = ({ children }: { children: ReactNode }) => <AppShell sidebar={<AppSidebar />}>{children}</AppShell>;
```

### shadcn

```tsx
import { AppShell, Sidebar, SidebarContent, SidebarGroup, SidebarItem, SidebarFooter } from "@/components/ui/app-shell";
import { Button } from "@/components/ui/button";
import { SidebarCustomize, SidebarSortable, SidebarSortableItem, useSidebarLayout } from "@/components/ui/sidebar-layout";
import { Inbox, LayoutDashboard, ListTodo } from "lucide-react";
import { type ReactNode, useState } from "react";

type Entry = { id: string; label: string; icon: ReactNode; required?: boolean; href: string };

const ENTRIES: Entry[] = [
  { id: "dashboard", label: "Dashboard", icon: <LayoutDashboard />, required: true, href: "/" },
  { id: "inbox", label: "Inbox", icon: <Inbox />, href: "/inbox" },
  { id: "issues", label: "My issues", icon: <ListTodo />, href: "/issues" },
];

function AppSidebar() {
  const [customizing, setCustomizing] = useState(false);
  const layout = useSidebarLayout("my-app-nav", ENTRIES.map((e) => e.id));
  const byId = new Map(ENTRIES.map((e) => [e.id, e]));

  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarSortable ids={layout.visible} onMove={layout.move}>
            {layout.visible.map((id) => {
              const entry = byId.get(id)!;
              return (
                <SidebarSortableItem key={id} id={id}>
                  <SidebarItem href={entry.href} icon={entry.icon}>{entry.label}</SidebarItem>
                </SidebarSortableItem>
              );
            })}
          </SidebarSortable>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter>
        <Button variant="ghost" size="sm" onClick={() => setCustomizing(true)}>Customize sidebar</Button>
      </SidebarFooter>
      <SidebarCustomize
        open={customizing}
        onOpenChange={setCustomizing}
        sections={[{ id: "main", items: ENTRIES, layout }]}
      />
    </Sidebar>
  );
}

export const Root = ({ children }: { children: ReactNode }) => <AppShell sidebar={<AppSidebar />}>{children}</AppShell>;
```

### Vue

```vue
<script setup lang="ts">
import { NqButton, NqSidebarCustomize, NqSidebarSortable, NqSidebarSortableItem, useSidebarLayout } from "@fadymondy/nasaq/vue";
import { Inbox, LayoutDashboard, ListTodo } from "lucide-vue-next";
import { ref } from "vue";

const ENTRIES = [
  { id: "dashboard", label: "Dashboard", icon: LayoutDashboard, required: true, href: "/" },
  { id: "inbox", label: "Inbox", icon: Inbox, href: "/inbox" },
  { id: "issues", label: "My issues", icon: ListTodo, href: "/issues" },
];

const customizing = ref(false);
const layout = useSidebarLayout("my-app-nav", ENTRIES.map((e) => e.id));
const byId = new Map(ENTRIES.map((e) => [e.id, e]));
const itemClass =
  "relative flex h-nav-row w-full items-center gap-2 rounded-control px-2 text-start text-body-sm text-sidebar-foreground transition-colors duration-150 ease-nq outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0";
</script>

<template>
  <nav class="flex w-64 flex-col gap-1 rounded-floating border border-border bg-sidebar p-2" aria-label="Main">
    <NqSidebarSortable :ids="layout.visible" :on-move="layout.move">
      <NqSidebarSortableItem v-for="id in layout.visible" :id="id" :key="id">
        <a :href="byId.get(id)!.href" :class="itemClass">
          <component :is="byId.get(id)!.icon" />
          <span class="min-w-0 flex-1 truncate">{{ byId.get(id)!.label }}</span>
        </a>
      </NqSidebarSortableItem>
    </NqSidebarSortable>
    <NqButton variant="ghost" size="sm" @click="customizing = true">Customize sidebar</NqButton>
    <NqSidebarCustomize v-model:open="customizing" :sections="[{ id: 'main', items: ENTRIES, layout }]" />
  </nav>
</template>
```

### Blade

```blade
<nav class="flex w-64 flex-col gap-1 rounded-floating border border-border bg-sidebar p-2" aria-label="Main">
    <x-nq::sidebar-layout storage-key="my-app-nav" :items="[
        ['id' => 'dashboard', 'label' => 'Dashboard', 'icon' => 'layout-dashboard', 'required' => true],
        ['id' => 'inbox', 'label' => 'Inbox', 'icon' => 'inbox'],
        ['id' => 'issues', 'label' => 'My issues', 'icon' => 'list-todo'],
    ]">
        <x-nq::sidebar-layout.sortable>
        <x-nq::sidebar-layout.sortable-item id="dashboard">
            <a href="/" class="relative flex h-nav-row w-full items-center gap-2 rounded-control px-2 text-start text-body-sm text-sidebar-foreground transition-colors duration-150 ease-nq outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0"><x-lucide-layout-dashboard /><span class="min-w-0 flex-1 truncate">Dashboard</span></a>
        </x-nq::sidebar-layout.sortable-item>
        <x-nq::sidebar-layout.sortable-item id="inbox">
            <a href="/inbox" class="relative flex h-nav-row w-full items-center gap-2 rounded-control px-2 text-start text-body-sm text-sidebar-foreground transition-colors duration-150 ease-nq outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0"><x-lucide-inbox /><span class="min-w-0 flex-1 truncate">Inbox</span></a>
        </x-nq::sidebar-layout.sortable-item>
        <x-nq::sidebar-layout.sortable-item id="issues">
            <a href="/issues" class="relative flex h-nav-row w-full items-center gap-2 rounded-control px-2 text-start text-body-sm text-sidebar-foreground transition-colors duration-150 ease-nq outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0"><x-lucide-list-todo /><span class="min-w-0 flex-1 truncate">My issues</span></a>
        </x-nq::sidebar-layout.sortable-item>
        </x-nq::sidebar-layout.sortable>
        <x-nq::sidebar-layout.trigger variant="ghost" size="sm">Customize sidebar</x-nq::sidebar-layout.trigger>
        <x-nq::sidebar-layout.customize />
    </x-nq::sidebar-layout>
</nav>
```

### HTML + Alpine

```html
<nav class="flex w-64 flex-col gap-1 rounded-floating border border-border bg-sidebar p-2" aria-label="Main">
    <div data-slot="sidebar-layout" x-data="nqSidebarLayout(JSON.parse('{\u0022sections\u0022:[{\u0022id\u0022:\u0022main\u0022,\u0022label\u0022:null,\u0022storageKey\u0022:\u0022my-app-nav\u0022,\u0022ids\u0022:[\u0022dashboard\u0022,\u0022inbox\u0022,\u0022issues\u0022],\u0022defaultHidden\u0022:[]}],\u0022open\u0022:false,\u0022moved\u0022:\u0022:label, position :position of :total\u0022}'))" x-modelable="open" x-id="['nq-dialog']" class="contents">
    <div data-slot="sidebar-sortable" class="flex flex-col"><div data-slot="sidebar-sortable-item" data-sortable-id="dashboard"
    x-show="isVisible('main', 'dashboard')" :style="{ order: position('main', 'dashboard') }"
    x-on:pointerdown="press($event, 'main', 'dashboard')" x-on:click.capture="swallowClick($event)" x-on:dragstart.prevent
    style="order: 0; "
    class="relative [&_a]:[-webkit-user-drag:none] data-dragging:z-10 data-dragging:cursor-grabbing data-dragging:*:bg-nq-surface-overlay data-dragging:*:shadow-floating"><a href="/" class="relative flex h-nav-row w-full items-center gap-2 rounded-control px-2 text-start text-body-sm text-sidebar-foreground transition-colors duration-150 ease-nq outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="7" height="9" x="3" y="3" rx="1"/>
  <rect width="7" height="5" x="14" y="3" rx="1"/>
  <rect width="7" height="9" x="14" y="12" rx="1"/>
  <rect width="7" height="5" x="3" y="16" rx="1"/>
</svg><span class="min-w-0 flex-1 truncate">Dashboard</span></a></div>
        <div data-slot="sidebar-sortable-item" data-sortable-id="inbox"
    x-show="isVisible('main', 'inbox')" :style="{ order: position('main', 'inbox') }"
    x-on:pointerdown="press($event, 'main', 'inbox')" x-on:click.capture="swallowClick($event)" x-on:dragstart.prevent
    style="order: 1; "
    class="relative [&_a]:[-webkit-user-drag:none] data-dragging:z-10 data-dragging:cursor-grabbing data-dragging:*:bg-nq-surface-overlay data-dragging:*:shadow-floating"><a href="/inbox" class="relative flex h-nav-row w-full items-center gap-2 rounded-control px-2 text-start text-body-sm text-sidebar-foreground transition-colors duration-150 ease-nq outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/>
  <path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>
</svg><span class="min-w-0 flex-1 truncate">Inbox</span></a></div>
        <div data-slot="sidebar-sortable-item" data-sortable-id="issues"
    x-show="isVisible('main', 'issues')" :style="{ order: position('main', 'issues') }"
    x-on:pointerdown="press($event, 'main', 'issues')" x-on:click.capture="swallowClick($event)" x-on:dragstart.prevent
    style="order: 2; "
    class="relative [&_a]:[-webkit-user-drag:none] data-dragging:z-10 data-dragging:cursor-grabbing data-dragging:*:bg-nq-surface-overlay data-dragging:*:shadow-floating"><a href="/issues" class="relative flex h-nav-row w-full items-center gap-2 rounded-control px-2 text-start text-body-sm text-sidebar-foreground transition-colors duration-150 ease-nq outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus [&_svg]:size-4 [&_svg]:shrink-0"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M13 5h8"/>
  <path d="M13 12h8"/>
  <path d="M13 19h8"/>
  <path d="m3 17 2 2 4-4"/>
  <rect x="3" y="4" width="6" height="6" rx="1"/>
</svg><span class="min-w-0 flex-1 truncate">My issues</span></a></div></div>
        <button data-slot="sidebar-layout-trigger"
     type="button"                         aria-haspopup="dialog" x-on:click="show()" :aria-expanded="open" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Customize sidebar</button>
        <template x-teleport="body">
    <div data-slot="dialog-portal">
        <div data-slot="dialog-backdrop" x-nq-presence="open" x-on:click="close()" class="fixed inset-0 z-50 bg-nq-fg/15 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
        <div data-slot="dialog-content" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
            class="fixed inset-0 z-50 m-auto grid h-fit w-[calc(100%-2rem)] rounded-floating border border-border bg-popover text-popover-foreground outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 max-w-md gap-0 p-0">
            <div data-slot="dialog-header" class="flex flex-col gap-1.5 text-start border-b border-border px-5 py-4 pe-12"><h2 data-slot="dialog-title" :id="$id('nq-dialog', 'title')" class="text-h3 text-foreground">Customize sidebar</h2>
        <p data-slot="dialog-description" :id="$id('nq-dialog', 'description')" class="text-body-sm text-muted-foreground">Drag to reorder. Switch items off to hide them.</p></div>
    <div class="flex max-h-[60dvh] flex-col gap-4 overflow-y-auto px-3 py-3">
                                <section class="flex flex-col gap-1" data-section="main" >
                                <ul class="flex flex-col gap-0.5">
                                                                    <li data-sortable-id="dashboard" :style="{ order: position('main', 'dashboard') }" style="order: 0"
                            class="relative flex h-10 items-center gap-2 rounded-control bg-popover px-1 data-dragging:z-10 data-dragging:shadow-floating">
                            <button type="button" aria-label="Reorder Dashboard" aria-keyshortcuts="ArrowUp ArrowDown Home End"
                                x-on:keydown="keyMove($event, 'main', 'dashboard', 'Dashboard')" x-on:pointerdown="press($event, 'main', 'dashboard', true)"
                                class="inline-flex size-7 cursor-grab touch-none items-center justify-center rounded-control text-muted-foreground outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus active:cursor-grabbing [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="9" cy="12" r="1"/>
  <circle cx="9" cy="5" r="1"/>
  <circle cx="9" cy="19" r="1"/>
  <circle cx="15" cy="12" r="1"/>
  <circle cx="15" cy="5" r="1"/>
  <circle cx="15" cy="19" r="1"/>
</svg></button>
                                                            <span class="text-muted-foreground [&_svg]:size-4" :class="isVisible('main', 'dashboard') ? '' : 'opacity-50'"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="7" height="9" x="3" y="3" rx="1"/>
  <rect width="7" height="5" x="14" y="3" rx="1"/>
  <rect width="7" height="9" x="14" y="12" rx="1"/>
  <rect width="7" height="5" x="3" y="16" rx="1"/>
</svg></span>
                                                        <span data-sortable-label class="min-w-0 flex-1 truncate text-body-sm text-foreground"
                                :class="isVisible('main', 'dashboard') ? 'text-foreground' : 'text-muted-foreground'">Dashboard</span>
                            <button type="button" role="switch" data-slot="switch" aria-label="Dashboard"
                                x-on:click="toggleVisible('main', 'dashboard')"
                                :aria-checked="String(isVisible('main', 'dashboard'))"
                                :data-checked="isVisible('main', 'dashboard') ? '' : undefined" :data-unchecked="isVisible('main', 'dashboard') ? undefined : ''"
                                aria-checked="true"  data-checked                                  disabled data-disabled                                 class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent bg-nq-line-strong p-0.5 outline-none transition-colors duration-150 ease-nq data-checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50 me-1">
                                <span data-slot="switch-thumb"  data-checked                                     :data-checked="isVisible('main', 'dashboard') ? '' : undefined" :data-unchecked="isVisible('main', 'dashboard') ? undefined : ''"
                                    class="block size-4 rounded-full bg-background shadow-xs data-checked:bg-primary-foreground transition-[translate] duration-150 ease-nq data-checked:translate-x-3.5 rtl:data-checked:-translate-x-3.5"></span>
                            </button>
                        </li>
                                                                    <li data-sortable-id="inbox" :style="{ order: position('main', 'inbox') }" style="order: 1"
                            class="relative flex h-10 items-center gap-2 rounded-control bg-popover px-1 data-dragging:z-10 data-dragging:shadow-floating">
                            <button type="button" aria-label="Reorder Inbox" aria-keyshortcuts="ArrowUp ArrowDown Home End"
                                x-on:keydown="keyMove($event, 'main', 'inbox', 'Inbox')" x-on:pointerdown="press($event, 'main', 'inbox', true)"
                                class="inline-flex size-7 cursor-grab touch-none items-center justify-center rounded-control text-muted-foreground outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus active:cursor-grabbing [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="9" cy="12" r="1"/>
  <circle cx="9" cy="5" r="1"/>
  <circle cx="9" cy="19" r="1"/>
  <circle cx="15" cy="12" r="1"/>
  <circle cx="15" cy="5" r="1"/>
  <circle cx="15" cy="19" r="1"/>
</svg></button>
                                                            <span class="text-muted-foreground [&_svg]:size-4" :class="isVisible('main', 'inbox') ? '' : 'opacity-50'"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/>
  <path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>
</svg></span>
                                                        <span data-sortable-label class="min-w-0 flex-1 truncate text-body-sm text-foreground"
                                :class="isVisible('main', 'inbox') ? 'text-foreground' : 'text-muted-foreground'">Inbox</span>
                            <button type="button" role="switch" data-slot="switch" aria-label="Inbox"
                                x-on:click="toggleVisible('main', 'inbox')"
                                :aria-checked="String(isVisible('main', 'inbox'))"
                                :data-checked="isVisible('main', 'inbox') ? '' : undefined" :data-unchecked="isVisible('main', 'inbox') ? undefined : ''"
                                aria-checked="true"  data-checked                                                                 class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent bg-nq-line-strong p-0.5 outline-none transition-colors duration-150 ease-nq data-checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50 me-1">
                                <span data-slot="switch-thumb"  data-checked                                     :data-checked="isVisible('main', 'inbox') ? '' : undefined" :data-unchecked="isVisible('main', 'inbox') ? undefined : ''"
                                    class="block size-4 rounded-full bg-background shadow-xs data-checked:bg-primary-foreground transition-[translate] duration-150 ease-nq data-checked:translate-x-3.5 rtl:data-checked:-translate-x-3.5"></span>
                            </button>
                        </li>
                                                                    <li data-sortable-id="issues" :style="{ order: position('main', 'issues') }" style="order: 2"
                            class="relative flex h-10 items-center gap-2 rounded-control bg-popover px-1 data-dragging:z-10 data-dragging:shadow-floating">
                            <button type="button" aria-label="Reorder My issues" aria-keyshortcuts="ArrowUp ArrowDown Home End"
                                x-on:keydown="keyMove($event, 'main', 'issues', 'My issues')" x-on:pointerdown="press($event, 'main', 'issues', true)"
                                class="inline-flex size-7 cursor-grab touch-none items-center justify-center rounded-control text-muted-foreground outline-none hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus active:cursor-grabbing [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="9" cy="12" r="1"/>
  <circle cx="9" cy="5" r="1"/>
  <circle cx="9" cy="19" r="1"/>
  <circle cx="15" cy="12" r="1"/>
  <circle cx="15" cy="5" r="1"/>
  <circle cx="15" cy="19" r="1"/>
</svg></button>
                                                            <span class="text-muted-foreground [&_svg]:size-4" :class="isVisible('main', 'issues') ? '' : 'opacity-50'"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M13 5h8"/>
  <path d="M13 12h8"/>
  <path d="M13 19h8"/>
  <path d="m3 17 2 2 4-4"/>
  <rect x="3" y="4" width="6" height="6" rx="1"/>
</svg></span>
                                                        <span data-sortable-label class="min-w-0 flex-1 truncate text-body-sm text-foreground"
                                :class="isVisible('main', 'issues') ? 'text-foreground' : 'text-muted-foreground'">My issues</span>
                            <button type="button" role="switch" data-slot="switch" aria-label="My issues"
                                x-on:click="toggleVisible('main', 'issues')"
                                :aria-checked="String(isVisible('main', 'issues'))"
                                :data-checked="isVisible('main', 'issues') ? '' : undefined" :data-unchecked="isVisible('main', 'issues') ? undefined : ''"
                                aria-checked="true"  data-checked                                                                 class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent bg-nq-line-strong p-0.5 outline-none transition-colors duration-150 ease-nq data-checked:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:cursor-not-allowed data-disabled:opacity-50 me-1">
                                <span data-slot="switch-thumb"  data-checked                                     :data-checked="isVisible('main', 'issues') ? '' : undefined" :data-unchecked="isVisible('main', 'issues') ? undefined : ''"
                                    class="block size-4 rounded-full bg-background shadow-xs data-checked:bg-primary-foreground transition-[translate] duration-150 ease-nq data-checked:translate-x-3.5 rtl:data-checked:-translate-x-3.5"></span>
                            </button>
                        </li>
                                    </ul>
            </section>
            </div>
    <div aria-live="polite" class="sr-only" x-text="announcement"></div>
    <div data-slot="dialog-footer" class="flex flex-col-reverse gap-2 sm:flex-row border-t border-border px-5 py-3 sm:justify-between"><button data-slot="button"
     type="button"                         x-bind:disabled="allDefault()" x-on:click="reset()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Reset to default</button>
        <button data-slot="button"
     type="button"                         x-on:click="close()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        Done</button></div>
                            <button type="button" data-slot="dialog-close" x-on:click="close()" aria-label="Close"
                    class="absolute end-3 top-3 inline-flex size-8 items-center justify-center rounded-control text-muted-foreground transition-colors duration-150 hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
                    </div>
    </div>
</template>
</div>
</nav>
```
