blob: 0c8e284982ae6e0b5794eb40e627c314967620b3 [file]
import 'dart:async';
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:web/web.dart' as web;
import '../../../utils/styles.dart';
import '../../shared/components/material_icon.dart';
import '../models/device_mode.dart';
class DeviceModeDropdown extends StatefulComponent {
const DeviceModeDropdown({
required this.mode,
required this.onModeSelected,
this.disabled = false,
super.key,
});
final DeviceMode mode;
final ValueChanged<DeviceMode> onModeSelected;
final bool disabled;
@override
State<DeviceModeDropdown> createState() => _DeviceModeDropdownState();
@css
static List<StyleRule> get styles => _DeviceModeDropdownState.styles;
}
class _DeviceModeDropdownState extends State<DeviceModeDropdown> {
bool _isOpen = false;
StreamSubscription<web.MouseEvent>? _clickOutsideSubscription;
void _toggleDropdown(web.Event event) {
event.stopPropagation(); // prevent immediate close from clicking the trigger itself
setState(() {
_isOpen = !_isOpen;
});
if (_isOpen) {
_listenForClickOutside();
} else {
_cancelListenForClickOutside();
}
}
void _listenForClickOutside() {
_clickOutsideSubscription?.cancel();
_clickOutsideSubscription = web.EventStreamProviders.clickEvent.forTarget(web.document).listen((e) {
if (!mounted) {
return;
}
setState(() {
_isOpen = false;
});
_cancelListenForClickOutside();
});
}
void _cancelListenForClickOutside() {
_clickOutsideSubscription?.cancel();
_clickOutsideSubscription = null;
}
@override
void dispose() {
_cancelListenForClickOutside();
super.dispose();
}
@override
Component build(BuildContext context) {
final currentMode = component.mode;
final disabled = component.disabled;
return div(classes: 'device-dropdown-container', [
button(
classes: 'device-dropdown-trigger${disabled ? ' disabled' : ''}',
attributes: disabled ? {'disabled': 'true'} : {},
events: disabled ? {} : {'click': _toggleDropdown},
[
materialIcon(currentMode.icon, size: 18.0),
span(classes: 'device-dropdown-label', [.text(currentMode.title)]),
materialIcon(MyMaterialIcons.keyboardArrowDown, size: 16.0, color: 'currentColor'),
],
),
if (_isOpen)
div(classes: 'device-dropdown-menu', [
for (final mode in DeviceMode.values)
div(
classes: 'device-dropdown-item${mode == currentMode ? ' active' : ''}',
events: {
'click': (e) {
component.onModeSelected(mode);
setState(() {
_isOpen = false;
});
_cancelListenForClickOutside();
},
},
[
materialIcon(mode.icon, size: 18.0),
span([.text(mode.title)]),
],
),
]),
]);
}
static List<StyleRule> get styles => [
css('.device-dropdown-container', [
css('&').styles(
display: .inlineFlex,
position: const .relative(),
),
css('.device-dropdown-trigger').styles(
display: .flex,
height: 28.px,
padding: .symmetric(horizontal: 8.px),
border: .none,
radius: .circular(4.px),
cursor: .pointer,
transition: Transition('background-color', duration: 150.ms, curve: .ease),
justifyContent: .center,
alignItems: .center,
gap: Gap.all(6.px),
color: colorOnSurface,
whiteSpace: .noWrap,
backgroundColor: Colors.transparent,
),
css('.device-dropdown-trigger:not(.disabled):hover').styles(
backgroundColor: colorContainerHigh,
),
css('.device-dropdown-trigger.disabled').styles(
opacity: 0.5,
cursor: .notAllowed,
),
css('.device-dropdown-label').styles(
color: colorOnSurface,
fontSize: 13.px,
fontWeight: .w500,
),
css('.device-dropdown-menu').styles(
display: .flex,
position: .absolute(top: 34.px, left: 0.px),
zIndex: const ZIndex(100),
minWidth: 180.px,
padding: .symmetric(vertical: 4.px),
border: .all(color: colorBorder, width: 1.px),
radius: .circular(8.px),
shadow: BoxShadow(
offsetX: 0.px,
offsetY: 8.px,
blur: 24.px,
color: Colors.black.withOpacity(0.5),
),
backdropFilter: .blur(8.px),
flexDirection: .column,
backgroundColor: colorContainer,
),
css('.device-dropdown-item').styles(
display: .flex,
padding: .symmetric(vertical: 8.px, horizontal: 12.px),
cursor: .pointer,
transition: .combine([
Transition('background-color', duration: 100.ms, curve: .ease),
Transition('color', duration: 100.ms, curve: .ease),
]),
alignItems: .center,
gap: .all(10.px),
color: colorOnSurfaceVariant,
fontSize: 13.px,
fontWeight: .w400,
),
css('.device-dropdown-item:hover').styles(
color: colorOnSurface,
backgroundColor: colorContainerHigh,
),
css('.device-dropdown-item.active').styles(
color: colorOnSurface,
fontWeight: .w500,
),
]),
];
}