CoolFace
Apppublic

uxoxo/eb2ab

sourceHugging Faceapache-2.0updated 11mo agoView on Hugging Face
0likes
components.py515 linesDownload Raw Back to design_system
1"""2Design System Components - Reusable UI building blocks for Gradio3 4This module provides factory functions to create consistent UI components5following the OOUX-based design system principles.6 7Author: Claude Code8Date: 2025-10-219"""10 11import gradio as gr12from typing import Optional, List, Dict, Any, Callable13from .design_tokens import DesignTokens as DT14 15 16class Components:17    """Factory class for creating design-system-compliant Gradio components"""18 19    @staticmethod20    def _merge_elem_classes(base_classes: List[str], kwargs: dict) -> List[str]:21        """22        Helper to merge elem_classes from kwargs into base classes.23        Removes elem_classes from kwargs to prevent duplicate argument error.24 25        Args:26            base_classes: List of CSS classes from component defaults27            kwargs: Keyword arguments that may contain elem_classes28 29        Returns:30            Merged list of CSS classes31        """32        elem_classes = base_classes.copy()33 34        if 'elem_classes' in kwargs:35            custom_classes = kwargs.pop('elem_classes')36            if isinstance(custom_classes, list):37                elem_classes.extend(custom_classes)38            elif isinstance(custom_classes, str):39                elem_classes.append(custom_classes)40 41        return elem_classes42 43    # ========== ATOMS ==========44 45    @staticmethod46    def button(47        label: str,48        variant: str = "primary",49        size: str = "md",50        icon: Optional[str] = None,51        **kwargs52    ) -> gr.Button:53        """54        Create a styled button following design system55 56        Args:57            label: Button text58            variant: primary | secondary | success | destructive | outline | ghost59            size: sm | md | lg60            icon: Optional icon name/emoji61            **kwargs: Additional Gradio button parameters62 63        Returns:64            gr.Button component65        """66        # Build CSS class based on variant and size67        base_classes = ["btn", f"btn-{variant}"]68        if size != "md":69            base_classes.append(f"btn-{size}")70 71        # Merge with any custom elem_classes from kwargs72        elem_classes = Components._merge_elem_classes(base_classes, kwargs)73 74        # Add icon to label if provided75        display_label = f"{icon} {label}" if icon else label76 77        return gr.Button(78            value=display_label,79            elem_classes=elem_classes,80            **kwargs81        )82 83    @staticmethod84    def progress_bar(85        value: float = 0,86        state: str = "default",87        **kwargs88    ) -> gr.Slider:89        """90        Create a progress bar91 92        Args:93            value: Progress value (0-100)94            state: default | processing | complete | error95            **kwargs: Additional parameters96 97        Returns:98            gr.Slider configured as progress bar99        """100        state_class = f"progress-bar-fill {state}" if state != "default" else "progress-bar-fill"101 102        # Merge with any custom elem_classes from kwargs103        elem_classes = Components._merge_elem_classes(["progress-bar"], kwargs)104 105        return gr.Slider(106            minimum=0,107            maximum=100,108            value=value,109            label=None,110            interactive=False,111            elem_classes=elem_classes,112            **kwargs113        )114 115    @staticmethod116    def tag(117        text: str,118        state: str = "pending",119        **kwargs120    ) -> gr.HTML:121        """122        Create a state tag/chip123 124        Args:125            text: Tag text126            state: pending | processing | modernized | approved | error127            **kwargs: Additional parameters128 129        Returns:130            gr.HTML component rendering tag131        """132        colors = DT.get_state_colors(state)133 134        html = f"""135        <span class="tag tag-{state}" style="136            background: {colors['bg']};137            color: {colors['text']};138            border: 1px solid {colors['border']};139        ">140            {text}141        </span>142        """143 144        return gr.HTML(html, **kwargs)145 146    @staticmethod147    def spinner(size: int = 20, **kwargs) -> gr.HTML:148        """149        Create a loading spinner150 151        Args:152            size: Spinner size in pixels153            **kwargs: Additional parameters154 155        Returns:156            gr.HTML component with animated spinner157        """158        html = f"""159        <div class="spinner" style="160            width: {size}px;161            height: {size}px;162        "></div>163        """164 165        return gr.HTML(html, **kwargs)166 167    # ========== MOLECULES ==========168 169    @staticmethod170    def card(171        title: str,172        content: Optional[gr.Component] = None,173        actions: Optional[List[gr.Button]] = None,174        **kwargs175    ) -> gr.Column:176        """177        Create a card container178 179        Args:180            title: Card title181            content: Card body content (Gradio component)182            actions: List of action buttons for footer183            **kwargs: Additional parameters184 185        Returns:186            gr.Column configured as card187        """188        with gr.Column(elem_classes=["card"], **kwargs) as card:189            # Header190            with gr.Row(elem_classes=["card-header"]):191                gr.Markdown(f"### {title}", elem_classes=["card-title"])192 193            # Body194            if content:195                with gr.Column(elem_classes=["card-body"]):196                    content197 198            # Footer with actions199            if actions:200                with gr.Row(elem_classes=["card-footer"]):201                    for action in actions:202                        action203 204        return card205 206    @staticmethod207    def estimate_box(208        cost: str,209        time: str,210        details: Optional[str] = None,211        **kwargs212    ) -> gr.Column:213        """214        Create an estimate/summary box215 216        Args:217            cost: Cost estimate string218            time: Time estimate string219            details: Optional detailed breakdown220            **kwargs: Additional parameters221 222        Returns:223            gr.Column styled as estimate box224        """225        with gr.Column(elem_classes=["estimate-box"], **kwargs) as box:226            gr.Markdown(227                f'<div class="cost-estimate">{cost}</div>',228                elem_classes=["cost-estimate"]229            )230            gr.Markdown(231                f'<div class="time-estimate">{time}</div>',232                elem_classes=["time-estimate"]233            )234            if details:235                gr.Markdown(details)236 237        return box238 239    @staticmethod240    def alert(241        message: str,242        variant: str = "info",243        icon: Optional[str] = None,244        **kwargs245    ) -> gr.HTML:246        """247        Create an alert/notification248 249        Args:250            message: Alert message251            variant: info | success | warning | error252            icon: Optional icon/emoji253            **kwargs: Additional parameters254 255        Returns:256            gr.HTML component257        """258        icons = {259            "info": "ℹ️",260            "success": "✅",261            "warning": "⚠️",262            "error": "❌"263        }264 265        display_icon = icon or icons.get(variant, "")266 267        html = f"""268        <div class="alert alert-{variant}">269            <span style="font-size: 20px;">{display_icon}</span>270            <div>{message}</div>271        </div>272        """273 274        return gr.HTML(html, **kwargs)275 276    @staticmethod277    def status_indicator(278        label: str,279        state: str,280        progress: Optional[float] = None,281        **kwargs282    ) -> gr.Column:283        """284        Create a status indicator with optional progress285 286        Args:287            label: Status label288            state: Current state289            progress: Optional progress value (0-100)290            **kwargs: Additional parameters291 292        Returns:293            gr.Column with status display294        """295        with gr.Column(**kwargs) as status:296            with gr.Row():297                Components.tag(label, state)298                if progress is not None:299                    gr.Markdown(f"{progress:.0f}%")300 301            if progress is not None:302                Components.progress_bar(progress, state)303 304        return status305 306    # ========== ORGANISMS ==========307 308    @staticmethod309    def project_card(310        title: str,311        progress: float,312        state: str,313        metadata: Optional[Dict[str, Any]] = None,314        on_click: Optional[Callable] = None,315        **kwargs316    ) -> gr.Column:317        """318        Create a project card319 320        Args:321            title: Project title322            progress: Progress percentage (0-100)323            state: Project state324            metadata: Optional metadata dict325            on_click: Optional click handler326            **kwargs: Additional parameters327 328        Returns:329            gr.Column configured as project card330        """331        with gr.Column(elem_classes=["project-card"], **kwargs) as card:332            # Header333            with gr.Row(elem_classes=["project-card-header"]):334                gr.Markdown(f"### {title}", elem_classes=["project-card-title"])335                Components.tag(state.upper(), state)336 337            # Metadata338            if metadata:339                with gr.Row():340                    for key, value in metadata.items():341                        gr.Markdown(f"**{key}:** {value}", elem_classes=["body-small"])342 343            # Progress344            with gr.Column(elem_classes=["project-card-progress"]):345                gr.Markdown(f"Progress: {progress:.0f}%", elem_classes=["body-small"])346                Components.progress_bar(progress, state)347 348            # Actions349            with gr.Row(elem_classes=["project-card-footer"]):350                Components.button("Resume", variant="primary", size="sm")351                Components.button("Export", variant="outline", size="sm")352 353        return card354 355    @staticmethod356    def chunk_row_table(357        chunks: List[Dict[str, Any]],358        on_row_click: Optional[Callable] = None,359        **kwargs360    ) -> gr.DataFrame:361        """362        Create a data table for chunks363 364        Args:365            chunks: List of chunk dictionaries366            on_row_click: Optional row click handler367            **kwargs: Additional parameters368 369        Returns:370            gr.DataFrame styled as data table371        """372        # Convert chunks to DataFrame format373        import pandas as pd374 375        df = pd.DataFrame(chunks)376 377        return gr.DataFrame(378            value=df,379            interactive=False,380            elem_classes=["data-table"],381            **kwargs382        )383 384    @staticmethod385    def split_pane_editor(386        left_content: str,387        right_content: str,388        left_label: str = "Original",389        right_label: str = "Modernized",390        **kwargs391    ) -> gr.Row:392        """393        Create a split-pane comparison editor394 395        Args:396            left_content: Left pane content397            right_content: Right pane content398            left_label: Left pane label399            right_label: Right pane label400            **kwargs: Additional parameters401 402        Returns:403            gr.Row with split panes404        """405        with gr.Row(elem_classes=["split-pane"], **kwargs) as pane:406            with gr.Column(elem_classes=["split-pane-panel"]):407                gr.Markdown(f"### {left_label}")408                gr.Textbox(409                    value=left_content,410                    lines=20,411                    interactive=False412                )413 414            with gr.Column(elem_classes=["split-pane-panel"]):415                gr.Markdown(f"### {right_label}")416                gr.Textbox(417                    value=right_content,418                    lines=20,419                    interactive=True420                )421 422        return pane423 424    @staticmethod425    def voice_selector(426        voices: List[Dict[str, str]],427        selected_voice: Optional[str] = None,428        on_select: Optional[Callable] = None,429        **kwargs430    ) -> gr.Column:431        """432        Create a voice selector grid433 434        Args:435            voices: List of voice dicts with 'name' and 'preview' keys436            selected_voice: Currently selected voice name437            on_select: Selection handler438            **kwargs: Additional parameters439 440        Returns:441            gr.Column with voice cards442        """443        with gr.Column(**kwargs) as selector:444            gr.Markdown("### Select Voice")445 446            with gr.Row():447                for voice in voices:448                    is_selected = voice['name'] == selected_voice449                    classes = ["voice-card"]450                    if is_selected:451                        classes.append("selected")452 453                    with gr.Column(elem_classes=classes):454                        gr.Markdown(f"**{voice['name']}**")455                        if 'preview' in voice:456                            gr.Audio(voice['preview'], label=None)457                        Components.button(458                            "Select" if not is_selected else "Selected ✓",459                            variant="primary" if not is_selected else "success",460                            size="sm"461                        )462 463        return selector464 465    @staticmethod466    def processing_queue_bar(467        current_job: str,468        progress: float,469        visible: bool = True,470        **kwargs471    ) -> gr.Column:472        """473        Create global processing queue bar474 475        Args:476            current_job: Current job description477            progress: Progress value (0-100)478            visible: Whether bar is visible479            **kwargs: Additional parameters480 481        Returns:482            gr.Column styled as queue bar483        """484        classes = ["processing-queue-bar"]485        if visible:486            classes.append("visible")487 488        with gr.Column(elem_classes=classes, **kwargs) as bar:489            with gr.Row(elem_classes=["processing-queue-content"]):490                with gr.Column():491                    Components.spinner()492                    gr.Markdown(current_job)493 494                gr.Markdown(f"{progress:.0f}%")495 496                Components.button("Pause", variant="ghost", size="sm", icon="⏸️")497 498        return bar499 500 501# Convenience exports502button = Components.button503progress_bar = Components.progress_bar504tag = Components.tag505spinner = Components.spinner506card = Components.card507estimate_box = Components.estimate_box508alert = Components.alert509status_indicator = Components.status_indicator510project_card = Components.project_card511chunk_row_table = Components.chunk_row_table512split_pane_editor = Components.split_pane_editor513voice_selector = Components.voice_selector514processing_queue_bar = Components.processing_queue_bar515