{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mobile-bar-nav",
  "title": "MobileBarNav",
  "description": "Persistent mobile shell navigation for Menu, the router-derived current area, Search, Inbox, Messages, and Agents.",
  "type": "registry:ui",
  "meta": {
    "library": "@ds-mo/ui",
    "distribution": "npm",
    "storybook": "https://zainadeel.github.io/compomo/?path=/story/mobile-bar-nav",
    "source": "src/wc/components/MobileBarNav/MobileBarNav.tsx",
    "intentStatus": "complete",
    "intent": {
      "audience": "specialized",
      "status": "experimental",
      "summary": "Persistent mobile shell navigation for Menu, the router-derived current area, Search, Inbox, Messages, and Agents.",
      "useWhen": [
        "ShellApp is rendering its dedicated below-768px application composition."
      ],
      "avoidWhen": [
        "Rendering desktop or tablet shell chrome.",
        "An arbitrary set of product tabs is required."
      ],
      "commonlyComposedWith": [
        "component:ds-shell-app"
      ],
      "patterns": [
        "pattern:application-shell"
      ],
      "accessibility": [
        "Every icon-only item keeps a localized accessible name.",
        "Only the active destination exposes aria-current; Menu exposes its controlled pane through aria-expanded."
      ],
      "states": [
        "The fixed groups are Menu plus current area, followed by Search, Inbox, Messages, and Agents.",
        "Selection promotes foreground to primary without painting a selected fill; unselected destinations use tertiary foreground.",
        "Opening Mobile Sheet Nav replaces the Menu glyph with Cross while preserving its accessible name.",
        "Reselecting an active destination does not reset its nested state.",
        "Direct touch paints the pressed overlay immediately, keeps a quick tap visible for one short frame, and clears it after release or cancellation without relying on Safari's latch-prone active state."
      ],
      "responsiveBehavior": [
        "Render only below the mobile breakpoint; its primary surface extends through the bottom safe-area inset when the application enables viewport-fit=cover.",
        "The primary-background bar keeps an 8px outer inset; its two icon-only groups anchor to opposite edges while retaining fixed 40px targets and a divider between Menu and current area.",
        "Bar chrome and its divider are not text-selectable and suppress the iOS touch callout."
      ],
      "references": [
        {
          "label": "Storybook",
          "path": "src/wc/components/MobileBarNav/MobileBarNav.stories.ts"
        }
      ]
    },
    "api": {
      "props": {
        "activeDestination": {
          "type": "MobileDestination",
          "resolvedType": "\"agents\" | \"area\" | \"help\" | \"inbox\" | \"messages\" | \"search\"",
          "attribute": "active-destination",
          "default": "'area'",
          "required": false,
          "mutable": false
        },
        "agentsDot": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "agents-dot",
          "default": "false",
          "required": false,
          "mutable": false
        },
        "agentsLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "agents-label",
          "default": "'Agents'",
          "required": false,
          "mutable": false
        },
        "currentArea": {
          "type": "PanelNavItem",
          "resolvedType": "PanelNavItem",
          "default": "{\n    id: 'tracking',\n    icon: 'MapPage',\n    label: 'Tracking',\n  }",
          "required": false,
          "mutable": false
        },
        "helpLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "help-label",
          "default": "'Help & Support'",
          "required": false,
          "mutable": false
        },
        "inboxDot": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "inbox-dot",
          "default": "false",
          "required": false,
          "mutable": false
        },
        "inboxLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "inbox-label",
          "default": "'Inbox'",
          "required": false,
          "mutable": false
        },
        "menuLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "menu-label",
          "default": "'Menu'",
          "required": false,
          "mutable": false
        },
        "messagesDot": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "messages-dot",
          "default": "false",
          "required": false,
          "mutable": false
        },
        "messagesLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "messages-label",
          "default": "'Messages'",
          "required": false,
          "mutable": false
        },
        "searchDot": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "search-dot",
          "default": "false",
          "required": false,
          "mutable": false
        },
        "searchLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "search-label",
          "default": "'Search'",
          "required": false,
          "mutable": false
        },
        "sheetNavExpanded": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "sheet-nav-expanded",
          "default": "false",
          "required": false,
          "mutable": false
        }
      },
      "events": [
        {
          "name": "dsDestinationChange",
          "detail": "MobileBarNavDestinationDetail",
          "bubbles": true,
          "cancelable": true,
          "composed": true
        },
        {
          "name": "dsSheetNavToggle",
          "detail": "boolean",
          "bubbles": true,
          "cancelable": true,
          "composed": true
        }
      ],
      "methods": [
        {
          "name": "focusDestination",
          "signature": "focusDestination(destination: MobileDestination | \"sheet-nav\") => Promise<void>"
        }
      ],
      "slots": []
    },
    "props": {
      "activeDestination": {
        "type": "MobileDestination",
        "resolvedType": "\"agents\" | \"area\" | \"help\" | \"inbox\" | \"messages\" | \"search\"",
        "attribute": "active-destination",
        "default": "'area'",
        "required": false,
        "mutable": false
      },
      "agentsDot": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "agents-dot",
        "default": "false",
        "required": false,
        "mutable": false
      },
      "agentsLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "agents-label",
        "default": "'Agents'",
        "required": false,
        "mutable": false
      },
      "currentArea": {
        "type": "PanelNavItem",
        "resolvedType": "PanelNavItem",
        "default": "{\n    id: 'tracking',\n    icon: 'MapPage',\n    label: 'Tracking',\n  }",
        "required": false,
        "mutable": false
      },
      "helpLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "help-label",
        "default": "'Help & Support'",
        "required": false,
        "mutable": false
      },
      "inboxDot": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "inbox-dot",
        "default": "false",
        "required": false,
        "mutable": false
      },
      "inboxLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "inbox-label",
        "default": "'Inbox'",
        "required": false,
        "mutable": false
      },
      "menuLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "menu-label",
        "default": "'Menu'",
        "required": false,
        "mutable": false
      },
      "messagesDot": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "messages-dot",
        "default": "false",
        "required": false,
        "mutable": false
      },
      "messagesLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "messages-label",
        "default": "'Messages'",
        "required": false,
        "mutable": false
      },
      "searchDot": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "search-dot",
        "default": "false",
        "required": false,
        "mutable": false
      },
      "searchLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "search-label",
        "default": "'Search'",
        "required": false,
        "mutable": false
      },
      "sheetNavExpanded": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "sheet-nav-expanded",
        "default": "false",
        "required": false,
        "mutable": false
      }
    },
    "events": [
      {
        "name": "dsDestinationChange",
        "detail": "MobileBarNavDestinationDetail",
        "bubbles": true,
        "cancelable": true,
        "composed": true
      },
      {
        "name": "dsSheetNavToggle",
        "detail": "boolean",
        "bubbles": true,
        "cancelable": true,
        "composed": true
      }
    ],
    "methods": [
      {
        "name": "focusDestination",
        "signature": "focusDestination(destination: MobileDestination | \"sheet-nav\") => Promise<void>"
      }
    ],
    "slots": [],
    "exports": {
      "customElement": "ds-mobile-bar-nav",
      "react": "DsMobileBarNav",
      "vue": "DsMobileBarNav",
      "angular": "DsMobileBarNav"
    },
    "consumption": {
      "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
      "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
      "customElements": {
        "import": "import '@ds-mo/ui/dist/components/ds-mobile-bar-nav.js';",
        "example": "<ds-mobile-bar-nav></ds-mobile-bar-nav>"
      },
      "react": {
        "import": "import { DsMobileBarNav } from '@ds-mo/ui/react';",
        "example": "<DsMobileBarNav />"
      },
      "vue": {
        "import": "import { DsMobileBarNav } from '@ds-mo/ui/vue';",
        "example": "<DsMobileBarNav />"
      },
      "angular": {
        "import": "import { DsMobileBarNav } from '@ds-mo/ui/angular/ds-mobile-bar-nav';",
        "example": "<ds-mobile-bar-nav></ds-mobile-bar-nav>"
      },
      "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: currentArea.",
      "peerDependencies": {
        "required": [
          "@ds-mo/tokens ^6.5.0",
          "@ds-mo/icons ^7.0.0"
        ],
        "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
      }
    }
  },
  "dependencies": [
    "@ds-mo/ui",
    "@ds-mo/tokens",
    "@ds-mo/icons"
  ],
  "registryDependencies": [
    "badge",
    "icon"
  ],
  "files": [
    {
      "path": "src/wc/components/MobileBarNav/MobileBarNav.css",
      "content": "@import '../../utils/chrome-layout.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/interaction-fill.css';\n\n:host {\n  display: block;\n  min-width: 0;\n  background: var(--color-background-primary);\n  color: var(--color-foreground-tertiary);\n  -webkit-user-select: none;\n  user-select: none;\n  -webkit-touch-callout: none;\n}\n\n.mobile-bar-nav {\n  justify-content: space-between;\n  min-width: 0;\n  padding-block-end: max(var(--ds-chrome-padding), env(safe-area-inset-bottom));\n  border-top: var(--dimension-stroke-width-012) solid var(--color-border-tertiary);\n  background: var(--color-background-primary);\n  -webkit-user-select: none;\n  user-select: none;\n  -webkit-touch-callout: none;\n}\n\n.mobile-bar-nav__group {\n  display: flex;\n  align-items: center;\n  gap: var(--dimension-space-100);\n}\n\n.mobile-bar-nav__item {\n  appearance: none;\n  flex: 0 0 var(--dimension-size-500);\n  width: var(--dimension-size-500);\n  height: var(--dimension-size-500);\n  padding: var(--dimension-space-100);\n  border: 0;\n  border-radius: var(--dimension-radius-025);\n  background: transparent;\n  color: inherit;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  cursor: default;\n}\n\n.mobile-bar-nav__item--selected {\n  color: var(--color-foreground-primary);\n}\n\n/*\n * Safari can retain its native :active state after a direct touch. On coarse\n * touch-only pointers, the component-owned class provides one deterministic\n * press frame and clears it after release/cancel.\n */\n@media (hover: none) and (pointer: coarse) {\n  .mobile-bar-nav__item.ds-interaction-fill:active:not(:disabled)::after {\n    background: transparent;\n  }\n\n  .mobile-bar-nav__item--pressed.ds-interaction-fill::after,\n  .mobile-bar-nav__item--pressed.ds-interaction-fill:active:not(:disabled)::after {\n    background: var(--ds-interaction-pressed);\n  }\n}\n\n.mobile-bar-nav__divider {\n  flex: 0 0 var(--dimension-stroke-width-012);\n  width: var(--dimension-stroke-width-012);\n  height: var(--dimension-iconography-lg);\n  background: var(--color-border-tertiary);\n  pointer-events: none;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.mobile-bar-nav__icon {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--dimension-iconography-lg);\n  height: var(--dimension-iconography-lg);\n}\n\n.mobile-bar-nav__dot {\n  position: absolute;\n  inset-block-start: calc(var(--dimension-space-025) * -1);\n  inset-inline-end: calc(var(--dimension-space-025) * -1);\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/wc/components/MobileBarNav/MobileBarNav.tsx",
      "content": "import {\n  Component,\n  Element,\n  Event,\n  EventEmitter,\n  h,\n  Host,\n  Method,\n  Prop,\n  State,\n} from '@stencil/core';\nimport {\n  shouldEmitMobileDestinationChange,\n  type MobileDestination,\n} from '../../shell/shell-responsive';\nimport type { PanelNavItem } from '../PanelNav/panel-nav-types';\nimport type { MobileBarNavDestinationDetail } from './mobile-bar-nav-types';\n\ninterface DestinationConfig {\n  id: MobileDestination;\n  icon: string;\n  label: string;\n  dot: boolean;\n}\n\nconst MINIMUM_DIRECT_PRESS_MS = 120;\n\n@Component({\n  tag: 'ds-mobile-bar-nav',\n  styleUrl: 'MobileBarNav.css',\n  scoped: true,\n})\nexport class MobileBarNav {\n  @Element() el!: HTMLElement;\n\n  @Prop() activeDestination: MobileDestination = 'area';\n  @Prop() currentArea: PanelNavItem = {\n    id: 'tracking',\n    icon: 'MapPage',\n    label: 'Tracking',\n  };\n  @Prop() sheetNavExpanded: boolean = false;\n  @Prop() menuLabel: string = 'Menu';\n  @Prop() searchLabel: string = 'Search';\n  @Prop() inboxLabel: string = 'Inbox';\n  @Prop() messagesLabel: string = 'Messages';\n  @Prop() agentsLabel: string = 'Agents';\n  @Prop() helpLabel: string = 'Help & Support';\n  @Prop() searchDot: boolean = false;\n  @Prop() inboxDot: boolean = false;\n  @Prop() messagesDot: boolean = false;\n  @Prop() agentsDot: boolean = false;\n\n  @State() private pressedItemId: string | null = null;\n\n  @Event() dsSheetNavToggle!: EventEmitter<boolean>;\n  @Event() dsDestinationChange!: EventEmitter<MobileBarNavDestinationDetail>;\n\n  private activePointerId: number | null = null;\n  private pressedAt = 0;\n  private pressClearTimer: ReturnType<typeof setTimeout> | null = null;\n\n  disconnectedCallback() {\n    this.clearPressedItem();\n  }\n\n  private clearPressTimer() {\n    if (this.pressClearTimer === null) return;\n    clearTimeout(this.pressClearTimer);\n    this.pressClearTimer = null;\n  }\n\n  private clearPressedItem() {\n    this.clearPressTimer();\n    this.activePointerId = null;\n    this.pressedItemId = null;\n  }\n\n  private beginDirectPress(itemId: string, event: PointerEvent) {\n    if (!event.isPrimary || event.pointerType === 'mouse' || event.button !== 0) return;\n\n    this.clearPressTimer();\n    this.activePointerId = event.pointerId;\n    this.pressedAt = performance.now();\n    this.pressedItemId = itemId;\n\n    const target = event.currentTarget as HTMLElement;\n    try {\n      target.setPointerCapture(event.pointerId);\n    } catch {\n      // Pointer capture can fail if Safari has already cancelled the stream.\n    }\n  }\n\n  private finishDirectPress(event: PointerEvent) {\n    if (event.pointerId !== this.activePointerId) return;\n\n    this.activePointerId = null;\n    const remaining = Math.max(\n      0,\n      MINIMUM_DIRECT_PRESS_MS - (performance.now() - this.pressedAt)\n    );\n\n    this.clearPressTimer();\n    this.pressClearTimer = setTimeout(() => {\n      this.pressClearTimer = null;\n      this.pressedItemId = null;\n    }, remaining);\n  }\n\n  private cancelDirectPress(event: PointerEvent) {\n    if (event.pointerId !== this.activePointerId) return;\n    this.clearPressedItem();\n  }\n\n  private pressHandlers(itemId: string) {\n    return {\n      onPointerDown: (event: PointerEvent) => this.beginDirectPress(itemId, event),\n      onPointerUp: (event: PointerEvent) => this.finishDirectPress(event),\n      onPointerCancel: (event: PointerEvent) => this.cancelDirectPress(event),\n      onLostPointerCapture: (event: PointerEvent) => this.finishDirectPress(event),\n    };\n  }\n\n  private destinationConfig(): DestinationConfig[] {\n    const currentArea =\n      this.activeDestination === 'help'\n        ? {\n            id: 'help' as const,\n            icon: 'CircleQuestion',\n            label: this.helpLabel,\n            dot: false,\n          }\n        : {\n            id: 'area' as const,\n            icon: this.currentArea.icon || 'MapPage',\n            label: this.currentArea.label || 'Area',\n            dot: false,\n          };\n\n    return [\n      currentArea,\n      { id: 'search', icon: 'MagnifyingGlass', label: this.searchLabel, dot: this.searchDot },\n      { id: 'inbox', icon: 'Inbox', label: this.inboxLabel, dot: this.inboxDot },\n      {\n        id: 'messages',\n        icon: 'MessageBubbleStack',\n        label: this.messagesLabel,\n        dot: this.messagesDot,\n      },\n      { id: 'agents', icon: 'AI', label: this.agentsLabel, dot: this.agentsDot },\n    ];\n  }\n\n  private selectDestination(destination: MobileDestination) {\n    if (\n      !shouldEmitMobileDestinationChange(\n        this.activeDestination,\n        destination,\n        this.sheetNavExpanded\n      )\n    ) {\n      return;\n    }\n    this.dsDestinationChange.emit({ destination });\n    // Owners may replace the visible mobile stage (and transiently drop focus\n    // in WebKit) while handling the controlled destination change.\n    requestAnimationFrame(() => {\n      void this.focusDestination(destination);\n    });\n  }\n\n  @Method()\n  async focusDestination(destination: MobileDestination | 'sheet-nav') {\n    const target = this.el.querySelector<HTMLElement>(\n      `#${\n        destination === 'sheet-nav'\n          ? 'ds-mobile-sheet-nav-trigger'\n          : `ds-mobile-bar-${destination}-trigger`\n      }`\n    );\n    target?.focus({ preventScroll: true });\n  }\n\n  private renderDestination(item: DestinationConfig) {\n    const selected = !this.sheetNavExpanded && item.id === this.activeDestination;\n    return (\n      <button\n        id={`ds-mobile-bar-${item.id}-trigger`}\n        type=\"button\"\n        class={{\n          'mobile-bar-nav__item': true,\n          'mobile-bar-nav__item--selected': selected,\n          'mobile-bar-nav__item--pressed': this.pressedItemId === item.id,\n          'ds-focus-ring-inset': true,\n          'ds-interaction-fill': true,\n        }}\n        aria-label={item.label}\n        aria-current={selected ? 'page' : undefined}\n        onClick={() => this.selectDestination(item.id)}\n        {...this.pressHandlers(item.id)}\n      >\n        <span class=\"mobile-bar-nav__icon ds-interaction-fill__content\">\n          <ds-icon name={item.icon} size=\"lg\" color=\"inherit\" />\n          {item.dot && (\n            <ds-badge class=\"mobile-bar-nav__dot\" variant=\"dot\" label=\"\" aria-hidden=\"true\" />\n          )}\n        </span>\n      </button>\n    );\n  }\n\n  render() {\n    const [currentArea, ...tools] = this.destinationConfig();\n\n    return (\n      <Host>\n        <nav class=\"mobile-bar-nav ds-chrome-row ds-chrome-space--md\" aria-label=\"Primary\">\n          <div class=\"mobile-bar-nav__group mobile-bar-nav__group--context\">\n            <button\n              id=\"ds-mobile-sheet-nav-trigger\"\n              type=\"button\"\n              class={{\n                'mobile-bar-nav__item': true,\n                'mobile-bar-nav__item--selected': this.sheetNavExpanded,\n                'mobile-bar-nav__item--pressed':\n                  this.pressedItemId === 'sheet-nav',\n                'ds-focus-ring-inset': true,\n                'ds-interaction-fill': true,\n              }}\n              aria-label={this.menuLabel}\n              aria-expanded={String(this.sheetNavExpanded)}\n              aria-controls=\"ds-mobile-sheet-nav\"\n              onClick={() => this.dsSheetNavToggle.emit(!this.sheetNavExpanded)}\n              {...this.pressHandlers('sheet-nav')}\n            >\n              <span class=\"mobile-bar-nav__icon ds-interaction-fill__content\">\n                <ds-icon\n                  name={this.sheetNavExpanded ? 'Cross' : 'Hamburger'}\n                  size=\"lg\"\n                  color=\"inherit\"\n                />\n              </span>\n            </button>\n            <span class=\"mobile-bar-nav__divider\" aria-hidden=\"true\" />\n            {this.renderDestination(currentArea)}\n          </div>\n          <div class=\"mobile-bar-nav__group mobile-bar-nav__group--tools\">\n            {tools.map(item => this.renderDestination(item))}\n          </div>\n        </nav>\n      </Host>\n    );\n  }\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/wc/components/MobileBarNav/mobile-bar-nav-types.ts",
      "content": "import type { MobileDestination } from '../../shell/shell-responsive';\n\nexport interface MobileBarNavDestinationDetail {\n  destination: MobileDestination;\n}\n",
      "type": "registry:ui"
    }
  ]
}
