public/controls/New-UiExpander.ps1
|
function New-UiExpander { <# .SYNOPSIS Creates a collapsible expander section with a header and content area. .DESCRIPTION Creates a theme-aware collapsible section. Click the header to toggle visibility of the content. Built from primitives for proper dark theme support. .PARAMETER Header The text displayed in the expander header. .PARAMETER Content Scriptblock containing the UI elements to show when expanded. .PARAMETER IsExpanded Start with the expander open. Default is collapsed. .PARAMETER Variable Variable name for accessing this control in button actions. .PARAMETER WPFProperties Hashtable of additional WPF properties to set on the control. .EXAMPLE New-UiExpander -Header 'Advanced Options' -Content { New-UiToggle -Text 'Enable logging' -Variable 'enableLog' New-UiToggle -Text 'Verbose mode' -Variable 'verbose' } .EXAMPLE New-UiExpander -Header 'Details' -IsExpanded -Content { New-UiLabel -Text 'This section starts open' } #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Header, [Parameter(Mandatory)] [scriptblock]$Content, [switch]$IsExpanded, [Parameter()] [string]$Variable, [Parameter()] [hashtable]$WPFProperties ) # Grab session and theme context $session = Assert-UiSession -CallerName 'New-UiExpander' $colors = Get-ThemeColors $parent = $session.CurrentParent # Build the outer container with themed border $container = [System.Windows.Controls.Border]@{ BorderThickness = [System.Windows.Thickness]::new(1) BorderBrush = ConvertTo-UiBrush $colors.Border Background = ConvertTo-UiBrush $colors.ControlBg CornerRadius = [System.Windows.CornerRadius]::new(4) Margin = [System.Windows.Thickness]::new(4) Tag = 'ControlBgBrush' } # Stack panel holds header row and collapsible content $outerStack = [System.Windows.Controls.StackPanel]::new() $container.Child = $outerStack # Chevron glyph rotates 90deg when content is visible $chevron = [System.Windows.Controls.TextBlock]@{ Text = [PsUi.ModuleContext]::GetIcon('ChevronRight') FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') FontSize = 12 Foreground = ConvertTo-UiBrush $colors.SecondaryText VerticalAlignment = 'Center' Margin = [System.Windows.Thickness]::new(0, 0, 8, 0) RenderTransformOrigin = '0.5,0.5' } $chevron.RenderTransform = [System.Windows.Media.RotateTransform]::new(0) # Header label with semibold weight $headerText = [System.Windows.Controls.TextBlock]@{ Text = $Header FontFamily = [System.Windows.Media.FontFamily]::new('Segoe UI Variable, Segoe UI') FontSize = 13 FontWeight = 'SemiBold' Foreground = ConvertTo-UiBrush $colors.ControlFg VerticalAlignment = 'Center' Tag = 'ControlFgBrush' } # Clickable header row with chevron and label $headerPanel = [System.Windows.Controls.StackPanel]@{ Orientation = 'Horizontal' Cursor = 'Hand' Background = [System.Windows.Media.Brushes]::Transparent Margin = [System.Windows.Thickness]::new(10, 8, 10, 8) } [void]$headerPanel.Children.Add($chevron) [void]$headerPanel.Children.Add($headerText) [void]$outerStack.Children.Add($headerPanel) # Content area - hidden by default unless IsExpanded $contentPanel = [System.Windows.Controls.StackPanel]@{ Margin = [System.Windows.Thickness]::new(12, 0, 12, 10) Visibility = if ($IsExpanded) { 'Visible' } else { 'Collapsed' } } [void]$outerStack.Children.Add($contentPanel) if ($IsExpanded) { $chevron.RenderTransform.Angle = 90 } # Toggle visibility and rotate chevron on click $headerPanel.Add_MouseLeftButtonUp({ param($sender, $eventArgs) $outerStack = $sender.Parent $contentPanel = $outerStack.Children[1] $chevronGlyph = $sender.Children[0] if ($contentPanel.Visibility -eq 'Collapsed') { $contentPanel.Visibility = 'Visible' $chevronGlyph.RenderTransform.Angle = 90 } else { $contentPanel.Visibility = 'Collapsed' $chevronGlyph.RenderTransform.Angle = 0 } }) # Hover feedback on header row $headerPanel.Add_MouseEnter({ param($sender, $eventArgs) $sender.Opacity = 0.7 }) $headerPanel.Add_MouseLeave({ param($sender, $eventArgs) $sender.Opacity = 1.0 }) # Register control for variable hydration if name provided if ($Variable) { $session.AddControlSafe($Variable, $container) } # Register themed elements for dynamic switching [PsUi.ThemeEngine]::RegisterElement($container) [PsUi.ThemeEngine]::RegisterElement($headerText) # Attach to parent container if ($parent -is [System.Windows.Controls.Panel]) { [void]$parent.Children.Add($container) } elseif ($parent -is [System.Windows.Controls.ItemsControl]) { [void]$parent.Items.Add($container) } elseif ($parent -is [System.Windows.Controls.ContentControl]) { $parent.Content = $container } # Execute content scriptblock with inner panel as parent context $previousParent = $session.CurrentParent $session.CurrentParent = $contentPanel try { & $Content } finally { $session.CurrentParent = $previousParent } if ($WPFProperties) { Set-UiProperties -Control $container -Properties $WPFProperties } } |