Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
af3b153
feat(shadcn-ui): add missing ShadInput config fields to FlutterShadcn…
CGQAQ Feb 11, 2026
119f76a
feat(shadcn-ui): regenerate bindings and add input demo page
CGQAQ Feb 11, 2026
fc50ee9
fix(shadcn-input): add alignment mapping for textAlign support
CGQAQ Feb 11, 2026
f1b5cc3
refactor(use-cases): use FlutterShadcnInput directly in input demo
CGQAQ Feb 11, 2026
3cf6ede
chore(use-cases): link local react-shadcn-ui package
CGQAQ Feb 11, 2026
3559977
fix(use-cases): fix quick start cards layout with horizontal scroll
CGQAQ Feb 11, 2026
695f5f7
feat(use-cases): enable all shadcn component demos
CGQAQ Feb 11, 2026
108dd46
fix(use-cases): allow quick start cards to expand when few items
CGQAQ Feb 11, 2026
ce319fb
feat(use-cases): hide Shadcn UI quick start card in production
CGQAQ Feb 11, 2026
0c2ef6b
fix(use-cases): add prebuild step for linked react-shadcn-ui package
CGQAQ Feb 12, 2026
c8dbfb9
fix(use-cases): install react-shadcn-ui deps before prebuild
CGQAQ Feb 12, 2026
feee4ec
fix(react-shadcn-ui): track generated src/lib/ files for CI builds
CGQAQ Feb 12, 2026
3db7614
Merge branch 'feat/use-cases-updates' into feat/shadcn-input-and-use-…
CGQAQ Feb 14, 2026
06cc4f7
Merge branch 'feat/shadcn-input' into feat/shadcn-input-and-use-cases
CGQAQ Feb 14, 2026
9af77a4
feat(react-shadcn-ui): add InputOTP component
CGQAQ Feb 14, 2026
711ee51
feat: add Shadcn Menubar and Cupertino Menu components
CGQAQ Feb 14, 2026
e4ca915
fix(react-shadcn-ui): enhance popover component and fix gesture confl…
CGQAQ Feb 14, 2026
2bfa995
feat(react-shadcn-ui): enhance progress component with color, height,…
CGQAQ Feb 14, 2026
d8a1c1c
feat(webf-shadcn-ui): rewrite RadioGroup component with proper naming…
CGQAQ Feb 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions native_uis/webf_cupertino_ui/lib/src/menu.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Properties for <flutter-cupertino-menu>.
* A tap-triggered popup menu wrapping Flutter's showMenu with Cupertino styling.
*/

interface MenuAction {
/** Button label text. */
text: string;
/** Optional trailing icon name (Cupertino icon key). */
icon?: string;
/** Marks this action as destructive (red). */
destructive?: boolean;
/**
* Optional event name associated with this action.
* If omitted, a name is derived from the text.
*/
event?: string;
}

interface FlutterCupertinoMenuProperties {
/**
* Whether the menu trigger is disabled.
* When disabled, tapping the child will not open the menu.
* @default false
*/
disabled?: boolean;
}

interface FlutterCupertinoMenuMethods {
/**
* Set the list of actions displayed in the popup menu.
*/
setActions(actions: MenuAction[]): void;
}

interface FlutterCupertinoMenuSelectDetail {
/** Zero-based index of the selected action. */
index: number;
/** Action text. */
text: string;
/** Event name for this action. */
event: string;
/** Whether the action is destructive. */
destructive: boolean;
}

interface FlutterCupertinoMenuEvents {
/**
* Fired when an action is selected.
* detail contains metadata about the selected action.
*/
select: CustomEvent<FlutterCupertinoMenuSelectDetail>;
}
208 changes: 208 additions & 0 deletions native_uis/webf_cupertino_ui/lib/src/menu.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* Copyright (C) 2024-present The OpenWebF Company. All rights reserved.
* Licensed under the Apache License, Version 2.0.
*/
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:webf/rendering.dart';
import 'package:webf/webf.dart';
import 'package:webf/dom.dart' as dom;

import 'icon.dart';
import 'menu_bindings_generated.dart';
import 'logger.dart';

