from django.forms.widgets import CheckboxInput from django.utils.safestring import mark_safe from django.utils.html import format_html import json import uuid class ToggleSwitchWidget(CheckboxInput): """ A customizable toggle switch widget for Django forms. Author: Ogliari Natan Date: 11-08-2025 Proposal for Django Foundation Args: size (str): Size of the toggle - 'xs', 'sm', 'md', 'lg', 'xl' or custom tuple (width, height) active_color (str): Color when toggle is active (default: '#10B981') inactive_color (str): Color when toggle is inactive (default: '#9CA3AF') active_text (str): Text to display when active (default: 'Active') inactive_text (str): Text to display when inactive (default: 'Inactive') show_label (bool): Whether to show the text label (default: True) animation_speed (float): Animation speed in seconds (default: 0.3) """ # Predefined sizes with maintained proportions SIZES = { 'xs': (32, 16), # width, height in pixels 'sm': (40, 20), 'md': (48, 24), 'lg': (56, 28), 'xl': (64, 32), } def __init__(self, size='md', active_color="#B910B9", inactive_color='#9CA3AF', active_text='Active', inactive_text='Inactive', show_label=True, animation_speed=0.3, attrs=None): super().__init__(attrs) # Handle size parameter if isinstance(size, str) and size in self.SIZES: self.width, self.height = self.SIZES[size] elif isinstance(size, (tuple, list)) and len(size) == 2: self.width, self.height = size else: self.width, self.height = self.SIZES['md'] # Calculate proportional dimensions self.button_size = int(self.height * 0.75) # Button is 75% of height self.padding = int(self.height * 0.125) # Padding is 12.5% of height self.translate_x = self.width - self.button_size - (self.padding * 2) # Set colors and text self.active_color = active_color self.inactive_color = inactive_color self.active_text = active_text self.inactive_text = inactive_text self.show_label = show_label self.animation_speed = animation_speed def render(self, name, value, attrs=None, renderer=None): if attrs is None: attrs = {} # Generate unique ID for this instance widget_id = attrs.get('id', f'toggle_{uuid.uuid4().hex[:8]}') is_checked = bool(value) checked_attr = 'checked' if is_checked else '' # Build the widget HTML context = { 'widget_id': widget_id, 'name': name, 'checked': checked_attr, 'width': self.width, 'height': self.height, 'button_size': self.button_size, 'padding': self.padding, 'translate_x': self.translate_x, 'active_color': self.active_color, 'inactive_color': self.inactive_color, 'active_text': self.active_text, 'inactive_text': self.inactive_text, 'current_text': self.active_text if is_checked else self.inactive_text, 'current_color': self.active_color if is_checked else self.inactive_color, 'show_label': 'inline-block' if self.show_label else 'none', 'animation_speed': self.animation_speed, 'font_size': max(12, int(self.height * 0.6)), # Proportional font size } html = '''
%(current_text)s
''' % context return mark_safe(html)