Button Component Demo

A component that renders a Bootstrap Button element using OverReact’s statically-typed React prop API.

Contents

Example

Use props.skin to choose from one of six predefined styles for the Button component, each serving its own semantic purpose.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement buttonExamplesDemo() =>
  (Dom.div()..className = 'btn-toolbar')(
    Button()('Primary'),
    (Button()..skin = ButtonSkin.SECONDARY)('Secondary'),
    (Button()..skin = ButtonSkin.SUCCESS)('Success'),
    (Button()..skin = ButtonSkin.INFO)('Info'),
    (Button()..skin = ButtonSkin.WARNING)('Warning'),
    (Button()..skin = ButtonSkin.DANGER)('Danger'),
    (Button()
      ..href = '#'
      ..skin = ButtonSkin.LINK
    )('Link')
  );
import 'package:over_react/over_react.dart';

import '../demo_components.dart';
part 'button.over_react.g.dart';

UiFactory<ButtonProps> Button = _$Button;

mixin ButtonProps on UiProps {
  /// The skin / "context" for the [Button].
  ///
  /// See: <https://getbootstrap.com/docs/4.4/components/buttons/#examples>.
  ///
  /// Default: [ButtonSkin.PRIMARY]
  ButtonSkin skin;

  /// The size of the [Button].
  ///
  /// See: <https://getbootstrap.com/docs/4.4/components/buttons/#sizes>.
  ///
  /// Default: [ButtonSize.DEFAULT]
  ButtonSize size;

  /// Whether the [Button] should appear "active".
  ///
  /// See: <https://getbootstrap.com/docs/4.4/components/buttons/#active-state>
  ///
  /// Default: false
  bool isActive;

  /// Whether the [Button] is disabled.
  ///
  /// See: <https://getbootstrap.com/docs/4.4/components/buttons/#disabled-state>
  ///
  /// Default: false
  @Accessor(key: 'disabled', keyNamespace: '')
  bool isDisabled;

  /// Whether the [Button] is a block level button -- that which spans the full
  /// width of its parent.
  ///
  /// Default: false
  bool isBlock;

  /// The HTML `href` attribute value for the [Button].
  ///
  /// If set, the item will render via [Dom.a].
  ///
  /// _Proxies [DomProps.href]_
  @Accessor(keyNamespace: '')
  String href;

  /// The HTML `target` attribute value for the [Button].
  ///
  /// If set, the item will render via [Dom.a].
  ///
  /// _Proxies [DomProps.target]_
  @Accessor(keyNamespace: '')
  String target;

  /// The HTML `type` attribute value for the [Button] when
  /// rendered via [Dom.button].
  ///
  /// This will only be applied if [href] is not set.
  ///
  /// _Proxies [DomProps.type]_
  ///
  /// Default: [ButtonType.BUTTON]
  ButtonType type;
}

mixin ButtonState on UiState {}

class ButtonComponent<T extends ButtonProps, S extends ButtonState>
    extends UiStatefulComponent2<T, S> {
  @override
  get defaultProps => (newProps()
    ..skin = ButtonSkin.PRIMARY
    ..size = ButtonSize.DEFAULT
    ..isActive = false
    ..isDisabled = false
    ..isBlock = false
    ..type = ButtonType.BUTTON
  );

  @override
  render() {
    return renderButton(props.children);
  }

  ReactElement renderButton(dynamic children) {
    BuilderOnlyUiFactory<DomProps> factory = buttonDomNodeFactory;

    return (factory()
      ..modifyProps(addUnconsumedDomProps)
      ..className = getButtonClasses().toClassName()
      ..href = props.href
      ..target = props.target
      ..type = type
      ..disabled = isAnchorLink ? null : props.isDisabled
      ..aria.disabled = isAnchorLink ? props.isDisabled : null
    )(children);
  }

  ClassNameBuilder getButtonClasses() {
    return forwardingClassNameBuilder()
      ..add('btn')
      ..add('btn-block', props.isBlock)
      ..add('active', isActive)
      ..add('disabled', props.isDisabled)
      ..add(props.skin.className)
      ..add(props.size.className);
  }

  BuilderOnlyUiFactory<DomProps> get buttonDomNodeFactory => isAnchorLink ? Dom.a : Dom.button;

  bool get isAnchorLink => props.href != null;

  bool get isActive => props.isActive;

  String get type => isAnchorLink ? null : props.type.typeName;
}

class ButtonSkin extends ClassNameConstant {
  const ButtonSkin._(String name, String className) : super(name, className);

  /// [className] value: 'btn-primary'
  static const ButtonSkin PRIMARY =
      ButtonSkin._('PRIMARY', 'btn-primary');

  /// [className] value: 'btn-secondary'
  static const ButtonSkin SECONDARY =
      ButtonSkin._('SECONDARY', 'btn-secondary');

  /// [className] value: 'btn-danger'
  static const ButtonSkin DANGER =
      ButtonSkin._('DANGER', 'btn-danger');

