Repository: StacDev/stac
Pull request #216: Add support for the DropdownMenu widget
Author: login: Odinachi
Created: 2025-04-14T08:51:16Z | Merged: 2025-04-14T19:57:31Z

<!--
  Thanks for contributing!

  Provide a description of your changes below and a general summary in the title

  Please look at the following checklist to ensure that your PR can be accepted quickly:
-->

## Description

<!--- Describe your changes in detail -->

## Related Issues

<!--- List the related issues to this PR -->
Closes #206

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x] New feature (non-breaking change which adds functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Code refactor
- [ ] Build configuration change
- [x] Documentation
- [x] Chore


Linked issue #206: feat: Add support for the DropdownMenu widget
**Description**

Add support for the [DropdownMenu widget](https://api.flutter.dev/flutter/material/DropdownMenu-class.html).

**Requirements**

- [ ] Create widget parser
- [ ] Add an example in the [Stac Gallery App](https://github.com/StacDev/stac/tree/dev/examples/stac_gallery)
- [ ] Add documentation in the [Stac Docs website](https://github.com/StacDev/stac/tree/dev/website/docs)
Linked issue comments: user: login: Odinachi
created_at: 2025-04-12T10:11:51Z
body: assign this to me @divyanshub024 
author_association: CONTRIBUTOR
user: login: divyanshub024
created_at: 2025-04-12T13:14:54Z
body: Thanks, @Odinachi!! I've assigned it to you.
author_association: MEMBER

Diff:
diff --git a/examples/stac_gallery/assets/json/dropdown_menu_view_example.json b/examples/stac_gallery/assets/json/dropdown_menu_view_example.json
new file mode 100644
index 000000000..1859ba16c
--- /dev/null
+++ b/examples/stac_gallery/assets/json/dropdown_menu_view_example.json
@@ -0,0 +1,65 @@
+{
+  "type": "scaffold",
+  "appBar": {
+    "type": "appBar",
+    "title": {
+      "type": "text",
+      "data": "Stac DropDown"
+    }
+  },
+  "body": {
+    "type": "column",
+    "children": [
+      {
+        "type": "dropdownMenu",
+        "leadingIcon": {
+          "type": "icon",
+          "iconType": "material",
+          "icon": "arrow_downward",
+          "size": 32
+        },
+        "trailingIcon": {
+          "type": "icon",
+          "iconType": "material",
+          "icon": "double_arrow",
+          "size": 32
+        },
+        "initialSelection": "b",
+        "dropdownMenuEntries": [
+          {
+            "label": "A",
+            "value": "a",
+            "leadingIcon": {
+              "type": "icon",
+              "iconType": "material",
+              "icon": "arrow_downward_sharp",
+              "size": 32
+            },
+            "trailingIcon": {
+              "type": "icon",
+              "iconType": "material",
+              "icon": "arrow_forward_ios",
+              "size": 32
+            }
+          },
+          {
+            "label": "B",
+            "value": "b",
+            "leadingIcon": {
+              "type": "icon",
+              "iconType": "material",
+              "icon": "arrow_downward_sharp",
+              "size": 32
+            },
+            "trailingIcon": {
+              "type": "icon",
+              "iconType": "material",
+              "icon": "arrow_forward_ios",
+              "size": 32
+            }
+          }
+        ]
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/examples/stac_gallery/assets/json/home_screen.json b/examples/stac_gallery/assets/json/home_screen.json
index e8fe6286e..54668640a 100644
--- a/examples/stac_gallery/assets/json/home_screen.json
+++ b/examples/stac_gallery/assets/json/home_screen.json
@@ -1282,5 +1282,27 @@
         "assetPath": "assets/json/dynamic_list_view_example.json"
       }
     }
+  },
+  {
+    "type": "listTile",
+    "leading": {
+      "type": "icon",
+      "icon": "api"
+    },
+    "title": {
+      "type": "text",
+      "data": "Stac DropDown Menu"
+    },
+    "subtitle": {
+      "type": "text",
+      "data": "Drop down menu to select items"
+    },
+    "onTap": {
+      "actionType": "navigate",
+      "widgetJson": {
+        "type": "exampleScreen",
+        "assetPath": "assets/json/dropdown_menu_view_example.json"
+      }
+    }
   }
 ]
\ No newline at end of file
diff --git a/packages/stac/lib/src/framework/stac.dart b/packages/stac/lib/src/framework/stac.dart
index 4e28a9e5a..0933bd496 100644
--- a/packages/stac/lib/src/framework/stac.dart
+++ b/packages/stac/lib/src/framework/stac.dart
@@ -9,6 +9,7 @@ import 'package:stac/src/action_parsers/stac_network_request/stac_network_reques
 import 'package:stac/src/framework/stac_registry.dart';
 import 'package:stac/src/parsers/parsers.dart';
 import 'package:stac/src/parsers/stac_custom_scroll_view/stac_custom_scroll_view_parser.dart';
+import 'package:stac/src/parsers/stac_dropdown_menu/stac_dropdown_menu_parser.dart';
 import 'package:stac/src/parsers/stac_sliver_app_bar/stac_sliver_app_bar_parser.dart';
 import 'package:stac/src/services/stac_network_service.dart';
 import 'package:stac/src/utils/log.dart';
@@ -99,6 +100,7 @@ class Stac {
     const StacFittedBoxParser(),
     const StacLimitedBoxParser(),
     const StacDynamicViewParser(),
+    const StacDropdownMenuParser()
   ];
 
   static final _actionParsers = <StacActionParser>[
diff --git a/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.dart b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.dart
new file mode 100644
index 000000000..668cfc56b
--- /dev/null
+++ b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.dart
@@ -0,0 +1,47 @@
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:stac/src/parsers/parsers.dart';
+import 'package:stac/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart'
+    show StacDropdownMenuEntry;
+import 'package:stac/src/parsers/stac_input_decoration_theme/stac_input_decoration_theme.dart';
+import 'package:stac/src/parsers/stac_offset/stac_offset.dart';
+import 'package:stac/src/utils/text_input_utils.dart';
+
+export 'package:stac/src/parsers/stac_container/stac_container_parser.dart';
+
+part 'stac_dropdown_menu.freezed.dart';
+
+part 'stac_dropdown_menu.g.dart';
+
+@freezed
+abstract class StacDropdownMenu with _$StacDropdownMenu {
+  const factory StacDropdownMenu({
+    @Default(true) bool enabled,
+    double? width,
+    double? menuHeight,
+    Map<String, dynamic>? leadingIcon,
+    Map<String, dynamic>? trailingIcon,
+    Map<String, dynamic>? label,
+    String? hintText,
+    String? helperText,
+    String? errorText,
+    Map<String, dynamic>? selectedTrailingIcon,
+    bool? enableFilter,
+    bool? enableSearch,
+    StacTextInputType? keyboardType,
+    StacTextStyle? textStyle,
+    @Default(TextAlign.start) TextAlign textAlign,
+    StacInputDecorationTheme? inputDecorationTheme,
+    @Default([]) List<StacInputFormatter> inputFormatters,
+    StacOffset? alignmentOffset,
+    StacEdgeInsets? expandedInsets,
+    bool? requestFocusOnTap,
+    dynamic initialSelection,
+    @Default([]) List<StacDropdownMenuEntry> dropdownMenuEntries,
+    @Default(DropdownMenuCloseBehavior.all)
+    DropdownMenuCloseBehavior? closeBehavior,
+  }) = _StacDropdownMenu;
+
+  factory StacDropdownMenu.fromJson(Map<String, dynamic> json) =>
+      _$StacDropdownMenuFromJson(json);
+}
diff --git a/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.freezed.dart b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.freezed.dart
new file mode 100644
index 000000000..b0527a947
--- /dev/null
+++ b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.freezed.dart
@@ -0,0 +1,850 @@
+// dart format width=80
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'stac_dropdown_menu.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity<T>(T value) => value;
+
+/// @nodoc
+mixin _$StacDropdownMenu {
+  bool get enabled;
+  double? get width;
+  double? get menuHeight;
+  Map<String, dynamic>? get leadingIcon;
+  Map<String, dynamic>? get trailingIcon;
+  Map<String, dynamic>? get label;
+  String? get hintText;
+  String? get helperText;
+  String? get errorText;
+  Map<String, dynamic>? get selectedTrailingIcon;
+  bool? get enableFilter;
+  bool? get enableSearch;
+  StacTextInputType? get keyboardType;
+  StacTextStyle? get textStyle;
+  TextAlign get textAlign;
+  StacInputDecorationTheme? get inputDecorationTheme;
+  List<StacInputFormatter> get inputFormatters;
+  StacOffset? get alignmentOffset;
+  StacEdgeInsets? get expandedInsets;
+  bool? get requestFocusOnTap; // TextEditingController? controller;
+  dynamic get initialSelection; // ValueChanged<T?>? onSelected;
+// FocusNode? focusNode;
+  List<StacDropdownMenuEntry>
+      get dropdownMenuEntries; //    FilterCallback<T>? filterCallback;
+//    SearchCallback<T>? searchCallback,
+//
+//
+//
+  DropdownMenuCloseBehavior? get closeBehavior;
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @pragma('vm:prefer-inline')
+  $StacDropdownMenuCopyWith<StacDropdownMenu> get copyWith =>
+      _$StacDropdownMenuCopyWithImpl<StacDropdownMenu>(
+          this as StacDropdownMenu, _$identity);
+
+  /// Serializes this StacDropdownMenu to a JSON map.
+  Map<String, dynamic> toJson();
+
+  @override
+  bool operator ==(Object other) {
+    return identical(this, other) ||
+        (other.runtimeType == runtimeType &&
+            other is StacDropdownMenu &&
+            (identical(other.enabled, enabled) || other.enabled == enabled) &&
+            (identical(other.width, width) || other.width == width) &&
+            (identical(other.menuHeight, menuHeight) ||
+                other.menuHeight == menuHeight) &&
+            const DeepCollectionEquality()
+                .equals(other.leadingIcon, leadingIcon) &&
+            const DeepCollectionEquality()
+                .equals(other.trailingIcon, trailingIcon) &&
+            const DeepCollectionEquality().equals(other.label, label) &&
+            (identical(other.hintText, hintText) ||
+                other.hintText == hintText) &&
+            (identical(other.helperText, helperText) ||
+                other.helperText == helperText) &&
+            (identical(other.errorText, errorText) ||
+                other.errorText == errorText) &&
+            const DeepCollectionEquality()
+                .equals(other.selectedTrailingIcon, selectedTrailingIcon) &&
+            (identical(other.enableFilter, enableFilter) ||
+                other.enableFilter == enableFilter) &&
+            (identical(other.enableSearch, enableSearch) ||
+                other.enableSearch == enableSearch) &&
+            (identical(other.keyboardType, keyboardType) ||
+                other.keyboardType == keyboardType) &&
+            (identical(other.textStyle, textStyle) ||
+                other.textStyle == textStyle) &&
+            (identical(other.textAlign, textAlign) ||
+                other.textAlign == textAlign) &&
+            (identical(other.inputDecorationTheme, inputDecorationTheme) ||
+                other.inputDecorationTheme == inputDecorationTheme) &&
+            const DeepCollectionEquality()
+                .equals(other.inputFormatters, inputFormatters) &&
+            (identical(other.alignmentOffset, alignmentOffset) ||
+                other.alignmentOffset == alignmentOffset) &&
+            (identical(other.expandedInsets, expandedInsets) ||
+                other.expandedInsets == expandedInsets) &&
+            (identical(other.requestFocusOnTap, requestFocusOnTap) ||
+                other.requestFocusOnTap == requestFocusOnTap) &&
+            const DeepCollectionEquality()
+                .equals(other.initialSelection, initialSelection) &&
+            const DeepCollectionEquality()
+                .equals(other.dropdownMenuEntries, dropdownMenuEntries) &&
+            (identical(other.closeBehavior, closeBehavior) ||
+                other.closeBehavior == closeBehavior));
+  }
+
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  int get hashCode => Object.hashAll([
+        runtimeType,
+        enabled,
+        width,
+        menuHeight,
+        const DeepCollectionEquality().hash(leadingIcon),
+        const DeepCollectionEquality().hash(trailingIcon),
+        const DeepCollectionEquality().hash(label),
+        hintText,
+        helperText,
+        errorText,
+        const DeepCollectionEquality().hash(selectedTrailingIcon),
+        enableFilter,
+        enableSearch,
+        keyboardType,
+        textStyle,
+        textAlign,
+        inputDecorationTheme,
+        const DeepCollectionEquality().hash(inputFormatters),
+        alignmentOffset,
+        expandedInsets,
+        requestFocusOnTap,
+        const DeepCollectionEquality().hash(initialSelection),
+        const DeepCollectionEquality().hash(dropdownMenuEntries),
+        closeBehavior
+      ]);
+
+  @override
+  String toString() {
+    return 'StacDropdownMenu(enabled: $enabled, width: $width, menuHeight: $menuHeight, leadingIcon: $leadingIcon, trailingIcon: $trailingIcon, label: $label, hintText: $hintText, helperText: $helperText, errorText: $errorText, selectedTrailingIcon: $selectedTrailingIcon, enableFilter: $enableFilter, enableSearch: $enableSearch, keyboardType: $keyboardType, textStyle: $textStyle, textAlign: $textAlign, inputDecorationTheme: $inputDecorationTheme, inputFormatters: $inputFormatters, alignmentOffset: $alignmentOffset, expandedInsets: $expandedInsets, requestFocusOnTap: $requestFocusOnTap, initialSelection: $initialSelection, dropdownMenuEntries: $dropdownMenuEntries, closeBehavior: $closeBehavior)';
+  }
+}
+
+/// @nodoc
+abstract mixin class $StacDropdownMenuCopyWith<$Res> {
+  factory $StacDropdownMenuCopyWith(
+          StacDropdownMenu value, $Res Function(StacDropdownMenu) _then) =
+      _$StacDropdownMenuCopyWithImpl;
+  @useResult
+  $Res call(
+      {bool enabled,
+      double? width,
+      double? menuHeight,
+      Map<String, dynamic>? leadingIcon,
+      Map<String, dynamic>? trailingIcon,
+      Map<String, dynamic>? label,
+      String? hintText,
+      String? helperText,
+      String? errorText,
+      Map<String, dynamic>? selectedTrailingIcon,
+      bool? enableFilter,
+      bool? enableSearch,
+      StacTextInputType? keyboardType,
+      StacTextStyle? textStyle,
+      TextAlign textAlign,
+      StacInputDecorationTheme? inputDecorationTheme,
+      List<StacInputFormatter> inputFormatters,
+      StacOffset? alignmentOffset,
+      StacEdgeInsets? expandedInsets,
+      bool? requestFocusOnTap,
+      dynamic initialSelection,
+      List<StacDropdownMenuEntry> dropdownMenuEntries,
+      DropdownMenuCloseBehavior? closeBehavior});
+
+  $StacTextStyleCopyWith<$Res>? get textStyle;
+  $StacInputDecorationThemeCopyWith<$Res>? get inputDecorationTheme;
+  $StacOffsetCopyWith<$Res>? get alignmentOffset;
+  $StacEdgeInsetsCopyWith<$Res>? get expandedInsets;
+}
+
+/// @nodoc
+class _$StacDropdownMenuCopyWithImpl<$Res>
+    implements $StacDropdownMenuCopyWith<$Res> {
+  _$StacDropdownMenuCopyWithImpl(this._self, this._then);
+
+  final StacDropdownMenu _self;
+  final $Res Function(StacDropdownMenu) _then;
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @pragma('vm:prefer-inline')
+  @override
+  $Res call({
+    Object? enabled = null,
+    Object? width = freezed,
+    Object? menuHeight = freezed,
+    Object? leadingIcon = freezed,
+    Object? trailingIcon = freezed,
+    Object? label = freezed,
+    Object? hintText = freezed,
+    Object? helperText = freezed,
+    Object? errorText = freezed,
+    Object? selectedTrailingIcon = freezed,
+    Object? enableFilter = freezed,
+    Object? enableSearch = freezed,
+    Object? keyboardType = freezed,
+    Object? textStyle = freezed,
+    Object? textAlign = null,
+    Object? inputDecorationTheme = freezed,
+    Object? inputFormatters = null,
+    Object? alignmentOffset = freezed,
+    Object? expandedInsets = freezed,
+    Object? requestFocusOnTap = freezed,
+    Object? initialSelection = freezed,
+    Object? dropdownMenuEntries = null,
+    Object? closeBehavior = freezed,
+  }) {
+    return _then(_self.copyWith(
+      enabled: null == enabled
+          ? _self.enabled
+          : enabled // ignore: cast_nullable_to_non_nullable
+              as bool,
+      width: freezed == width
+          ? _self.width
+          : width // ignore: cast_nullable_to_non_nullable
+              as double?,
+      menuHeight: freezed == menuHeight
+          ? _self.menuHeight
+          : menuHeight // ignore: cast_nullable_to_non_nullable
+              as double?,
+      leadingIcon: freezed == leadingIcon
+          ? _self.leadingIcon
+          : leadingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      trailingIcon: freezed == trailingIcon
+          ? _self.trailingIcon
+          : trailingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      label: freezed == label
+          ? _self.label
+          : label // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      hintText: freezed == hintText
+          ? _self.hintText
+          : hintText // ignore: cast_nullable_to_non_nullable
+              as String?,
+      helperText: freezed == helperText
+          ? _self.helperText
+          : helperText // ignore: cast_nullable_to_non_nullable
+              as String?,
+      errorText: freezed == errorText
+          ? _self.errorText
+          : errorText // ignore: cast_nullable_to_non_nullable
+              as String?,
+      selectedTrailingIcon: freezed == selectedTrailingIcon
+          ? _self.selectedTrailingIcon
+          : selectedTrailingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      enableFilter: freezed == enableFilter
+          ? _self.enableFilter
+          : enableFilter // ignore: cast_nullable_to_non_nullable
+              as bool?,
+      enableSearch: freezed == enableSearch
+          ? _self.enableSearch
+          : enableSearch // ignore: cast_nullable_to_non_nullable
+              as bool?,
+      keyboardType: freezed == keyboardType
+          ? _self.keyboardType
+          : keyboardType // ignore: cast_nullable_to_non_nullable
+              as StacTextInputType?,
+      textStyle: freezed == textStyle
+          ? _self.textStyle
+          : textStyle // ignore: cast_nullable_to_non_nullable
+              as StacTextStyle?,
+      textAlign: null == textAlign
+          ? _self.textAlign
+          : textAlign // ignore: cast_nullable_to_non_nullable
+              as TextAlign,
+      inputDecorationTheme: freezed == inputDecorationTheme
+          ? _self.inputDecorationTheme
+          : inputDecorationTheme // ignore: cast_nullable_to_non_nullable
+              as StacInputDecorationTheme?,
+      inputFormatters: null == inputFormatters
+          ? _self.inputFormatters
+          : inputFormatters // ignore: cast_nullable_to_non_nullable
+              as List<StacInputFormatter>,
+      alignmentOffset: freezed == alignmentOffset
+          ? _self.alignmentOffset
+          : alignmentOffset // ignore: cast_nullable_to_non_nullable
+              as StacOffset?,
+      expandedInsets: freezed == expandedInsets
+          ? _self.expandedInsets
+          : expandedInsets // ignore: cast_nullable_to_non_nullable
+              as StacEdgeInsets?,
+      requestFocusOnTap: freezed == requestFocusOnTap
+          ? _self.requestFocusOnTap
+          : requestFocusOnTap // ignore: cast_nullable_to_non_nullable
+              as bool?,
+      initialSelection: freezed == initialSelection
+          ? _self.initialSelection
+          : initialSelection // ignore: cast_nullable_to_non_nullable
+              as dynamic,
+      dropdownMenuEntries: null == dropdownMenuEntries
+          ? _self.dropdownMenuEntries
+          : dropdownMenuEntries // ignore: cast_nullable_to_non_nullable
+              as List<StacDropdownMenuEntry>,
+      closeBehavior: freezed == closeBehavior
+          ? _self.closeBehavior
+          : closeBehavior // ignore: cast_nullable_to_non_nullable
+              as DropdownMenuCloseBehavior?,
+    ));
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacTextStyleCopyWith<$Res>? get textStyle {
+    if (_self.textStyle == null) {
+      return null;
+    }
+
+    return $StacTextStyleCopyWith<$Res>(_self.textStyle!, (value) {
+      return _then(_self.copyWith(textStyle: value));
+    });
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacInputDecorationThemeCopyWith<$Res>? get inputDecorationTheme {
+    if (_self.inputDecorationTheme == null) {
+      return null;
+    }
+
+    return $StacInputDecorationThemeCopyWith<$Res>(_self.inputDecorationTheme!,
+        (value) {
+      return _then(_self.copyWith(inputDecorationTheme: value));
+    });
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacOffsetCopyWith<$Res>? get alignmentOffset {
+    if (_self.alignmentOffset == null) {
+      return null;
+    }
+
+    return $StacOffsetCopyWith<$Res>(_self.alignmentOffset!, (value) {
+      return _then(_self.copyWith(alignmentOffset: value));
+    });
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacEdgeInsetsCopyWith<$Res>? get expandedInsets {
+    if (_self.expandedInsets == null) {
+      return null;
+    }
+
+    return $StacEdgeInsetsCopyWith<$Res>(_self.expandedInsets!, (value) {
+      return _then(_self.copyWith(expandedInsets: value));
+    });
+  }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _StacDropdownMenu implements StacDropdownMenu {
+  const _StacDropdownMenu(
+      {this.enabled = true,
+      this.width,
+      this.menuHeight,
+      final Map<String, dynamic>? leadingIcon,
+      final Map<String, dynamic>? trailingIcon,
+      final Map<String, dynamic>? label,
+      this.hintText,
+      this.helperText,
+      this.errorText,
+      final Map<String, dynamic>? selectedTrailingIcon,
+      this.enableFilter,
+      this.enableSearch,
+      this.keyboardType,
+      this.textStyle,
+      this.textAlign = TextAlign.start,
+      this.inputDecorationTheme,
+      final List<StacInputFormatter> inputFormatters = const [],
+      this.alignmentOffset,
+      this.expandedInsets,
+      this.requestFocusOnTap,
+      this.initialSelection,
+      final List<StacDropdownMenuEntry> dropdownMenuEntries = const [],
+      this.closeBehavior = DropdownMenuCloseBehavior.all})
+      : _leadingIcon = leadingIcon,
+        _trailingIcon = trailingIcon,
+        _label = label,
+        _selectedTrailingIcon = selectedTrailingIcon,
+        _inputFormatters = inputFormatters,
+        _dropdownMenuEntries = dropdownMenuEntries;
+  factory _StacDropdownMenu.fromJson(Map<String, dynamic> json) =>
+      _$StacDropdownMenuFromJson(json);
+
+  @override
+  @JsonKey()
+  final bool enabled;
+  @override
+  final double? width;
+  @override
+  final double? menuHeight;
+  final Map<String, dynamic>? _leadingIcon;
+  @override
+  Map<String, dynamic>? get leadingIcon {
+    final value = _leadingIcon;
+    if (value == null) return null;
+    if (_leadingIcon is EqualUnmodifiableMapView) return _leadingIcon;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(value);
+  }
+
+  final Map<String, dynamic>? _trailingIcon;
+  @override
+  Map<String, dynamic>? get trailingIcon {
+    final value = _trailingIcon;
+    if (value == null) return null;
+    if (_trailingIcon is EqualUnmodifiableMapView) return _trailingIcon;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(value);
+  }
+
+  final Map<String, dynamic>? _label;
+  @override
+  Map<String, dynamic>? get label {
+    final value = _label;
+    if (value == null) return null;
+    if (_label is EqualUnmodifiableMapView) return _label;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(value);
+  }
+
+  @override
+  final String? hintText;
+  @override
+  final String? helperText;
+  @override
+  final String? errorText;
+  final Map<String, dynamic>? _selectedTrailingIcon;
+  @override
+  Map<String, dynamic>? get selectedTrailingIcon {
+    final value = _selectedTrailingIcon;
+    if (value == null) return null;
+    if (_selectedTrailingIcon is EqualUnmodifiableMapView)
+      return _selectedTrailingIcon;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(value);
+  }
+
+  @override
+  final bool? enableFilter;
+  @override
+  final bool? enableSearch;
+  @override
+  final StacTextInputType? keyboardType;
+  @override
+  final StacTextStyle? textStyle;
+  @override
+  @JsonKey()
+  final TextAlign textAlign;
+  @override
+  final StacInputDecorationTheme? inputDecorationTheme;
+  final List<StacInputFormatter> _inputFormatters;
+  @override
+  @JsonKey()
+  List<StacInputFormatter> get inputFormatters {
+    if (_inputFormatters is EqualUnmodifiableListView) return _inputFormatters;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableListView(_inputFormatters);
+  }
+
+  @override
+  final StacOffset? alignmentOffset;
+  @override
+  final StacEdgeInsets? expandedInsets;
+  @override
+  final bool? requestFocusOnTap;
+// TextEditingController? controller;
+  @override
+  final dynamic initialSelection;
+// ValueChanged<T?>? onSelected;
+// FocusNode? focusNode;
+  final List<StacDropdownMenuEntry> _dropdownMenuEntries;
+// ValueChanged<T?>? onSelected;
+// FocusNode? focusNode;
+  @override
+  @JsonKey()
+  List<StacDropdownMenuEntry> get dropdownMenuEntries {
+    if (_dropdownMenuEntries is EqualUnmodifiableListView)
+      return _dropdownMenuEntries;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableListView(_dropdownMenuEntries);
+  }
+
+//    FilterCallback<T>? filterCallback;
+//    SearchCallback<T>? searchCallback,
+//
+//
+//
+  @override
+  @JsonKey()
+  final DropdownMenuCloseBehavior? closeBehavior;
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @pragma('vm:prefer-inline')
+  _$StacDropdownMenuCopyWith<_StacDropdownMenu> get copyWith =>
+      __$StacDropdownMenuCopyWithImpl<_StacDropdownMenu>(this, _$identity);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return _$StacDropdownMenuToJson(
+      this,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return identical(this, other) ||
+        (other.runtimeType == runtimeType &&
+            other is _StacDropdownMenu &&
+            (identical(other.enabled, enabled) || other.enabled == enabled) &&
+            (identical(other.width, width) || other.width == width) &&
+            (identical(other.menuHeight, menuHeight) ||
+                other.menuHeight == menuHeight) &&
+            const DeepCollectionEquality()
+                .equals(other._leadingIcon, _leadingIcon) &&
+            const DeepCollectionEquality()
+                .equals(other._trailingIcon, _trailingIcon) &&
+            const DeepCollectionEquality().equals(other._label, _label) &&
+            (identical(other.hintText, hintText) ||
+                other.hintText == hintText) &&
+            (identical(other.helperText, helperText) ||
+                other.helperText == helperText) &&
+            (identical(other.errorText, errorText) ||
+                other.errorText == errorText) &&
+            const DeepCollectionEquality()
+                .equals(other._selectedTrailingIcon, _selectedTrailingIcon) &&
+            (identical(other.enableFilter, enableFilter) ||
+                other.enableFilter == enableFilter) &&
+            (identical(other.enableSearch, enableSearch) ||
+                other.enableSearch == enableSearch) &&
+            (identical(other.keyboardType, keyboardType) ||
+                other.keyboardType == keyboardType) &&
+            (identical(other.textStyle, textStyle) ||
+                other.textStyle == textStyle) &&
+            (identical(other.textAlign, textAlign) ||
+                other.textAlign == textAlign) &&
+            (identical(other.inputDecorationTheme, inputDecorationTheme) ||
+                other.inputDecorationTheme == inputDecorationTheme) &&
+            const DeepCollectionEquality()
+                .equals(other._inputFormatters, _inputFormatters) &&
+            (identical(other.alignmentOffset, alignmentOffset) ||
+                other.alignmentOffset == alignmentOffset) &&
+            (identical(other.expandedInsets, expandedInsets) ||
+                other.expandedInsets == expandedInsets) &&
+            (identical(other.requestFocusOnTap, requestFocusOnTap) ||
+                other.requestFocusOnTap == requestFocusOnTap) &&
+            const DeepCollectionEquality()
+                .equals(other.initialSelection, initialSelection) &&
+            const DeepCollectionEquality()
+                .equals(other._dropdownMenuEntries, _dropdownMenuEntries) &&
+            (identical(other.closeBehavior, closeBehavior) ||
+                other.closeBehavior == closeBehavior));
+  }
+
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  int get hashCode => Object.hashAll([
+        runtimeType,
+        enabled,
+        width,
+        menuHeight,
+        const DeepCollectionEquality().hash(_leadingIcon),
+        const DeepCollectionEquality().hash(_trailingIcon),
+        const DeepCollectionEquality().hash(_label),
+        hintText,
+        helperText,
+        errorText,
+        const DeepCollectionEquality().hash(_selectedTrailingIcon),
+        enableFilter,
+        enableSearch,
+        keyboardType,
+        textStyle,
+        textAlign,
+        inputDecorationTheme,
+        const DeepCollectionEquality().hash(_inputFormatters),
+        alignmentOffset,
+        expandedInsets,
+        requestFocusOnTap,
+        const DeepCollectionEquality().hash(initialSelection),
+        const DeepCollectionEquality().hash(_dropdownMenuEntries),
+        closeBehavior
+      ]);
+
+  @override
+  String toString() {
+    return 'StacDropdownMenu(enabled: $enabled, width: $width, menuHeight: $menuHeight, leadingIcon: $leadingIcon, trailingIcon: $trailingIcon, label: $label, hintText: $hintText, helperText: $helperText, errorText: $errorText, selectedTrailingIcon: $selectedTrailingIcon, enableFilter: $enableFilter, enableSearch: $enableSearch, keyboardType: $keyboardType, textStyle: $textStyle, textAlign: $textAlign, inputDecorationTheme: $inputDecorationTheme, inputFormatters: $inputFormatters, alignmentOffset: $alignmentOffset, expandedInsets: $expandedInsets, requestFocusOnTap: $requestFocusOnTap, initialSelection: $initialSelection, dropdownMenuEntries: $dropdownMenuEntries, closeBehavior: $closeBehavior)';
+  }
+}
+
+/// @nodoc
+abstract mixin class _$StacDropdownMenuCopyWith<$Res>
+    implements $StacDropdownMenuCopyWith<$Res> {
+  factory _$StacDropdownMenuCopyWith(
+          _StacDropdownMenu value, $Res Function(_StacDropdownMenu) _then) =
+      __$StacDropdownMenuCopyWithImpl;
+  @override
+  @useResult
+  $Res call(
+      {bool enabled,
+      double? width,
+      double? menuHeight,
+      Map<String, dynamic>? leadingIcon,
+      Map<String, dynamic>? trailingIcon,
+      Map<String, dynamic>? label,
+      String? hintText,
+      String? helperText,
+      String? errorText,
+      Map<String, dynamic>? selectedTrailingIcon,
+      bool? enableFilter,
+      bool? enableSearch,
+      StacTextInputType? keyboardType,
+      StacTextStyle? textStyle,
+      TextAlign textAlign,
+      StacInputDecorationTheme? inputDecorationTheme,
+      List<StacInputFormatter> inputFormatters,
+      StacOffset? alignmentOffset,
+      StacEdgeInsets? expandedInsets,
+      bool? requestFocusOnTap,
+      dynamic initialSelection,
+      List<StacDropdownMenuEntry> dropdownMenuEntries,
+      DropdownMenuCloseBehavior? closeBehavior});
+
+  @override
+  $StacTextStyleCopyWith<$Res>? get textStyle;
+  @override
+  $StacInputDecorationThemeCopyWith<$Res>? get inputDecorationTheme;
+  @override
+  $StacOffsetCopyWith<$Res>? get alignmentOffset;
+  @override
+  $StacEdgeInsetsCopyWith<$Res>? get expandedInsets;
+}
+
+/// @nodoc
+class __$StacDropdownMenuCopyWithImpl<$Res>
+    implements _$StacDropdownMenuCopyWith<$Res> {
+  __$StacDropdownMenuCopyWithImpl(this._self, this._then);
+
+  final _StacDropdownMenu _self;
+  final $Res Function(_StacDropdownMenu) _then;
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $Res call({
+    Object? enabled = null,
+    Object? width = freezed,
+    Object? menuHeight = freezed,
+    Object? leadingIcon = freezed,
+    Object? trailingIcon = freezed,
+    Object? label = freezed,
+    Object? hintText = freezed,
+    Object? helperText = freezed,
+    Object? errorText = freezed,
+    Object? selectedTrailingIcon = freezed,
+    Object? enableFilter = freezed,
+    Object? enableSearch = freezed,
+    Object? keyboardType = freezed,
+    Object? textStyle = freezed,
+    Object? textAlign = null,
+    Object? inputDecorationTheme = freezed,
+    Object? inputFormatters = null,
+    Object? alignmentOffset = freezed,
+    Object? expandedInsets = freezed,
+    Object? requestFocusOnTap = freezed,
+    Object? initialSelection = freezed,
+    Object? dropdownMenuEntries = null,
+    Object? closeBehavior = freezed,
+  }) {
+    return _then(_StacDropdownMenu(
+      enabled: null == enabled
+          ? _self.enabled
+          : enabled // ignore: cast_nullable_to_non_nullable
+              as bool,
+      width: freezed == width
+          ? _self.width
+          : width // ignore: cast_nullable_to_non_nullable
+              as double?,
+      menuHeight: freezed == menuHeight
+          ? _self.menuHeight
+          : menuHeight // ignore: cast_nullable_to_non_nullable
+              as double?,
+      leadingIcon: freezed == leadingIcon
+          ? _self._leadingIcon
+          : leadingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      trailingIcon: freezed == trailingIcon
+          ? _self._trailingIcon
+          : trailingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      label: freezed == label
+          ? _self._label
+          : label // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      hintText: freezed == hintText
+          ? _self.hintText
+          : hintText // ignore: cast_nullable_to_non_nullable
+              as String?,
+      helperText: freezed == helperText
+          ? _self.helperText
+          : helperText // ignore: cast_nullable_to_non_nullable
+              as String?,
+      errorText: freezed == errorText
+          ? _self.errorText
+          : errorText // ignore: cast_nullable_to_non_nullable
+              as String?,
+      selectedTrailingIcon: freezed == selectedTrailingIcon
+          ? _self._selectedTrailingIcon
+          : selectedTrailingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      enableFilter: freezed == enableFilter
+          ? _self.enableFilter
+          : enableFilter // ignore: cast_nullable_to_non_nullable
+              as bool?,
+      enableSearch: freezed == enableSearch
+          ? _self.enableSearch
+          : enableSearch // ignore: cast_nullable_to_non_nullable
+              as bool?,
+      keyboardType: freezed == keyboardType
+          ? _self.keyboardType
+          : keyboardType // ignore: cast_nullable_to_non_nullable
+              as StacTextInputType?,
+      textStyle: freezed == textStyle
+          ? _self.textStyle
+          : textStyle // ignore: cast_nullable_to_non_nullable
+              as StacTextStyle?,
+      textAlign: null == textAlign
+          ? _self.textAlign
+          : textAlign // ignore: cast_nullable_to_non_nullable
+              as TextAlign,
+      inputDecorationTheme: freezed == inputDecorationTheme
+          ? _self.inputDecorationTheme
+          : inputDecorationTheme // ignore: cast_nullable_to_non_nullable
+              as StacInputDecorationTheme?,
+      inputFormatters: null == inputFormatters
+          ? _self._inputFormatters
+          : inputFormatters // ignore: cast_nullable_to_non_nullable
+              as List<StacInputFormatter>,
+      alignmentOffset: freezed == alignmentOffset
+          ? _self.alignmentOffset
+          : alignmentOffset // ignore: cast_nullable_to_non_nullable
+              as StacOffset?,
+      expandedInsets: freezed == expandedInsets
+          ? _self.expandedInsets
+          : expandedInsets // ignore: cast_nullable_to_non_nullable
+              as StacEdgeInsets?,
+      requestFocusOnTap: freezed == requestFocusOnTap
+          ? _self.requestFocusOnTap
+          : requestFocusOnTap // ignore: cast_nullable_to_non_nullable
+              as bool?,
+      initialSelection: freezed == initialSelection
+          ? _self.initialSelection
+          : initialSelection // ignore: cast_nullable_to_non_nullable
+              as dynamic,
+      dropdownMenuEntries: null == dropdownMenuEntries
+          ? _self._dropdownMenuEntries
+          : dropdownMenuEntries // ignore: cast_nullable_to_non_nullable
+              as List<StacDropdownMenuEntry>,
+      closeBehavior: freezed == closeBehavior
+          ? _self.closeBehavior
+          : closeBehavior // ignore: cast_nullable_to_non_nullable
+              as DropdownMenuCloseBehavior?,
+    ));
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacTextStyleCopyWith<$Res>? get textStyle {
+    if (_self.textStyle == null) {
+      return null;
+    }
+
+    return $StacTextStyleCopyWith<$Res>(_self.textStyle!, (value) {
+      return _then(_self.copyWith(textStyle: value));
+    });
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacInputDecorationThemeCopyWith<$Res>? get inputDecorationTheme {
+    if (_self.inputDecorationTheme == null) {
+      return null;
+    }
+
+    return $StacInputDecorationThemeCopyWith<$Res>(_self.inputDecorationTheme!,
+        (value) {
+      return _then(_self.copyWith(inputDecorationTheme: value));
+    });
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacOffsetCopyWith<$Res>? get alignmentOffset {
+    if (_self.alignmentOffset == null) {
+      return null;
+    }
+
+    return $StacOffsetCopyWith<$Res>(_self.alignmentOffset!, (value) {
+      return _then(_self.copyWith(alignmentOffset: value));
+    });
+  }
+
+  /// Create a copy of StacDropdownMenu
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacEdgeInsetsCopyWith<$Res>? get expandedInsets {
+    if (_self.expandedInsets == null) {
+      return null;
+    }
+
+    return $StacEdgeInsetsCopyWith<$Res>(_self.expandedInsets!, (value) {
+      return _then(_self.copyWith(expandedInsets: value));
+    });
+  }
+}
+
+// dart format on
diff --git a/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.g.dart b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.g.dart
new file mode 100644
index 000000000..3c41bb7e4
--- /dev/null
+++ b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.g.dart
@@ -0,0 +1,114 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'stac_dropdown_menu.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_StacDropdownMenu _$StacDropdownMenuFromJson(Map<String, dynamic> json) =>
+    _StacDropdownMenu(
+      enabled: json['enabled'] as bool? ?? true,
+      width: (json['width'] as num?)?.toDouble(),
+      menuHeight: (json['menuHeight'] as num?)?.toDouble(),
+      leadingIcon: json['leadingIcon'] as Map<String, dynamic>?,
+      trailingIcon: json['trailingIcon'] as Map<String, dynamic>?,
+      label: json['label'] as Map<String, dynamic>?,
+      hintText: json['hintText'] as String?,
+      helperText: json['helperText'] as String?,
+      errorText: json['errorText'] as String?,
+      selectedTrailingIcon:
+          json['selectedTrailingIcon'] as Map<String, dynamic>?,
+      enableFilter: json['enableFilter'] as bool?,
+      enableSearch: json['enableSearch'] as bool?,
+      keyboardType:
+          $enumDecodeNullable(_$StacTextInputTypeEnumMap, json['keyboardType']),
+      textStyle: json['textStyle'] == null
+          ? null
+          : StacTextStyle.fromJson(json['textStyle']),
+      textAlign: $enumDecodeNullable(_$TextAlignEnumMap, json['textAlign']) ??
+          TextAlign.start,
+      inputDecorationTheme: json['inputDecorationTheme'] == null
+          ? null
+          : StacInputDecorationTheme.fromJson(
+              json['inputDecorationTheme'] as Map<String, dynamic>),
+      inputFormatters: (json['inputFormatters'] as List<dynamic>?)
+              ?.map(
+                  (e) => StacInputFormatter.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      alignmentOffset: json['alignmentOffset'] == null
+          ? null
+          : StacOffset.fromJson(
+              json['alignmentOffset'] as Map<String, dynamic>),
+      expandedInsets: json['expandedInsets'] == null
+          ? null
+          : StacEdgeInsets.fromJson(json['expandedInsets']),
+      requestFocusOnTap: json['requestFocusOnTap'] as bool?,
+      initialSelection: json['initialSelection'],
+      dropdownMenuEntries: (json['dropdownMenuEntries'] as List<dynamic>?)
+              ?.map((e) =>
+                  StacDropdownMenuEntry.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      closeBehavior: $enumDecodeNullable(
+              _$DropdownMenuCloseBehaviorEnumMap, json['closeBehavior']) ??
+          DropdownMenuCloseBehavior.all,
+    );
+
+Map<String, dynamic> _$StacDropdownMenuToJson(_StacDropdownMenu instance) =>
+    <String, dynamic>{
+      'enabled': instance.enabled,
+      'width': instance.width,
+      'menuHeight': instance.menuHeight,
+      'leadingIcon': instance.leadingIcon,
+      'trailingIcon': instance.trailingIcon,
+      'label': instance.label,
+      'hintText': instance.hintText,
+      'helperText': instance.helperText,
+      'errorText': instance.errorText,
+      'selectedTrailingIcon': instance.selectedTrailingIcon,
+      'enableFilter': instance.enableFilter,
+      'enableSearch': instance.enableSearch,
+      'keyboardType': _$StacTextInputTypeEnumMap[instance.keyboardType],
+      'textStyle': instance.textStyle,
+      'textAlign': _$TextAlignEnumMap[instance.textAlign]!,
+      'inputDecorationTheme': instance.inputDecorationTheme,
+      'inputFormatters': instance.inputFormatters,
+      'alignmentOffset': instance.alignmentOffset,
+      'expandedInsets': instance.expandedInsets,
+      'requestFocusOnTap': instance.requestFocusOnTap,
+      'initialSelection': instance.initialSelection,
+      'dropdownMenuEntries': instance.dropdownMenuEntries,
+      'closeBehavior':
+          _$DropdownMenuCloseBehaviorEnumMap[instance.closeBehavior],
+    };
+
+const _$StacTextInputTypeEnumMap = {
+  StacTextInputType.text: 'text',
+  StacTextInputType.multiline: 'multiline',
+  StacTextInputType.number: 'number',
+  StacTextInputType.phone: 'phone',
+  StacTextInputType.datetime: 'datetime',
+  StacTextInputType.emailAddress: 'emailAddress',
+  StacTextInputType.url: 'url',
+  StacTextInputType.visiblePassword: 'visiblePassword',
+  StacTextInputType.name: 'name',
+  StacTextInputType.streetAddress: 'streetAddress',
+  StacTextInputType.none: 'none',
+};
+
+const _$TextAlignEnumMap = {
+  TextAlign.left: 'left',
+  TextAlign.right: 'right',
+  TextAlign.center: 'center',
+  TextAlign.justify: 'justify',
+  TextAlign.start: 'start',
+  TextAlign.end: 'end',
+};
+
+const _$DropdownMenuCloseBehaviorEnumMap = {
+  DropdownMenuCloseBehavior.all: 'all',
+  DropdownMenuCloseBehavior.self: 'self',
+  DropdownMenuCloseBehavior.none: 'none',
+};
diff --git a/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu_parser.dart b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu_parser.dart
new file mode 100644
index 000000000..ea9e46603
--- /dev/null
+++ b/packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu_parser.dart
@@ -0,0 +1,80 @@
+import 'package:flutter/material.dart';
+import 'package:stac/src/parsers/stac_dropdown_menu/stac_dropdown_menu.dart';
+import 'package:stac/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart';
+import 'package:stac/src/parsers/stac_input_decoration_theme/stac_input_decoration_theme.dart';
+import 'package:stac/src/parsers/stac_offset/stac_offset.dart';
+import 'package:stac/src/utils/widget_type.dart';
+import 'package:stac/stac.dart';
+
+class StacDropdownMenuParser extends StacParser<StacDropdownMenu> {
+  const StacDropdownMenuParser();
+
+  @override
+  String get type => WidgetType.dropdownMenu.name;
+
+  @override
+  StacDropdownMenu getModel(Map<String, dynamic> json) =>
+      StacDropdownMenu.fromJson(json);
+
+  @override
+  Widget parse(BuildContext context, StacDropdownMenu model) {
+    return _DropDownMenuWidget(model: model);
+  }
+}
+
+class _DropDownMenuWidget extends StatefulWidget {
+  const _DropDownMenuWidget({required this.model});
+
+  final StacDropdownMenu model;
+
+  @override
+  State<_DropDownMenuWidget> createState() => _DropDownMenuWidgetState();
+}
+
+class _DropDownMenuWidgetState extends State<_DropDownMenuWidget> {
+  final TextEditingController _controller = TextEditingController();
+  final _focusNode = FocusNode();
+  late final StacDropdownMenu model;
+
+  @override
+  void initState() {
+    model = widget.model;
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return DropdownMenu(
+      initialSelection: model.initialSelection,
+      focusNode: _focusNode,
+      controller: _controller,
+      dropdownMenuEntries: model.dropdownMenuEntries
+          .map((StacDropdownMenuEntry dropDownMenu) =>
+              dropDownMenu.parse(context)!)
+          .toList(),
+      enabled: model.enabled,
+      width: model.width,
+      menuHeight: model.menuHeight,
+      leadingIcon: Stac.fromJson(model.leadingIcon, context),
+      trailingIcon: Stac.fromJson(model.trailingIcon, context),
+      label: Stac.fromJson(model.label, context),
+      hintText: model.hintText,
+      helperText: model.helperText,
+      errorText: model.errorText,
+      selectedTrailingIcon: Stac.fromJson(model.selectedTrailingIcon, context),
+      enableFilter: model.enableFilter ?? true,
+      enableSearch: model.enableSearch ?? true,
+      keyboardType: model.keyboardType?.value,
+      textStyle: model.textStyle?.parse(context),
+      textAlign: model.textAlign,
+      inputDecorationTheme: model.inputDecorationTheme.parse(context),
+      requestFocusOnTap: model.requestFocusOnTap,
+      expandedInsets: model.expandedInsets.parse,
+      alignmentOffset: model.alignmentOffset?.parse,
+      inputFormatters: model.inputFormatters
+          .map((StacInputFormatter formatter) =>
+              formatter.type.format(formatter.rule ?? ""))
+          .toList(),
+    );
+  }
+}
diff --git a/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart b/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart
new file mode 100644
index 000000000..1aa97cbec
--- /dev/null
+++ b/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart
@@ -0,0 +1,36 @@
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:stac/src/framework/framework.dart' show Stac;
+import 'package:stac/src/parsers/parsers.dart';
+
+part 'stac_dropdown_menu_entry.freezed.dart';
+part 'stac_dropdown_menu_entry.g.dart';
+
+@freezed
+abstract class StacDropdownMenuEntry with _$StacDropdownMenuEntry {
+  const factory StacDropdownMenuEntry({
+    dynamic value,
+    @Default("") String label,
+    Map<String, dynamic>? labelWidget,
+    Map<String, dynamic>? leadingIcon,
+    Map<String, dynamic>? trailingIcon,
+    @Default(true) bool enabled,
+    StacButtonStyle? style,
+  }) = _StacBDropdownMenuEntry;
+
+  factory StacDropdownMenuEntry.fromJson(Map<String, dynamic> json) =>
+      _$StacDropdownMenuEntryFromJson(json);
+}
+
+extension StacDropdownMenuEntryParser on StacDropdownMenuEntry? {
+  DropdownMenuEntry? parse(BuildContext context) {
+    return DropdownMenuEntry(
+      value: this?.value,
+      label: this!.label,
+      labelWidget: Stac.fromJson(this?.labelWidget, context),
+      leadingIcon: Stac.fromJson(this?.leadingIcon, context),
+      enabled: this?.enabled ?? true,
+      style: this?.style?.parseText(context),
+    );
+  }
+}
diff --git a/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.freezed.dart b/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.freezed.dart
new file mode 100644
index 000000000..1608a00df
--- /dev/null
+++ b/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.freezed.dart
@@ -0,0 +1,360 @@
+// dart format width=80
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'stac_dropdown_menu_entry.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity<T>(T value) => value;
+StacDropdownMenuEntry _$StacDropdownMenuEntryFromJson(
+    Map<String, dynamic> json) {
+  return _StacBDropdownMenuEntry.fromJson(json);
+}
+
+/// @nodoc
+mixin _$StacDropdownMenuEntry {
+  dynamic get value;
+  String get label;
+  Map<String, dynamic>? get labelWidget;
+  Map<String, dynamic>? get leadingIcon;
+  Map<String, dynamic>? get trailingIcon;
+  bool get enabled;
+  StacButtonStyle? get style;
+
+  /// Create a copy of StacDropdownMenuEntry
+  /// with the given fields replaced by the non-null parameter values.
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @pragma('vm:prefer-inline')
+  $StacDropdownMenuEntryCopyWith<StacDropdownMenuEntry> get copyWith =>
+      _$StacDropdownMenuEntryCopyWithImpl<StacDropdownMenuEntry>(
+          this as StacDropdownMenuEntry, _$identity);
+
+  /// Serializes this StacDropdownMenuEntry to a JSON map.
+  Map<String, dynamic> toJson();
+
+  @override
+  bool operator ==(Object other) {
+    return identical(this, other) ||
+        (other.runtimeType == runtimeType &&
+            other is StacDropdownMenuEntry &&
+            const DeepCollectionEquality().equals(other.value, value) &&
+            (identical(other.label, label) || other.label == label) &&
+            const DeepCollectionEquality()
+                .equals(other.labelWidget, labelWidget) &&
+            const DeepCollectionEquality()
+                .equals(other.leadingIcon, leadingIcon) &&
+            const DeepCollectionEquality()
+                .equals(other.trailingIcon, trailingIcon) &&
+            (identical(other.enabled, enabled) || other.enabled == enabled) &&
+            (identical(other.style, style) || other.style == style));
+  }
+
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  int get hashCode => Object.hash(
+      runtimeType,
+      const DeepCollectionEquality().hash(value),
+      label,
+      const DeepCollectionEquality().hash(labelWidget),
+      const DeepCollectionEquality().hash(leadingIcon),
+      const DeepCollectionEquality().hash(trailingIcon),
+      enabled,
+      style);
+
+  @override
+  String toString() {
+    return 'StacDropdownMenuEntry(value: $value, label: $label, labelWidget: $labelWidget, leadingIcon: $leadingIcon, trailingIcon: $trailingIcon, enabled: $enabled, style: $style)';
+  }
+}
+
+/// @nodoc
+abstract mixin class $StacDropdownMenuEntryCopyWith<$Res> {
+  factory $StacDropdownMenuEntryCopyWith(StacDropdownMenuEntry value,
+          $Res Function(StacDropdownMenuEntry) _then) =
+      _$StacDropdownMenuEntryCopyWithImpl;
+  @useResult
+  $Res call(
+      {dynamic value,
+      String label,
+      Map<String, dynamic>? labelWidget,
+      Map<String, dynamic>? leadingIcon,
+      Map<String, dynamic>? trailingIcon,
+      bool enabled,
+      StacButtonStyle? style});
+
+  $StacButtonStyleCopyWith<$Res>? get style;
+}
+
+/// @nodoc
+class _$StacDropdownMenuEntryCopyWithImpl<$Res>
+    implements $StacDropdownMenuEntryCopyWith<$Res> {
+  _$StacDropdownMenuEntryCopyWithImpl(this._self, this._then);
+
+  final StacDropdownMenuEntry _self;
+  final $Res Function(StacDropdownMenuEntry) _then;
+
+  /// Create a copy of StacDropdownMenuEntry
+  /// with the given fields replaced by the non-null parameter values.
+  @pragma('vm:prefer-inline')
+  @override
+  $Res call({
+    Object? value = freezed,
+    Object? label = null,
+    Object? labelWidget = freezed,
+    Object? leadingIcon = freezed,
+    Object? trailingIcon = freezed,
+    Object? enabled = null,
+    Object? style = freezed,
+  }) {
+    return _then(_self.copyWith(
+      value: freezed == value
+          ? _self.value
+          : value // ignore: cast_nullable_to_non_nullable
+              as dynamic,
+      label: null == label
+          ? _self.label
+          : label // ignore: cast_nullable_to_non_nullable
+              as String,
+      labelWidget: freezed == labelWidget
+          ? _self.labelWidget
+          : labelWidget // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      leadingIcon: freezed == leadingIcon
+          ? _self.leadingIcon
+          : leadingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      trailingIcon: freezed == trailingIcon
+          ? _self.trailingIcon
+          : trailingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      enabled: null == enabled
+          ? _self.enabled
+          : enabled // ignore: cast_nullable_to_non_nullable
+              as bool,
+      style: freezed == style
+          ? _self.style
+          : style // ignore: cast_nullable_to_non_nullable
+              as StacButtonStyle?,
+    ));
+  }
+
+  /// Create a copy of StacDropdownMenuEntry
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacButtonStyleCopyWith<$Res>? get style {
+    if (_self.style == null) {
+      return null;
+    }
+
+    return $StacButtonStyleCopyWith<$Res>(_self.style!, (value) {
+      return _then(_self.copyWith(style: value));
+    });
+  }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _StacBDropdownMenuEntry implements StacDropdownMenuEntry {
+  const _StacBDropdownMenuEntry(
+      {this.value,
+      this.label = "",
+      final Map<String, dynamic>? labelWidget,
+      final Map<String, dynamic>? leadingIcon,
+      final Map<String, dynamic>? trailingIcon,
+      this.enabled = true,
+      this.style})
+      : _labelWidget = labelWidget,
+        _leadingIcon = leadingIcon,
+        _trailingIcon = trailingIcon;
+  factory _StacBDropdownMenuEntry.fromJson(Map<String, dynamic> json) =>
+      _$StacBDropdownMenuEntryFromJson(json);
+
+  @override
+  final dynamic value;
+  @override
+  @JsonKey()
+  final String label;
+  final Map<String, dynamic>? _labelWidget;
+  @override
+  Map<String, dynamic>? get labelWidget {
+    final value = _labelWidget;
+    if (value == null) return null;
+    if (_labelWidget is EqualUnmodifiableMapView) return _labelWidget;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(value);
+  }
+
+  final Map<String, dynamic>? _leadingIcon;
+  @override
+  Map<String, dynamic>? get leadingIcon {
+    final value = _leadingIcon;
+    if (value == null) return null;
+    if (_leadingIcon is EqualUnmodifiableMapView) return _leadingIcon;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(value);
+  }
+
+  final Map<String, dynamic>? _trailingIcon;
+  @override
+  Map<String, dynamic>? get trailingIcon {
+    final value = _trailingIcon;
+    if (value == null) return null;
+    if (_trailingIcon is EqualUnmodifiableMapView) return _trailingIcon;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(value);
+  }
+
+  @override
+  @JsonKey()
+  final bool enabled;
+  @override
+  final StacButtonStyle? style;
+
+  /// Create a copy of StacDropdownMenuEntry
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @pragma('vm:prefer-inline')
+  _$StacBDropdownMenuEntryCopyWith<_StacBDropdownMenuEntry> get copyWith =>
+      __$StacBDropdownMenuEntryCopyWithImpl<_StacBDropdownMenuEntry>(
+          this, _$identity);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return _$StacBDropdownMenuEntryToJson(
+      this,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return identical(this, other) ||
+        (other.runtimeType == runtimeType &&
+            other is _StacBDropdownMenuEntry &&
+            const DeepCollectionEquality().equals(other.value, value) &&
+            (identical(other.label, label) || other.label == label) &&
+            const DeepCollectionEquality()
+                .equals(other._labelWidget, _labelWidget) &&
+            const DeepCollectionEquality()
+                .equals(other._leadingIcon, _leadingIcon) &&
+            const DeepCollectionEquality()
+                .equals(other._trailingIcon, _trailingIcon) &&
+            (identical(other.enabled, enabled) || other.enabled == enabled) &&
+            (identical(other.style, style) || other.style == style));
+  }
+
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  int get hashCode => Object.hash(
+      runtimeType,
+      const DeepCollectionEquality().hash(value),
+      label,
+      const DeepCollectionEquality().hash(_labelWidget),
+      const DeepCollectionEquality().hash(_leadingIcon),
+      const DeepCollectionEquality().hash(_trailingIcon),
+      enabled,
+      style);
+
+  @override
+  String toString() {
+    return 'StacDropdownMenuEntry(value: $value, label: $label, labelWidget: $labelWidget, leadingIcon: $leadingIcon, trailingIcon: $trailingIcon, enabled: $enabled, style: $style)';
+  }
+}
+
+/// @nodoc
+abstract mixin class _$StacBDropdownMenuEntryCopyWith<$Res>
+    implements $StacDropdownMenuEntryCopyWith<$Res> {
+  factory _$StacBDropdownMenuEntryCopyWith(_StacBDropdownMenuEntry value,
+          $Res Function(_StacBDropdownMenuEntry) _then) =
+      __$StacBDropdownMenuEntryCopyWithImpl;
+  @override
+  @useResult
+  $Res call(
+      {dynamic value,
+      String label,
+      Map<String, dynamic>? labelWidget,
+      Map<String, dynamic>? leadingIcon,
+      Map<String, dynamic>? trailingIcon,
+      bool enabled,
+      StacButtonStyle? style});
+
+  @override
+  $StacButtonStyleCopyWith<$Res>? get style;
+}
+
+/// @nodoc
+class __$StacBDropdownMenuEntryCopyWithImpl<$Res>
+    implements _$StacBDropdownMenuEntryCopyWith<$Res> {
+  __$StacBDropdownMenuEntryCopyWithImpl(this._self, this._then);
+
+  final _StacBDropdownMenuEntry _self;
+  final $Res Function(_StacBDropdownMenuEntry) _then;
+
+  /// Create a copy of StacDropdownMenuEntry
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $Res call({
+    Object? value = freezed,
+    Object? label = null,
+    Object? labelWidget = freezed,
+    Object? leadingIcon = freezed,
+    Object? trailingIcon = freezed,
+    Object? enabled = null,
+    Object? style = freezed,
+  }) {
+    return _then(_StacBDropdownMenuEntry(
+      value: freezed == value
+          ? _self.value
+          : value // ignore: cast_nullable_to_non_nullable
+              as dynamic,
+      label: null == label
+          ? _self.label
+          : label // ignore: cast_nullable_to_non_nullable
+              as String,
+      labelWidget: freezed == labelWidget
+          ? _self._labelWidget
+          : labelWidget // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      leadingIcon: freezed == leadingIcon
+          ? _self._leadingIcon
+          : leadingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      trailingIcon: freezed == trailingIcon
+          ? _self._trailingIcon
+          : trailingIcon // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>?,
+      enabled: null == enabled
+          ? _self.enabled
+          : enabled // ignore: cast_nullable_to_non_nullable
+              as bool,
+      style: freezed == style
+          ? _self.style
+          : style // ignore: cast_nullable_to_non_nullable
+              as StacButtonStyle?,
+    ));
+  }
+
+  /// Create a copy of StacDropdownMenuEntry
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @pragma('vm:prefer-inline')
+  $StacButtonStyleCopyWith<$Res>? get style {
+    if (_self.style == null) {
+      return null;
+    }
+
+    return $StacButtonStyleCopyWith<$Res>(_self.style!, (value) {
+      return _then(_self.copyWith(style: value));
+    });
+  }
+}
+
+// dart format on
diff --git a/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.g.dart b/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.g.dart
new file mode 100644
index 000000000..fd725c87a
--- /dev/null
+++ b/packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.g.dart
@@ -0,0 +1,33 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'stac_dropdown_menu_entry.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_StacBDropdownMenuEntry _$StacBDropdownMenuEntryFromJson(
+        Map<String, dynamic> json) =>
+    _StacBDropdownMenuEntry(
+      value: json['value'],
+      label: json['label'] as String? ?? "",
+      labelWidget: json['labelWidget'] as Map<String, dynamic>?,
+      leadingIcon: json['leadingIcon'] as Map<String, dynamic>?,
+      trailingIcon: json['trailingIcon'] as Map<String, dynamic>?,
+      enabled: json['enabled'] as bool? ?? true,
+      style: json['style'] == null
+          ? null
+          : StacButtonStyle.fromJson(json['style'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$StacBDropdownMenuEntryToJson(
+        _StacBDropdownMenuEntry instance) =>
+    <String, dynamic>{
+      'value': instance.value,
+      'label': instance.label,
+      'labelWidget': instance.labelWidget,
+      'leadingIcon': instance.leadingIcon,
+      'trailingIcon': instance.trailingIcon,
+      'enabled': instance.enabled,
+      'style': instance.style,
+    };
diff --git a/packages/stac/lib/src/utils/widget_type.dart b/packages/stac/lib/src/utils/widget_type.dart
index e38c0bc34..1f2fef182 100644
--- a/packages/stac/lib/src/utils/widget_type.dart
+++ b/packages/stac/lib/src/utils/widget_type.dart
@@ -16,6 +16,7 @@ enum WidgetType {
   coloredBox,
   column,
   container,
+  dropdownMenu,
   customScrollView,
   defaultBottomNavigationController,
   defaultTabController,
diff --git a/website/docs/widgets/dropdown_menu.md b/website/docs/widgets/dropdown_menu.md
new file mode 100644
index 000000000..b00088cd3
--- /dev/null
+++ b/website/docs/widgets/dropdown_menu.md
@@ -0,0 +1,82 @@
+# DropdownMenu
+
+The Stac DropdownMenu allows you to build a Flutter DropdownMenu widget using JSON.
+To know more about the DropdownMenu widget in Flutter, refer to
+the [official documentation](https://api.flutter.dev/flutter/material/DropdownMenu-class.html).
+
+## Properties
+
+| Property             | Type                            | Description                                               |
+|----------------------|---------------------------------|-----------------------------------------------------------|
+| dropdownMenuEntries  | `List<StacDropdownMenuEntries>` | The entries to display in the dropdown menu.              |
+| initialSelection     | `dynamic`                       | The initial selection value.                              |
+| enabled              | `bool`                          | Whether the dropdown menu is enabled. Defaults to `true`. |
+| label                | `Map<String, dynamic>?`         | The label to display for the dropdown menu.               |
+| leadingIcon          | `Map<String, dynamic>?`         | The icon to display at the start of the dropdown menu.    |
+| trailingIcon         | `Map<String, dynamic>?`         | The icon to display at the end of the dropdown menu.      |
+| hintText             | `String?`                       | The hint text to display when no selection is made.       |
+| errorText            | `String?`                       | The error text to display when there is an error.         |
+| width                | `double?`                       | The width of the dropdown menu.                           |
+| menuHeight           | `double?`                       | The height of the dropdown menu.                          |
+| inputDecorationTheme | `StacInputDecorationTheme?`     | The theme to use for the input decoration.                |
+| textStyle            | `StacTextStyle?`                | The text style to use for the dropdown menu.              |
+| enableFilter         | `bool`                          | Whether to enable filtering. Defaults to `true`.          |
+| enableSearch         | `bool`                          | Whether to enable search. Defaults to `true`.             |
+| requestFocusOnTap    | `bool`                          | Whether to request focus on tap. Defaults to `true`.      |
+
+# DropdownMenuEntry
+
+The Stac DropdownMenuEntry allows you to build a Flutter DropdownMenuEntry widget using JSON.
+To know more about the DropdownMenuEntry widget in Flutter, refer to
+the [official documentation](https://api.flutter.dev/flutter/material/DropdownMenuEntry-class.html).
+
+## Properties
+
+| Property     | Type                    | Description                                        |
+|--------------|-------------------------|----------------------------------------------------|
+| value        | `dynamic`               | The value associated with this entry.              |
+| label        | `String`                | The label to display for this entry.               |
+| enabled      | `bool`                  | Whether this entry is enabled. Defaults to `true`. |
+| leadingIcon  | `Map<String, dynamic>?` | The icon to display at the start of this entry.    |
+| trailingIcon | `Map<String, dynamic>?` | The icon to display at the end of this entry.      |
+| style        | `StacButtonStyle?`      | The style to use for this entry.                   |
+
+## Example JSON
+
+```json
+{
+  "type": "dropdownMenu",
+  "label": {
+    "type": "text",
+    "data": "Select an option"
+  },
+  "hintText": "Please select",
+  "width": 200,
+  "dropdownMenuEntries": [
+    {
+      "value": "option1",
+      "label": "Option 1",
+      "leadingIcon": {
+        "type": "icon",
+        "iconData": "home"
+      }
+    },
+    {
+      "value": "option2",
+      "label": "Option 2",
+      "leadingIcon": {
+        "type": "icon",
+        "iconData": "settings"
+      }
+    },
+    {
+      "value": "option3",
+      "label": "Option 3",
+      "leadingIcon": {
+        "type": "icon",
+        "iconData": "favorite"
+      }
+    }
+  ]
+}
+```
\ No newline at end of file


Reviews:
user: login: divyanshub024
body: Thank you for this PR 🙌🏻 
Please check my suggestions. 
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-14T13:05:23Z
user: login: divyanshub024
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T18:13:57Z
user: login: Odinachi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T18:32:40Z
user: login: divyanshub024
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T18:35:33Z
user: login: divyanshub024
body: Thank you for such a great contribution @Odinachi 🙌🏻 💯 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T19:57:25Z

Review comments:
diff_hunk: @@ -0,0 +1,45 @@
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:stac/src/parsers/parsers.dart';
+import 'package:stac/src/parsers/stac_input_decoration_theme/stac_input_decoration_theme.dart';
+import 'package:stac/src/parsers/stac_offset/stac_offset.dart';
+import 'package:stac/src/utils/text_input_utils.dart';
+
+import '../stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart';
+
+export 'package:stac/src/parsers/stac_container/stac_container_parser.dart';
+
+part 'stac_dropdown_menu.freezed.dart';
+part 'stac_dropdown_menu.g.dart';
+
+@freezed
+abstract class StacDropdownMenu with _$StacDropdownMenu {
+  const factory StacDropdownMenu(
+      {@Default(true) bool enabled,
+      double? width,
+      double? menuHeight,
+      Map<String, dynamic>? leadingIcon,
+      Map<String, dynamic>? trailingIcon,
+      Map<String, dynamic>? label,
+      String? hintText,
+      String? helperText,
+      String? errorText,
+      Map<String, dynamic>? selectedTrailingIcon,
+      bool? enableFilter,
+      bool? enableSearch,
+      StacTextInputType? keyboardType,
+      StacTextStyle? textStyle,
+      @Default(TextAlign.start) TextAlign textAlign,
+      StacInputDecorationTheme? inputDecorationTheme,
+      @Default([]) List<StacInputFormatter> inputFormatters,
+      StacOffset? alignmentOffset,
+      StacEdgeInsets? expandedInsets,
+      bool? requestFocusOnTap,
+      dynamic initialSelection,
+      @Default([]) List<StacDropdownMenuEntry> dropdownMenuEntries,
+      @Default(DropdownMenuCloseBehavior.all)
+      DropdownMenuCloseBehavior? closeBehavior}) = _StacDropdownMenu;
path: packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu.dart
user: login: divyanshub024
body: Can we please add a comma (,) for better formatting? 
created_at: 2025-04-14T12:14:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,79 @@
+# DropdownMenu
+
+The Stac DropdownMenu allows you to build a Flutter DropdownMenu widget using JSON.
+To know more about the DropdownMenu widget in Flutter, refer to
+the [official documentation](https://api.flutter.dev/flutter/material/DropdownMenu-class.html).
+
+## Properties
+
+| Property             | Type                            | Description                                               |
+|----------------------|---------------------------------|-----------------------------------------------------------|
+| dropdownMenuEntries  | `List<StacDropdownMenuEntries>` | The entries to display in the dropdown menu.              |
path: website/docs/widgets/dropdown_menu.md
user: login: divyanshub024
body: Some properties are missing here. Can we add all of them? 
created_at: 2025-04-14T12:56:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:stac/src/parsers/parsers.dart';
+
+import '../../../stac.dart' show Stac;
path: packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart
user: login: divyanshub024
body: Can we import this instead? 
"import 'package:stac/src/framework/framework.dart';" 
created_at: 2025-04-14T12:58:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:stac/src/parsers/parsers.dart';
+
+import '../../../stac.dart' show Stac;
+
+part 'stac_dropdown_menu_entry.freezed.dart';
+part 'stac_dropdown_menu_entry.g.dart';
+
+@freezed
+abstract class StacDropdownMenuEntry with _$StacDropdownMenuEntry {
+  const factory StacDropdownMenuEntry({
+    dynamic value,
+    @Default("") String label,
+    Map<String, dynamic>? labelWidget,
+    Map<String, dynamic>? leadingIcon,
+    Map<String, dynamic>? trailingIcon,
+    @Default(true) bool enabled,
+    StacButtonStyle? style,
+  }) = _StacBDropdownMenuEntry;
+
+  factory StacDropdownMenuEntry.fromJson(Map<String, dynamic> json) =>
+      _$StacDropdownMenuEntryFromJson(json);
+}
+
+extension StacDropdownMenuEntryParser on StacDropdownMenuEntry? {
+  DropdownMenuEntry? parse(BuildContext context) {
+    return DropdownMenuEntry(
+      value: this?.value,
+      label: this!.label,
+      labelWidget: Stac.fromJson(this?.labelWidget, context),
+      leadingIcon: Stac.fromJson(this?.leadingIcon, context),
+      enabled: this?.enabled ?? false,
path: packages/stac/lib/src/parsers/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart
user: login: divyanshub024
body: By default, it should be true as mentioned in `DropdownMenuEntry` class. 
created_at: 2025-04-14T12:59:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+import 'package:flutter/material.dart';
+import 'package:stac/src/parsers/stac_dropdown_menu/stac_dropdown_menu.dart';
+import 'package:stac/src/parsers/stac_input_decoration_theme/stac_input_decoration_theme.dart';
+import 'package:stac/src/parsers/stac_offset/stac_offset.dart';
+
+import '../../../stac.dart';
+import '../../utils/widget_type.dart';
+import '../stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart';
+
+class StacDropdownMenuParser extends StacParser<StacDropdownMenu> {
+  const StacDropdownMenuParser();
+
+  @override
+  String get type => WidgetType.dropdownMenu.name;
+
+  @override
+  StacDropdownMenu getModel(Map<String, dynamic> json) =>
+      StacDropdownMenu.fromJson(json);
+
+  @override
+  Widget parse(BuildContext context, StacDropdownMenu model) {
+    return _DropDownMenuWidget(model: model);
+  }
+}
+
+class _DropDownMenuWidget extends StatefulWidget {
+  const _DropDownMenuWidget({required this.model});
+
+  final StacDropdownMenu model;
+
+  @override
+  State<_DropDownMenuWidget> createState() => _DropDownMenuWidgetState();
+}
+
+class _DropDownMenuWidgetState extends State<_DropDownMenuWidget> {
+  final TextEditingController _controller = TextEditingController();
+  final _focusNode = FocusNode();
+  late final StacDropdownMenu model;
+
+  @override
+  void initState() {
+    model = widget.model;
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return DropdownMenu(
+      initialSelection: model.initialSelection,
+      focusNode: _focusNode,
+      controller: _controller,
+      dropdownMenuEntries: model.dropdownMenuEntries
+          .map((StacDropdownMenuEntry dropDownMenu) =>
+              dropDownMenu.parse(context)!)
+          .toList(),
+      enabled: model.enabled,
+      width: model.width,
+      menuHeight: model.menuHeight,
+      leadingIcon: Stac.fromJson(model.leadingIcon, context),
+      trailingIcon: Stac.fromJson(model.trailingIcon, context),
+      label: Stac.fromJson(model.label, context),
+      hintText: model.hintText,
+      helperText: model.helperText,
+      errorText: model.errorText,
+      selectedTrailingIcon: Stac.fromJson(model.selectedTrailingIcon, context),
+      enableFilter: model.enableFilter ?? false,
+      enableSearch: model.enableSearch ?? false,
path: packages/stac/lib/src/parsers/stac_dropdown_menu/stac_dropdown_menu_parser.dart
user: login: divyanshub024
body: `enableSearch` should be true by default. 
created_at: 2025-04-14T13:00:30Z
author_association: MEMBER
diff_hunk: @@ -20,6 +20,9 @@ the [official documentation](https://api.flutter.dev/flutter/material/DropdownMe
 | menuHeight           | `double?`                       | The height of the dropdown menu.                          |
 | inputDecorationTheme | `StacInputDecorationTheme?`     | The theme to use for the input decoration.                |
 | textStyle            | `StacTextStyle?`                | The text style to use for the dropdown menu.              |
+| enableFilter         | `bool`                          | Whether to enable filtering. Defaults to `false`.         |
+| enableSearch         | `bool`                          | Whether to enable search. Defaults to `false`.            |
path: website/docs/widgets/dropdown_menu.md
user: login: divyanshub024
body: I think it should be " Defaults to `true`."
created_at: 2025-04-14T18:13:57Z
author_association: MEMBER
diff_hunk: @@ -20,6 +20,9 @@ the [official documentation](https://api.flutter.dev/flutter/material/DropdownMe
 | menuHeight           | `double?`                       | The height of the dropdown menu.                          |
 | inputDecorationTheme | `StacInputDecorationTheme?`     | The theme to use for the input decoration.                |
 | textStyle            | `StacTextStyle?`                | The text style to use for the dropdown menu.              |
+| enableFilter         | `bool`                          | Whether to enable filtering. Defaults to `false`.         |
+| enableSearch         | `bool`                          | Whether to enable search. Defaults to `false`.            |
path: website/docs/widgets/dropdown_menu.md
user: login: Odinachi
body: My bad, I missed that.
created_at: 2025-04-14T18:32:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,6 +20,9 @@ the [official documentation](https://api.flutter.dev/flutter/material/DropdownMe
 | menuHeight           | `double?`                       | The height of the dropdown menu.                          |
 | inputDecorationTheme | `StacInputDecorationTheme?`     | The theme to use for the input decoration.                |
 | textStyle            | `StacTextStyle?`                | The text style to use for the dropdown menu.              |
+| enableFilter         | `bool`                          | Whether to enable filtering. Defaults to `false`.         |
+| enableSearch         | `bool`                          | Whether to enable search. Defaults to `false`.            |
path: website/docs/widgets/dropdown_menu.md
user: login: divyanshub024
body: No worries... :) 
created_at: 2025-04-14T18:35:33Z
author_association: MEMBER

PR comments:
user: login: CLAassistant
created_at: 2025-04-14T08:51:23Z
body: [![CLA assistant check](https://cla-assistant.io/pull/badge/signed)](https://cla-assistant.io/StacDev/stac?pullRequest=216) <br/>All committers have signed the CLA.
author_association: NONE
user: login: Odinachi
created_at: 2025-04-14T15:31:45Z
body: Resolved.
author_association: CONTRIBUTOR