katira/function-gemma
0
1import gradio as gr2 3 4class ColoredDropdown(gr.HTML):5 def __init__(6 self,7 choices: list[str] | None = None,8 *,9 placeholder: str = "Select...",10 value: str | None = None,11 colors: list[str] | None = None,12 label: str | None = None,13 **kwargs,14 ):15 html_template = """16 <div class="colored-dropdown-container">17 ${label ? `<span class="dropdown-label">${label}</span>` : ''}18 <div class="dropdown-wrapper">19 <button type="button" class="dropdown-trigger">20 <span class="selected-display">21 ${value ? `<span class="color-dot" style="background: ${colors[choices.indexOf(value)]};"></span>${value}` : `<span class="placeholder">${placeholder}</span>`}22 </span>23 <svg class="dropdown-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">24 <polyline points="6 9 12 15 18 9"></polyline>25 </svg>26 </button>27 <div class="dropdown-menu">28 ${choices.map((choice, i) => `29 <div class="dropdown-item ${value === choice ? 'selected' : ''}" data-value="${choice}" data-color="${colors[i]}">30 <span class="color-dot" style="background: ${colors[i]};"></span>31 <span class="item-text">${choice}</span>32 </div>33 `).join('')}34 </div>35 </div>36 </div>37 """38 39 css_template = """40 .colored-dropdown-container {41 display: flex;42 flex-direction: column;43 gap: var(--spacing-sm);44 overflow: visible;45 }46 .dropdown-label {47 color: var(--block-title-text-color);48 font-size: var(--text-sm);49 font-weight: 500;50 }51 .dropdown-wrapper {52 position: relative;53 z-index: 1;54 }55 .dropdown-trigger {56 width: 100%;57 display: flex;58 align-items: center;59 justify-content: space-between;60 padding: 8px 12px;61 border: 1px solid var(--border-color-primary);62 border-radius: var(--radius-md);63 background: var(--input-background-fill);64 color: var(--body-text-color);65 cursor: pointer;66 font-size: var(--text-md);67 text-align: left;68 transition: border-color 0.15s ease;69 }70 .dropdown-trigger:hover {71 border-color: var(--border-color-accent);72 }73 .dropdown-trigger:focus {74 outline: none;75 border-color: var(--color-accent);76 box-shadow: 0 0 0 2px var(--color-accent-soft);77 }78 .selected-display {79 display: flex;80 align-items: center;81 gap: 8px;82 flex: 1;83 overflow: hidden;84 text-overflow: ellipsis;85 white-space: nowrap;86 }87 .placeholder {88 color: var(--input-placeholder-color);89 }90 .dropdown-arrow {91 width: 16px;92 height: 16px;93 flex-shrink: 0;94 transition: transform 0.2s ease;95 }96 .dropdown-wrapper.open .dropdown-arrow {97 transform: rotate(180deg);98 }99 .dropdown-menu {100 position: fixed;101 max-height: 240px;102 overflow-y: auto;103 background: var(--background-fill-primary);104 border: 1px solid var(--border-color-primary);105 border-radius: var(--radius-md);106 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);107 z-index: 9999;108 display: none;109 }110 .dropdown-wrapper.open .dropdown-menu {111 display: block;112 }113 .dropdown-item {114 display: flex;115 align-items: center;116 gap: 8px;117 padding: 8px 12px;118 cursor: pointer;119 transition: background 0.1s ease;120 }121 .dropdown-item:hover {122 background: var(--background-fill-secondary);123 }124 .dropdown-item.selected {125 background: var(--color-accent-soft);126 }127 .item-text {128 overflow: hidden;129 text-overflow: ellipsis;130 white-space: nowrap;131 }132 .color-dot {133 width: 10px;134 height: 10px;135 border-radius: 50%;136 flex-shrink: 0;137 }138 """139 140 js_on_load = """141 const wrapper = element.querySelector('.dropdown-wrapper');142 const triggerButton = element.querySelector('.dropdown-trigger');143 const menu = element.querySelector('.dropdown-menu');144 const selectedDisplay = element.querySelector('.selected-display');145 146 function positionMenu() {147 const rect = triggerButton.getBoundingClientRect();148 menu.style.position = 'fixed';149 menu.style.top = (rect.bottom + 4) + 'px';150 menu.style.left = rect.left + 'px';151 menu.style.width = rect.width + 'px';152 menu.style.right = 'auto';153 }154 155 triggerButton.addEventListener('click', (e) => {156 e.stopPropagation();157 wrapper.classList.toggle('open');158 if (wrapper.classList.contains('open')) {159 positionMenu();160 }161 });162 163 menu.addEventListener('click', (e) => {164 const item = e.target.closest('.dropdown-item');165 if (!item) return;166 167 const value = item.dataset.value;168 const color = item.dataset.color;169 170 menu.querySelectorAll('.dropdown-item').forEach(el => el.classList.remove('selected'));171 item.classList.add('selected');172 173 selectedDisplay.innerHTML = `<span class="color-dot" style="background: ${color};"></span>${value}`;174 175 props.value = value;176 trigger('input');177 178 wrapper.classList.remove('open');179 });180 181 document.addEventListener('click', (e) => {182 if (!element.contains(e.target)) {183 wrapper.classList.remove('open');184 }185 });186 187 window.addEventListener('resize', () => {188 if (wrapper.classList.contains('open')) {189 positionMenu();190 }191 });192 193 window.addEventListener('scroll', () => {194 if (wrapper.classList.contains('open')) {195 positionMenu();196 }197 }, true);198 199 triggerButton.addEventListener('keydown', (e) => {200 if (e.key === 'Enter' || e.key === ' ') {201 e.preventDefault();202 wrapper.classList.toggle('open');203 if (wrapper.classList.contains('open')) {204 positionMenu();205 }206 } else if (e.key === 'Escape') {207 wrapper.classList.remove('open');208 }209 });210 """211 212 super().__init__(213 value=value,214 html_template=html_template,215 css_template=css_template,216 js_on_load=js_on_load,217 choices=choices,218 colors=colors,219 label=label,220 placeholder=placeholder,221 **kwargs,222 )223 224 def api_info(self):225 return {226 "enum": self.props["choices"],227 "title": "Dropdown",228 "type": "string",229 }230 231 232if __name__ == "__main__":233 234 def generate_color_variants(color: str, count: int):235 if color.startswith("rgb"):236 rgb_values = (237 color.replace("rgba", "").replace("rgb", "").strip("()").split(",")238 )239 r, g, b = (240 int(float(rgb_values[0])),241 int(float(rgb_values[1])),242 int(float(rgb_values[2])),243 )244 else:245 r = int(color[1:3], 16)246 g = int(color[3:5], 16)247 b = int(color[5:7], 16)248 249 colors = []250 for i in range(count):251 factor = 0.3 + (0.7 * i / max(count - 1, 1))252 nr = int(r * factor + (255 - r * factor) * (1 - factor) * 0.3)253 ng = int(g * factor + (255 - g * factor) * (1 - factor) * 0.3)254 nb = int(b * factor + (255 - b * factor) * (1 - factor) * 0.3)255 nr, ng, nb = min(255, nr), min(255, ng), min(255, nb)256 colors.append(f"#{nr:02x}{ng:02x}{nb:02x}")257 return colors258 259 def update_dropdown(color: str, s: int):260 items = [f"Item {i + 1} ({s + 1})" for i in range(12)]261 colors = generate_color_variants(color, len(items))262 return ColoredDropdown(263 choices=items,264 colors=colors,265 label=f"Select Item ({s + 1})",266 ), s + 1267 268 with gr.Blocks() as demo:269 s = gr.State(0)270 with gr.Row():271 with gr.Column():272 cp = gr.ColorPicker(value="#FF0000", label="Base Color")273 with gr.Column(scale=2):274 items = [f"Item {i + 1}" for i in range(15)]275 colors = generate_color_variants("#FF0000", len(items))276 dropdown = ColoredDropdown(277 choices=items,278 colors=colors,279 label="Select Item",280 value=items[0],281 )282 output = gr.Textbox(label="Selected Value")283 dropdown.input(lambda x: x, inputs=dropdown, outputs=output)284 cp.change(285 update_dropdown,286 inputs=[cp, s],287 outputs=[dropdown, s],288 show_progress="hidden",289 )290 demo.launch()291 