/// WebF custom element that provides a tap-triggered popup menu
/// with Cupertino styling.
///
/// Exposed as `<flutter-cupertino-menu>` in the DOM.
class FlutterCupertinoMenu extends FlutterCupertinoMenuBindings {
FlutterCupertinoMenu(super.context) {
_actions = <Map<String, dynamic>>[];
}

bool _disabled = false;
List<Map<String, dynamic>> _actions = <Map<String, dynamic>>[];

@override
bool get disabled => _disabled;

@override
set disabled(value) {
final bool next = value == true;
if (next != _disabled) {
_disabled = next;
state?.requestUpdateState(() {});
}
}

@override
bool get allowsInfiniteHeight => true;

@override
bool get allowsInfiniteWidth => true;

static StaticDefinedSyncBindingObjectMethodMap menuMethods = {
'setActions': StaticDefinedSyncBindingObjectMethod(
call: (element, args) {
final menu = castToType<FlutterCupertinoMenu>(element);

if (args.isNotEmpty) {
final actionsData = args[0];

if (actionsData is List) {
final List<Map<String, dynamic>> newActions = <Map<String, dynamic>>[];
for (final item in actionsData) {
if (item is Map) {
newActions.add(
Map<String, dynamic>.from(
item.map((key, value) => MapEntry(key.toString(), value)),
),
);
} else {
logger.w('Skipping non-map item in actions list: $item');
}
}
menu._actions = newActions;
} else {
menu._actions = <Map<String, dynamic>>[];
}
menu.state?.requestUpdateState(() {});
} else {
menu._actions = <Map<String, dynamic>>[];
menu.state?.requestUpdateState(() {});
}
},
),
};

@override
List<StaticDefinedSyncBindingObjectMethodMap> get methods =>
<StaticDefinedSyncBindingObjectMethodMap>[
...super.methods,
menuMethods,
];

IconData? _getIconData(String iconName) {
final IconData? icon = FlutterCupertinoIcon.getIconType(iconName);
if (icon == null) {
logger.w('Icon not found for name: $iconName');
}
return icon;
}

@override
FlutterCupertinoMenuState? get state =>
super.state as FlutterCupertinoMenuState?;

@override
WebFWidgetElementState createState() {
return FlutterCupertinoMenuState(this);
}
}

