part of 'field.dart'; class AppDropdownSearch extends StatelessWidget { final String label; final String hintText; final String searchHint; final IconData prefixIcon; final List items; final T? selectedItem; final String Function(T) itemAsString; final bool Function(T?, T?)? compareFn; final void Function(T?)? onChanged; final String? Function(T?)? validator; final String emptyMessage; final String Function(String)? emptySearchMessage; final Widget Function(BuildContext, T, bool)? itemBuilder; final bool showSearchBox; final bool isRequired; const AppDropdownSearch({ Key? key, required this.label, required this.hintText, required this.items, required this.itemAsString, this.searchHint = "Cari...", this.prefixIcon = Icons.category_outlined, this.selectedItem, this.compareFn, this.onChanged, this.validator, this.emptyMessage = "Tidak ada data tersedia", this.emptySearchMessage, this.itemBuilder, this.showSearchBox = true, this.isRequired = false, }) : super(key: key); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Row( children: [ Text( label, style: TextStyle( fontSize: 14, color: Colors.black, fontWeight: FontWeight.w600, ), ), if (isRequired) Text( ' *', style: TextStyle( fontSize: 14, color: Colors.red, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 8), // Dropdown DropdownSearch( items: items, selectedItem: selectedItem, // Dropdown properties dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( hintText: hintText, hintStyle: TextStyle(color: AppColor.textSecondary, fontSize: 14), prefixIcon: Icon( prefixIcon, color: AppColor.textSecondary, size: 20, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: AppColor.border, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: AppColor.border, width: 1.5), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: AppColor.primary, width: 2), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: AppColor.error, width: 1.5), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: AppColor.error, width: 2), ), filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), ), ), // Popup properties popupProps: PopupProps.menu( showSearchBox: showSearchBox, searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: searchHint, prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), ), menuProps: MenuProps( backgroundColor: Colors.white, elevation: 8, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), itemBuilder: itemBuilder ?? _defaultItemBuilder, emptyBuilder: (context, searchEntry) { return Container( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.search_off, color: Colors.grey.shade400, size: 48, ), const SizedBox(height: 12), Text( searchEntry.isEmpty ? emptyMessage : emptySearchMessage?.call(searchEntry) ?? "Tidak ditemukan data dengan '$searchEntry'", style: TextStyle( color: Colors.grey.shade600, fontSize: 14, ), textAlign: TextAlign.center, ), ], ), ); }, ), // Item as string (for search functionality) itemAsString: itemAsString, // Comparison function compareFn: compareFn, // On changed callback onChanged: onChanged, // Validator validator: validator, ), ], ); } Widget _defaultItemBuilder(BuildContext context, T item, bool isSelected) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isSelected ? Colors.blue.shade50 : Colors.transparent, border: Border( bottom: BorderSide(color: Colors.grey.shade100, width: 0.5), ), ), child: Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: isSelected ? Colors.blue.shade600 : Colors.grey.shade400, shape: BoxShape.circle, ), ), const SizedBox(width: 12), Expanded( child: Text( itemAsString(item), style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, color: isSelected ? Colors.blue.shade700 : Colors.black87, ), ), ), if (isSelected) Icon(Icons.check, color: Colors.blue.shade600, size: 18), ], ), ); } }