Private/Interactive/Render-TBMenuBox.ps1

function Render-TBMenuBox {
    <#
    .SYNOPSIS
        Renders menu items inside a rounded-corner box with in-place redraw.
    .DESCRIPTION
        Draws menu items with optional highlight indicator and checkboxes
        inside a Unicode box. Uses Console.SetCursorPosition for flicker-free
        in-place updates. Supports both single-select and multi-select modes.
    .PARAMETER Items
        Array of menu item display strings.
    .PARAMETER SelectedIndex
        The currently highlighted item index.
    .PARAMETER AnchorTop
        The console row to start rendering from.
    .PARAMETER Checked
        Optional boolean array for multi-select checkbox state.
    .PARAMETER IncludeBack
        If set, shows 'Esc to go back' in the hint line.
    .PARAMETER IncludeQuit
        If set, shows 'Esc to quit' in the hint line.
    .PARAMETER MultiSelect
        If set, renders checkboxes and shows multi-select hints.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$Items,

        [Parameter(Mandatory = $true)]
        [int]$SelectedIndex,

        [Parameter(Mandatory = $true)]
        [int]$AnchorTop,

        [Parameter()]
        [bool[]]$Checked,

        [Parameter()]
        [switch]$IncludeBack,

        [Parameter()]
        [switch]$IncludeQuit,

        [Parameter()]
        [switch]$MultiSelect
    )

    $palette = Get-TBColorPalette
    $esc = [char]27
    $reset = "${esc}[0m"

    $innerWidth = Get-TBConsoleInnerWidth
    $border = ([char]0x2502)
    $hLine = ([char]0x2500)

    $blueRGB  = @(137, 180, 250)
    $mauveRGB = @(203, 166, 247)
    $fitText = {
        param([string]$Text)
        if ($null -eq $Text) { return '' }
        if ($Text.Length -le $innerWidth) { return $Text }
        if ($innerWidth -le 3) { return $Text.Substring(0, $innerWidth) }
        return $Text.Substring(0, $innerWidth - 3) + '...'
    }

    $row = $AnchorTop

    # Empty line inside box
    $emptyLine = ' {0}{1}{2}{3}{4}' -f $palette.Surface, $border, (' ' * $innerWidth), $border, $reset
    [Console]::SetCursorPosition(0, $row)
    [Console]::Write($emptyLine)
    $row++

    # Menu items
    for ($i = 0; $i -lt $Items.Count; $i++) {
        [Console]::SetCursorPosition(0, $row)

        $num = $i + 1
        $isHighlighted = ($i -eq $SelectedIndex)

        if ($MultiSelect -and $Checked) {
            if ($Checked[$i]) {
                $checkChar = [char]0x2611  # checked box
            }
            else {
                $checkChar = [char]0x2610  # unchecked box
            }

            if ($isHighlighted) {
                $chevron = [char]0x276F  # heavy chevron
                $itemText = (' {0} {1} {2} {3} {4}' -f $chevron, $checkChar, $num, ([char]0x25B8), $Items[$i])
                $padded = (& $fitText $itemText).PadRight($innerWidth)
                $line = ' {0}{1}{2}{3}{4}{5}{6}{7}' -f $palette.Surface, $border, $palette.BgSelect, $palette.Mauve, $palette.Bold, $padded, $reset, ('{0}{1}{2}' -f $palette.Surface, $border, $reset)
            }
            else {
                $checkColor = if ($Checked[$i]) { $palette.Green } else { $palette.Dim }
                $itemText = (' {0} {1} {2} {3}' -f $checkChar, $num, ([char]0x25B8), $Items[$i])
                $padded = (& $fitText $itemText).PadRight($innerWidth)
                $line = ' {0}{1}{2}{3}{4}{5}' -f $palette.Surface, $border, $checkColor, $padded, $reset, ('{0}{1}{2}' -f $palette.Surface, $border, $reset)
            }
        }
        else {
            if ($isHighlighted) {
                $chevron = [char]0x276F
                $itemText = (' {0} {1} {2} {3}' -f $chevron, $num, ([char]0x25B8), $Items[$i])
                $padded = (& $fitText $itemText).PadRight($innerWidth)
                $line = ' {0}{1}{2}{3}{4}{5}{6}{7}' -f $palette.Surface, $border, $palette.BgSelect, $palette.Mauve, $palette.Bold, $padded, $reset, ('{0}{1}{2}' -f $palette.Surface, $border, $reset)
            }
            else {
                $itemText = (' {0} {1} {2}' -f $num, ([char]0x25B8), $Items[$i])
                $padded = (& $fitText $itemText).PadRight($innerWidth)
                $line = ' {0}{1}{2}{3}{4}{5}' -f $palette.Surface, $border, $palette.Text, $padded, $reset, ('{0}{1}{2}' -f $palette.Surface, $border, $reset)
            }
        }

        [Console]::Write($line)
        $row++
    }

    # Empty line
    [Console]::SetCursorPosition(0, $row)
    [Console]::Write($emptyLine)
    $row++

    # Separator line
    $sepGradient = Get-TBGradientLine -Character $hLine -Length $innerWidth -StartRGB $blueRGB -EndRGB $mauveRGB
    $sepLine = ' {0}{1}{2}{3}{4}' -f $palette.Surface, ([char]0x251C), $sepGradient, ([char]0x2524), $reset
    [Console]::SetCursorPosition(0, $row)
    [Console]::Write($sepLine)
    $row++

    # Hint line
    if ($MultiSelect) {
        $hintText = ' Space to toggle, A for all, Enter to confirm'
    }
    else {
        $hintText = ' Use arrow keys to navigate, Enter to select'
    }

    if ($IncludeBack) {
        $hintText += ', Esc to go back'
    }
    elseif ($IncludeQuit) {
        $hintText += ', Esc to quit'
    }

    $hintPadded = $hintText.PadRight($innerWidth)
    $hintLine = ' {0}{1}{2}{3}{4}{5}' -f $palette.Surface, $border, $palette.Dim, $hintPadded, $reset, ('{0}{1}{2}' -f $palette.Surface, $border, $reset)
    [Console]::SetCursorPosition(0, $row)
    [Console]::Write($hintLine)
    $row++

    # Bottom border
    $bottomGradient = Get-TBGradientLine -Character $hLine -Length $innerWidth -StartRGB $blueRGB -EndRGB $mauveRGB
    $bottomLine = ' {0}{1}{2}{3}{4}' -f $palette.Surface, ([char]0x2570), $bottomGradient, ([char]0x256F), $reset
    [Console]::SetCursorPosition(0, $row)
    [Console]::Write($bottomLine)
    $row++

    # Move cursor below the box
    [Console]::SetCursorPosition(0, $row)
}