class FlutterCupertinoMenuState extends WebFWidgetElementState {
FlutterCupertinoMenuState(super.widgetElement);

@override
FlutterCupertinoMenu get widgetElement =>
super.widgetElement as FlutterCupertinoMenu;

Widget? _findChild() {
for (final child in widgetElement.childNodes) {
if (child is dom.Element) {
return WebFWidgetElementChild(child: child.toWidget());
}
}
return null;
}

Future<void> _showMenu() async {
if (widgetElement._disabled || widgetElement._actions.isEmpty) return;

final RenderBox renderBox = context.findRenderObject() as RenderBox;
final Offset offset = renderBox.localToGlobal(Offset.zero);
final Size size = renderBox.size;

final List<PopupMenuEntry<int>> items = <PopupMenuEntry<int>>[];
for (int i = 0; i < widgetElement._actions.length; i++) {
final Map<String, dynamic> action = widgetElement._actions[i];
final bool isDestructive = action['destructive'] == true;
final String text = action['text'] as String? ?? '';
final String? iconName = action['icon'] as String?;

items.add(
PopupMenuItem<int>(
value: i,
child: Row(
children: [
Expanded(
child: Text(
text,
style: TextStyle(
color: isDestructive ? CupertinoColors.destructiveRed : null,
fontSize: 17,
),
),
),
if (iconName != null)
Padding(
padding: const EdgeInsets.only(left: 8),
child: Icon(
widgetElement._getIconData(iconName),
size: 20,
color: isDestructive ? CupertinoColors.destructiveRed : null,
),
),
],
),
),
);
}

final int? selected = await showMenu<int>(
context: context,
position: RelativeRect.fromLTRB(
offset.dx,
offset.dy + size.height,
offset.dx + size.width,
offset.dy + size.height,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
items: items,
);

if (selected != null && selected < widgetElement._actions.length) {
final Map<String, dynamic> action = widgetElement._actions[selected];
final String text = action['text'] as String? ?? '';
final bool isDestructive = action['destructive'] == true;
final String eventName = action['event'] as String? ??
text.toLowerCase().replaceAll(' ', '_');

final Map<String, dynamic> detail = <String, dynamic>{
'index': selected,
'text': text,
'event': eventName,
'destructive': isDestructive,
};

widgetElement.dispatchEvent(CustomEvent('select', detail: detail));
}
}

@override
Widget build(BuildContext context) {
final Widget? child = _findChild();

if (child == null) {
return const SizedBox.shrink();
}

return GestureDetector(
onTap: widgetElement._disabled ? null : _showMenu,
child: child,
);
}
}
36 changes: 36 additions & 0 deletions native_uis/webf_cupertino_ui/lib/src/menu_bindings_generated.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `webf codegen`
// ignore_for_file: avoid_unused_constructor_parameters
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: prefer_void_to_null
import 'package:webf/webf.dart';
abstract class FlutterCupertinoMenuBindings extends WidgetElement {
FlutterCupertinoMenuBindings(super.context);
bool get disabled;
set disabled(value);
@override
void initializeAttributes(Map<String, ElementAttributeProperty> attributes) {
super.initializeAttributes(attributes);
attributes['disabled'] = ElementAttributeProperty(
getter: () => disabled.toString(),
setter: (value) => disabled = value == 'true' || value == '',
deleter: () => disabled = false
);
}
static StaticDefinedBindingPropertyMap flutterCupertinoMenuProperties = {
'disabled': StaticDefinedBindingProperty(
getter: (element) => castToType<FlutterCupertinoMenuBindings>(element).disabled,
setter: (element, value) =>
castToType<FlutterCupertinoMenuBindings>(element).disabled = value,
),
};
@override
List<StaticDefinedBindingPropertyMap> get properties => [
...super.properties,
flutterCupertinoMenuProperties,
];
}
5 changes: 5 additions & 0 deletions native_uis/webf_cupertino_ui/lib/webf_cupertino_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export 'src/form_section.dart';
export 'src/input.dart';
export 'src/search_text_field.dart';
export 'src/date_picker.dart';
export 'src/menu.dart';
import 'src/alert.dart';
import 'src/action_sheet.dart';
import 'src/context_menu.dart';
Expand All @@ -41,6 +42,7 @@ import 'src/tab_view.dart';
import 'src/sliding_segmented_control.dart';
import 'src/list_tile.dart';
import 'src/search_text_field.dart';
import 'src/menu.dart';

/// Installs all Cupertino UI custom elements for WebF.
///
Expand Down Expand Up @@ -106,6 +108,9 @@ void installWebFCupertinoUI() {
WebF.defineCustomElement(
'flutter-cupertino-date-picker',
(context) => FlutterCupertinoDatePicker(context));
WebF.defineCustomElement(
'flutter-cupertino-menu',
(context) => FlutterCupertinoMenu(context));


// WebF.defineCustomElement('flutter-cupertino-input', (context) => FlutterCupertinoInput(context));
Expand Down
4 changes: 2 additions & 2 deletions native_uis/webf_shadcn_ui/IMPLEMENTATION_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ native_uis/webf_shadcn_ui/
| Input | `<flutter-shadcn-input>` | input.dart | input.d.ts | Done |
| Textarea | `<flutter-shadcn-textarea>` | textarea.dart | textarea.d.ts | Done |
| Checkbox | `<flutter-shadcn-checkbox>` | checkbox.dart | checkbox.d.ts | Done |
| Radio | `<flutter-shadcn-radio>` | radio.dart | radio.d.ts | Done |
| RadioGroup | `<flutter-shadcn-radio-group>` | radio_group.dart | radio_group.d.ts | Done |
| Switch | `<flutter-shadcn-switch>` | switch.dart | switch.d.ts | Done |
| Select | `<flutter-shadcn-select>` | select.dart | select.d.ts | Done |
| Slider | `<flutter-shadcn-slider>` | slider.dart | slider.d.ts | Done |
Expand Down Expand Up @@ -144,7 +144,7 @@ The following slot elements are implemented for compositional components:
- `<flutter-shadcn-table-cell>`

### Other Slots
- `<flutter-shadcn-radio-item>`
- `<flutter-shadcn-radio-group-item>`
- `<flutter-shadcn-combobox-item>`
- `<flutter-shadcn-popover-trigger>`
- `<flutter-shadcn-popover-content>`
Expand Down
2 changes: 1 addition & 1 deletion native_uis/webf_shadcn_ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ void main() {
- `<flutter-shadcn-input>` - Text input field
- `<flutter-shadcn-textarea>` - Multi-line text input
- `<flutter-shadcn-checkbox>` - Checkbox control
- `<flutter-shadcn-radio>` - Radio button group
- `<flutter-shadcn-radio-group>` - Radio button group
- `<flutter-shadcn-switch>` - Toggle switch
- `<flutter-shadcn-select>` - Dropdown select
- `<flutter-shadcn-slider>` - Range slider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ abstract class FlutterShadcnAccordionBindings extends WidgetElement {
super.initializeAttributes(attributes);
attributes['type'] = ElementAttributeProperty(
getter: () => type?.toString(),
setter: (value) => this.type = value,
setter: (value) => type = value,
deleter: () => type = null
);
attributes['value'] = ElementAttributeProperty(
getter: () => value?.toString(),
setter: (value) => this.value = value,
setter: (value) => value = value,
deleter: () => value = null
);
attributes['collapsible'] = ElementAttributeProperty(
getter: () => collapsible.toString(),
setter: (value) => this.collapsible = value == 'true' || value == '',
setter: (value) => collapsible = value == 'true' || value == '',
deleter: () => collapsible = false
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ abstract class FlutterShadcnAlertBindings extends WidgetElement {
super.initializeAttributes(attributes);
attributes['variant'] = ElementAttributeProperty(
getter: () => variant?.toString(),
setter: (value) => this.variant = value,
setter: (value) => variant = value,
deleter: () => variant = null
);
attributes['icon'] = ElementAttributeProperty(
getter: () => icon?.toString(),
setter: (value) => this.icon = value,
setter: (value) => icon = value,
deleter: () => icon = null
);
}
Expand Down
Loading
Loading