| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  | import { Dropdown as BootstrapDropdown, Tooltip } from "bootstrap"; | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  | import { ComponentChildren } from "preact"; | 
					
						
							|  |  |  | import Icon from "./Icon"; | 
					
						
							| 
									
										
										
										
											2025-08-29 12:40:16 +03:00
										 |  |  | import { useEffect, useMemo, useRef, useState, type CSSProperties } from "preact/compat"; | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  | import "./FormList.css"; | 
					
						
							| 
									
										
										
										
											2025-08-24 23:20:26 +03:00
										 |  |  | import { CommandNames } from "../../components/app_context"; | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  | import { useStaticTooltip } from "./hooks"; | 
					
						
							| 
									
										
										
										
											2025-10-08 19:06:19 +03:00
										 |  |  | import { handleRightToLeftPlacement, isMobile } from "../../services/utils"; | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | interface FormListOpts { | 
					
						
							|  |  |  |     children: ComponentChildren; | 
					
						
							|  |  |  |     onSelect?: (value: string) => void; | 
					
						
							| 
									
										
										
										
											2025-08-06 16:16:30 +03:00
										 |  |  |     style?: CSSProperties; | 
					
						
							| 
									
										
										
										
											2025-08-10 20:24:20 +03:00
										 |  |  |     fullHeight?: boolean; | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-10 20:24:20 +03:00
										 |  |  | export default function FormList({ children, onSelect, style, fullHeight }: FormListOpts) { | 
					
						
							| 
									
										
										
										
											2025-08-10 18:56:37 +03:00
										 |  |  |     const wrapperRef = useRef<HTMLDivElement | null>(null); | 
					
						
							|  |  |  |     const triggerRef = useRef<HTMLButtonElement | null>(null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (!triggerRef.current || !wrapperRef.current) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-10-08 19:06:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-10 18:56:37 +03:00
										 |  |  |         const $wrapperRef = $(wrapperRef.current); | 
					
						
							|  |  |  |         const dropdown = BootstrapDropdown.getOrCreateInstance(triggerRef.current); | 
					
						
							|  |  |  |         $wrapperRef.on("hide.bs.dropdown", (e) => e.preventDefault()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return () => { | 
					
						
							|  |  |  |             $wrapperRef.off("hide.bs.dropdown"); | 
					
						
							|  |  |  |             dropdown.dispose(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [ triggerRef, wrapperRef ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-10 20:24:20 +03:00
										 |  |  |     const builtinStyles = useMemo(() => { | 
					
						
							|  |  |  |         const style: CSSProperties = {}; | 
					
						
							|  |  |  |         if (fullHeight) { | 
					
						
							|  |  |  |             style.height = "100%"; | 
					
						
							| 
									
										
										
										
											2025-08-27 16:41:42 +03:00
										 |  |  |             style.overflow = "auto"; | 
					
						
							| 
									
										
										
										
											2025-08-10 20:24:20 +03:00
										 |  |  |         } | 
					
						
							|  |  |  |         return style; | 
					
						
							|  |  |  |     }, [ fullHeight ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2025-08-10 20:24:20 +03:00
										 |  |  |         <div className="dropdownWrapper" ref={wrapperRef} style={builtinStyles}> | 
					
						
							|  |  |  |             <div className="dropdown" style={builtinStyles}> | 
					
						
							| 
									
										
										
										
											2025-08-10 18:56:37 +03:00
										 |  |  |                 <button | 
					
						
							|  |  |  |                     ref={triggerRef} | 
					
						
							|  |  |  |                     type="button" style="display: none;" | 
					
						
							|  |  |  |                     data-bs-toggle="dropdown" data-bs-display="static"> | 
					
						
							|  |  |  |                 </button> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 <div class="dropdown-menu static show" style={{ | 
					
						
							|  |  |  |                     ...style ?? {}, | 
					
						
							| 
									
										
										
										
											2025-08-10 20:24:20 +03:00
										 |  |  |                     ...builtinStyles, | 
					
						
							| 
									
										
										
										
											2025-08-10 18:56:37 +03:00
										 |  |  |                     position: "relative", | 
					
						
							|  |  |  |                 }} onClick={(e) => { | 
					
						
							| 
									
										
										
										
											2025-08-27 18:27:47 +03:00
										 |  |  |                     const dropdownItem = (e.target as HTMLElement).closest(".dropdown-item") as HTMLElement | null; | 
					
						
							|  |  |  |                     const value = dropdownItem?.dataset?.value; | 
					
						
							| 
									
										
										
										
											2025-08-10 18:56:37 +03:00
										 |  |  |                     if (value && onSelect) { | 
					
						
							|  |  |  |                         onSelect(value); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }}> | 
					
						
							|  |  |  |                     {children} | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  |         </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 20:16:06 +03:00
										 |  |  | export interface FormListBadge { | 
					
						
							|  |  |  |     className?: string; | 
					
						
							|  |  |  |     text: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  | interface FormListItemOpts { | 
					
						
							| 
									
										
										
										
											2025-08-06 12:12:37 +03:00
										 |  |  |     children: ComponentChildren; | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  |     icon?: string; | 
					
						
							|  |  |  |     value?: string; | 
					
						
							| 
									
										
										
										
											2025-08-06 16:16:30 +03:00
										 |  |  |     title?: string; | 
					
						
							| 
									
										
										
										
											2025-08-10 17:53:45 +03:00
										 |  |  |     active?: boolean; | 
					
						
							| 
									
										
										
										
											2025-08-21 20:16:06 +03:00
										 |  |  |     badges?: FormListBadge[]; | 
					
						
							| 
									
										
										
										
											2025-08-21 20:30:12 +03:00
										 |  |  |     disabled?: boolean; | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  |     checked?: boolean | null; | 
					
						
							|  |  |  |     selected?: boolean; | 
					
						
							| 
									
										
										
										
											2025-09-21 15:13:08 +03:00
										 |  |  |     container?: boolean; | 
					
						
							| 
									
										
										
										
											2025-08-25 14:48:13 +03:00
										 |  |  |     onClick?: (e: MouseEvent) => void; | 
					
						
							| 
									
										
										
										
											2025-08-24 23:20:26 +03:00
										 |  |  |     triggerCommand?: CommandNames; | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  |     description?: string; | 
					
						
							|  |  |  |     className?: string; | 
					
						
							| 
									
										
										
										
											2025-08-22 12:34:21 +03:00
										 |  |  |     rtl?: boolean; | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  | const TOOLTIP_CONFIG: Partial<Tooltip.Options> = { | 
					
						
							| 
									
										
										
										
											2025-10-08 19:06:19 +03:00
										 |  |  |     placement: handleRightToLeftPlacement("right"), | 
					
						
							|  |  |  |     fallbackPlacements: [ handleRightToLeftPlacement("right") ] | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-21 15:13:08 +03:00
										 |  |  | export function FormListItem({ className, icon, value, title, active, disabled, checked, container, onClick, selected, rtl, triggerCommand, description, ...contentProps }: FormListItemOpts) { | 
					
						
							| 
									
										
										
										
											2025-08-29 12:48:10 +03:00
										 |  |  |     const itemRef = useRef<HTMLLIElement>(null); | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 20:56:37 +03:00
										 |  |  |     if (checked) { | 
					
						
							|  |  |  |         icon = "bx bx-check"; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  |     useStaticTooltip(itemRef, TOOLTIP_CONFIG); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2025-08-29 12:48:10 +03:00
										 |  |  |         <li | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  |             ref={itemRef} | 
					
						
							| 
									
										
										
										
											2025-09-21 15:13:08 +03:00
										 |  |  |             class={`dropdown-item ${active ? "active" : ""} ${disabled ? "disabled" : ""} ${selected ? "selected" : ""} ${container ? "dropdown-container-item": ""} ${className ?? ""}`} | 
					
						
							| 
									
										
										
										
											2025-08-10 18:56:37 +03:00
										 |  |  |             data-value={value} title={title} | 
					
						
							| 
									
										
										
										
											2025-09-21 15:13:08 +03:00
										 |  |  |             tabIndex={container ? -1 : 0} | 
					
						
							| 
									
										
										
										
											2025-08-21 20:56:37 +03:00
										 |  |  |             onClick={onClick} | 
					
						
							| 
									
										
										
										
											2025-08-24 23:20:26 +03:00
										 |  |  |             data-trigger-command={triggerCommand} | 
					
						
							| 
									
										
										
										
											2025-08-22 12:34:21 +03:00
										 |  |  |             dir={rtl ? "rtl" : undefined} | 
					
						
							| 
									
										
										
										
											2025-08-10 18:56:37 +03:00
										 |  |  |         > | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  |             <Icon icon={icon} />  | 
					
						
							| 
									
										
										
										
											2025-08-29 12:48:10 +03:00
										 |  |  |             {description ? ( | 
					
						
							|  |  |  |                 <div> | 
					
						
							|  |  |  |                     <FormListContent description={description} {...contentProps} /> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             ) : ( | 
					
						
							|  |  |  |                 <FormListContent description={description} {...contentProps} /> | 
					
						
							|  |  |  |             )} | 
					
						
							|  |  |  |         </li> | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  |     ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 12:48:10 +03:00
										 |  |  | function FormListContent({ children, badges, description }: Pick<FormListItemOpts, "children" | "badges" | "description">) { | 
					
						
							|  |  |  |     return <> | 
					
						
							|  |  |  |         {children} | 
					
						
							|  |  |  |         {badges && badges.map(({ className, text }) => ( | 
					
						
							|  |  |  |             <span className={`badge ${className ?? ""}`}>{text}</span> | 
					
						
							|  |  |  |         ))} | 
					
						
							|  |  |  |         {description && <div className="description">{description}</div>} | 
					
						
							|  |  |  |     </>; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 11:39:31 +03:00
										 |  |  | interface FormListHeaderOpts { | 
					
						
							|  |  |  |     text: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function FormListHeader({ text }: FormListHeaderOpts) { | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |         <li> | 
					
						
							|  |  |  |             <h6 className="dropdown-header">{text}</h6> | 
					
						
							|  |  |  |         </li> | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-08-21 20:30:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 12:34:21 +03:00
										 |  |  | export function FormDropdownDivider() { | 
					
						
							| 
									
										
										
										
											2025-08-21 20:30:12 +03:00
										 |  |  |     return <div className="dropdown-divider" />; | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function FormDropdownSubmenu({ icon, title, children }: { icon: string, title: ComponentChildren, children: ComponentChildren }) { | 
					
						
							| 
									
										
										
										
											2025-08-29 12:40:16 +03:00
										 |  |  |     const [ openOnMobile, setOpenOnMobile ] = useState(false); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2025-08-29 12:40:16 +03:00
										 |  |  |         <li className={`dropdown-item dropdown-submenu ${openOnMobile ? "submenu-open" : ""}`}> | 
					
						
							|  |  |  |             <span | 
					
						
							|  |  |  |                 className="dropdown-toggle" | 
					
						
							|  |  |  |                 onClick={(e) => { | 
					
						
							|  |  |  |                     e.stopPropagation(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (isMobile()) { | 
					
						
							|  |  |  |                         setOpenOnMobile(!openOnMobile); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }} | 
					
						
							|  |  |  |             > | 
					
						
							| 
									
										
										
										
											2025-08-29 15:01:29 +03:00
										 |  |  |                 <Icon icon={icon} />{" "} | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  |                 {title} | 
					
						
							|  |  |  |             </span> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 12:40:16 +03:00
										 |  |  |             <ul className={`dropdown-menu ${openOnMobile ? "show" : ""}`}> | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  |                 {children} | 
					
						
							|  |  |  |             </ul> | 
					
						
							|  |  |  |         </li> | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2025-10-08 19:06:19 +03:00
										 |  |  | } |