| import 'dart:async'; |
| |
| import 'package:jaspr/dom.dart'; |
| import 'package:jaspr/jaspr.dart'; |
| |
| import '../../../utils/styles.dart'; |
| |
| class TextButton extends StatelessComponent { |
| const TextButton({ |
| required this.label, |
| this.onClick, |
| this.disabled = false, |
| this.isLoading = false, |
| this.tooltip, |
| this.classes, |
| this.icon, |
| this.iconSize = 18.0, |
| this.primary = true, |
| super.key, |
| }); |
| |
| final String label; |
| final FutureOr<void> Function()? onClick; |
| final bool disabled; |
| final bool isLoading; |
| final String? tooltip; |
| final String? classes; |
| final Component? icon; |
| final double iconSize; |
| final bool primary; |
| |
| @override |
| Component build(BuildContext context) { |
| final isDisabled = disabled || isLoading; |
| final classNames = [ |
| 'text-button', |
| if (primary) 'primary', |
| if (classes case final extraClasses? when extraClasses.isNotEmpty) extraClasses, |
| ].join(' '); |
| final attributes = <String, String>{}; |
| if (tooltip != null) { |
| attributes['title'] = tooltip!; |
| } |
| if (isDisabled) { |
| attributes['disabled'] = 'true'; |
| } |
| |
| final displayIcon = isLoading ? const div(classes: 'text-button-spinner', []) : icon; |
| |
| return button( |
| classes: classNames, |
| attributes: attributes, |
| onClick: isDisabled ? null : onClick, |
| [ |
| ?displayIcon, |
| .text(label), |
| ], |
| ); |
| } |
| |
| @css |
| static List<StyleRule> get styles => [ |
| css('.text-button', [ |
| css('&').styles( |
| display: .inlineFlex, |
| padding: .symmetric(vertical: 5.px, horizontal: 8.px), |
| border: .all(color: colorBorder, width: 1.px), |
| radius: .circular(6.px), |
| outline: const Outline(style: .none), |
| cursor: .pointer, |
| transition: Transition.combine([ |
| Transition('background-color', duration: 200.ms, curve: .ease), |
| Transition('border-color', duration: 200.ms, curve: .ease), |
| Transition('color', duration: 200.ms, curve: .ease), |
| ]), |
| justifyContent: .center, |
| alignItems: .center, |
| gap: Gap.all(6.px), |
| color: colorOnSurface, |
| fontSize: 13.px, |
| fontWeight: .w500, |
| whiteSpace: .noWrap, |
| backgroundColor: colorContainer, |
| ), |
| css('&:not(:disabled):hover').styles( |
| backgroundColor: colorContainerHigh, |
| ), |
| css('&.primary:not(:disabled):hover').styles( |
| border: .all(color: colorPrimary, width: 1.px), |
| ), |
| css('&:disabled').styles( |
| opacity: 0.5, |
| cursor: .notAllowed, |
| ), |
| ]), |
| css('.text-button-spinner').styles( |
| raw: { |
| 'width': '12px', |
| 'height': '12px', |
| 'border': '2px solid currentColor', |
| 'border-top-color': 'transparent', |
| 'border-radius': '50%', |
| 'animation': 'text-button-spin 0.8s linear infinite', |
| }, |
| ), |
| css.keyframes('text-button-spin', { |
| '0%': const Styles(raw: {'transform': 'rotate(0deg)'}), |
| '100%': const Styles(raw: {'transform': 'rotate(360deg)'}), |
| }), |
| ]; |
| } |