  /// [className] value: 'btn-success'
  static const ButtonSkin SUCCESS =
      ButtonSkin._('SUCCESS', 'btn-success');

  /// [className] value: 'btn-warning'
  static const ButtonSkin WARNING =
      ButtonSkin._('WARNING', 'btn-warning');

  /// [className] value: 'btn-info'
  static const ButtonSkin INFO =
      ButtonSkin._('INFO', 'btn-info');

  /// [className] value: 'btn-link'
  static const ButtonSkin LINK =
      ButtonSkin._('LINK', 'btn-link');

  /// [className] value: 'btn-outline-primary'
  static const ButtonSkin PRIMARY_OUTLINE =
      ButtonSkin._('PRIMARY_OUTLINE', 'btn-outline-primary');

  /// [className] value: 'btn-outline-secondary'
  static const ButtonSkin SECONDARY_OUTLINE =
      ButtonSkin._('SECONDARY_OUTLINE', 'btn-outline-secondary');

  /// [className] value: 'btn-outline-danger'
  static const ButtonSkin DANGER_OUTLINE =
      ButtonSkin._('DANGER_OUTLINE', 'btn-outline-danger');

  /// [className] value: 'btn-outline-success'
  static const ButtonSkin SUCCESS_OUTLINE =
      ButtonSkin._('SUCCESS_OUTLINE', 'btn-outline-success');

  /// [className] value: 'btn-outline-warning'
  static const ButtonSkin WARNING_OUTLINE =
      ButtonSkin._('WARNING_OUTLINE', 'btn-outline-warning');

  /// [className] value: 'btn-outline-info'
  static const ButtonSkin INFO_OUTLINE =
      ButtonSkin._('INFO_OUTLINE', 'btn-outline-info');
}

class ButtonSize extends ClassNameConstant {
  const ButtonSize._(String name, String className) : super(name, className);

  /// [className] value: ''
  static const ButtonSize DEFAULT =
      ButtonSize._('DEFAULT', '');

  /// [className] value: 'btn-lg'
  static const ButtonSize LARGE =
      ButtonSize._('LARGE', 'btn-lg');

  /// [className] value: 'btn-sm'
  static const ButtonSize SMALL =
      ButtonSize._('SMALL', 'btn-sm');
}

Button types

By default, the Button component renders a <button> element. However, if props.href is set, an <a> will be rendered instead.

Optionally, set props.type to ButtonType.SUBMIT or ButtonType.RESET to render a submit / reset form button, respectively.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement buttonTypesDemo() =>
  (Dom.div()..className = 'btn-toolbar')(
    Button()('Button'),
    (Button()..href = '#')('Link'),
    (Button()..type = ButtonType.SUBMIT)('Submit'),
    (Button()..type = ButtonType.RESET)('Reset')
  );

Outline buttons

Set props.skin to one of the "outline" options when you are in need of a Button, but not the ones that come with a hefty background color.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement buttonOutlineDemo() =>
  (Dom.div()..className = 'btn-toolbar')(
    (Button()..skin = ButtonSkin.PRIMARY_OUTLINE)('Primary'),
    (Button()..skin = ButtonSkin.SECONDARY_OUTLINE)('Secondary'),
    (Button()..skin = ButtonSkin.SUCCESS_OUTLINE)('Success'),
    (Button()..skin = ButtonSkin.INFO_OUTLINE)('Info'),
    (Button()..skin = ButtonSkin.WARNING_OUTLINE)('Warning'),
    (Button()..skin = ButtonSkin.DANGER_OUTLINE)('Danger')
  );

Sizes

Set props.size if you fancy larger or smaller Buttons.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement buttonSizesDemo() =>
  (Dom.div()..className = 'btn-toolbar')(
    (Button()..size = ButtonSize.SMALL)('Small'),
    Button()('Default'),
    (Button()..size = ButtonSize.LARGE)('Large')
  );

Set props.isBlock to render Buttons that span the full width of a parent element.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement buttonBlockDemo() => Dom.div()(
  (Button()
    ..isBlock = true
  )('Block level button'),
  (Button()
    ..isBlock = true
    ..skin = ButtonSkin.SECONDARY
  )('Block level button')
);

Active state

Set props.isActive to make a Button appear active.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement buttonActiveDemo() =>
  (Dom.div()..className = 'btn-toolbar')(
    (Button()
      ..isActive = true
    )('Primary button'),
    (Button()
      ..isActive = true
      ..skin = ButtonSkin.SECONDARY
    )('Button')
  );

Disabled state

Set props.isDisabled to disable a Button.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement buttonDisabledDemo() =>
  (Dom.div()..className = 'btn-toolbar')(
    (Button()
      ..isDisabled = true
    )('Primary button'),
    (Button()
      ..href = '#'
      ..isDisabled = true
      ..skin = ButtonSkin.SECONDARY
    )('Link')
  );