ListGroup Component Demo

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

Contents

Basic example

The most basic ListGroup component is simply list ListGroupItem components.

The components encapsulate the complexity of the underlying HTML, ensuring that all the default CSS classes and other HTML attributes are present, leaving you with minimal markup.

Rendering OverReact Demo . . .
part of over_react.web.demos;

ReactElement listGroupBasicDemo() =>
  ListGroup()(
    ListGroupItem()('Dapibus ac facilisis in'),
    ListGroupItem()('Cras sit amet nibh libero'),
    ListGroupItem()('Porta ac consectetur ac'),
    ListGroupItem()('Vestibulum at eros')
  );
part of over_react.web.demo_components;

/// Bootstrap's `ListGroup` component is flexible and powerful for
/// displaying lists of [ListGroupItem] components.
///
/// See: <http://v4-alpha.getbootstrap.com/components/list-group/>
@Factory()
UiFactory<ListGroupProps> ListGroup;

@Props()
class ListGroupProps extends UiProps {
  /// The HTML element type for the [ListGroup], specifying its
  /// DOM representation when rendered.
  ///
  /// Default: [ListGroupElementType.DIV]
  ListGroupElementType elementType;
}

@Component()
class ListGroupComponent extends UiComponent<ListGroupProps> {
  @override
  Map getDefaultProps() => (newProps()
    ..elementType = ListGroupElementType.DIV
  );

  @override
  render() {
    var classes = forwardingClassNameBuilder()
      ..add('list-group');

    return (props.elementType.componentBuilderFactory()
      ..addProps(copyUnconsumedDomProps())
      ..className = classes.toClassName()
    )(props.children);
  }
}

/// Options for the [Element] that will be used when
/// rendering a [ListGroup] component.
class ListGroupElementType {
  final BuilderOnlyUiFactory<DomProps> componentBuilderFactory;
  ListGroupElementType._internal(this.componentBuilderFactory);

  /// A [Dom.ul] (HTML `<ul>` element)
  static final ListGroupElementType UL  = new ListGroupElementType._internal(Dom.ul);

  /// A [Dom.div] (HTML `<div>` element)
  static final ListGroupElementType DIV = new ListGroupElementType._internal(Dom.div);
}
part of over_react.web.demo_components;

/// Nest one or more `ListGroupItem` components within a [ListGroup]
/// to render individual items within a list.
///
/// See: <http://v4-alpha.getbootstrap.com/components/list-group/>
@Factory()
UiFactory<ListGroupItemProps> ListGroupItem;

@Props()
class ListGroupItemProps extends UiProps {
  /// The HTML element type for the [ListGroupItem], specifying its DOM
  /// representation when rendered.
  ///
  /// Will only be used if [href] and [onClick] are both `null`.
  ///
  /// Default: [ListGroupItemElementType.SPAN]
  ListGroupItemElementType elementType;

  /// Optional header text to display within the [ListGroupItem] above
  /// the value of [children].
  ///
  /// See: <http://v4-alpha.getbootstrap.com/components/list-group/#custom-content>.
  dynamic header;

  /// The size of the [header] text you desire.
  ///
  /// Default: [ListGroupItemHeaderElementSize.H5]
  ListGroupItemHeaderElementSize headerSize;

  /// Additional props to be added to the [header] element _(if specified)_.
  Map headerProps;

  /// The skin / "context" for the [ListGroupItem].
  ///
  /// See: <http://v4-alpha.getbootstrap.com/components/list-group/#contextual-classes>.
  ///
  /// Default: [ListGroupItemSkin.DEFAULT]
  ListGroupItemSkin skin;

  /// Whether the [ListGroupItem] should appear "active".
  ///
  /// See: <http://v4-alpha.getbootstrap.com/components/list-group/#anchors-and-buttons>
  ///
  /// Default: false
  bool isActive;

  /// Whether the [ListGroupItem] is disabled.
  ///
  /// See: <http://v4-alpha.getbootstrap.com/components/list-group/#disabled-items>
  ///
  /// Default: false
  @Accessor(key: 'disabled', keyNamespace: '')
  bool isDisabled;

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

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

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

@Component()
class ListGroupItemComponent extends UiComponent<ListGroupItemProps> {
  @override
  Map getDefaultProps() => (newProps()
    ..elementType = ListGroupItemElementType.SPAN
    ..skin = ListGroupItemSkin.DEFAULT
    ..isActive = false
    ..isDisabled = false
    ..type = ButtonType.BUTTON
    ..headerSize = ListGroupItemHeaderElementSize.H5
  );

  @override
  render() {
    var children = props.children;

    if (props.header != null) {
      children = [
        renderItemHeader(),
        (Dom.p()
          ..className = 'list-group-item-text'
          ..key = 'item-text'
        )(props.children)
      ];
    }

    BuilderOnlyUiFactory<DomProps> factory = _getItemDomNodeFactory();

    return (factory()
      ..addProps(copyUnconsumedDomProps())
      ..className = _getItemClasses().toClassName()
      ..href = props.href
      ..target = props.target
      ..type = _isActionItem && !_isAnchorLink ? props.type.typeName : null
      ..disabled = _useDisabledAttr ? props.isDisabled : null
      ..addProps(ariaProps()
        ..disabled = !_useDisabledAttr ? props.isDisabled : null
      )
    )(children);
  }

  ReactElement renderItemHeader() {
    if (props.header == null) return null;

    var headerClasses = new ClassNameBuilder.fromProps(props.headerProps)
      ..add('list-group-item-heading');

    return (props.headerSize.componentBuilderFactory()
      ..addProps(props.headerProps)
      ..className = headerClasses.toClassName()
      ..key = 'item-header'
    )(props.header);
  }

  BuilderOnlyUiFactory<DomProps> _getItemDomNodeFactory() {
    var factory;

    if (props.href != null) {
      factory = Dom.a;
    } else if (props.onClick != null) {
      factory = Dom.button;
    } else {
      factory = props.elementType.componentBuilderFactory;
    }

    return factory;
  }

  ClassNameBuilder _getItemClasses() {
    return forwardingClassNameBuilder()
      ..add('list-group-item')
      ..add('list-group-item-action', _isActionItem)
      ..add('active', props.isActive)
      ..add('disabled', props.isDisabled)
      ..add(props.skin.className);
  }

  bool get _useDisabledAttr => _getItemDomNodeFactory() == Dom.button;

  bool get _isActionItem => (props.href ?? props.onClick) != null;

  bool get _isAnchorLink => props.href != null;
}

/// Contextual skin options for a [ListGroupItem] component.
class ListGroupItemSkin extends ClassNameConstant {
  const ListGroupItemSkin._(String name, String className) : super(name, className);

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

  /// [className] value: 'list-group-item-danger'
  static const ListGroupItemSkin DANGER =
      const ListGroupItemSkin._('DANGER', 'list-group-item-danger');

  /// [className] value: 'list-group-item-success'
  static const ListGroupItemSkin SUCCESS =
      const ListGroupItemSkin._('SUCCESS', 'list-group-item-success');

  /// [className] value: 'list-group-item-warning'
  static const ListGroupItemSkin WARNING =
      const ListGroupItemSkin._('WARNING', 'list-group-item-warning');

  /// [className] value: 'list-group-item-info'
  static const ListGroupItemSkin INFO =
      const ListGroupItemSkin._('INFO', 'list-group-item-info');
}

/// Options for the [Element] that will be used when rendering a [ListGroupItem] component.
class ListGroupItemElementType {
  final BuilderOnlyUiFactory<DomProps> componentBuilderFactory;
  ListGroupItemElementType._internal(this.componentBuilderFactory);

  /// A [Dom.li] (HTML `<li>` element)
  ///
  /// Will only be used if [ListGroupItemProps.href] and
  /// [ListGroupItemProps.onClick] are both `null`.
  ///
  /// Only use this when the parent [ListGroup] has
  /// [ListGroupProps.elementType] set to [ListGroupElementType.UL].
  static final ListGroupItemElementType LI =
      new ListGroupItemElementType._internal(Dom.li);

  /// A [Dom.span] (HTML `<span>` element)
  ///
  /// Will only be used if [ListGroupItemProps.href] and
  /// [ListGroupItemProps.onClick] are both `null`.
  static final ListGroupItemElementType SPAN =
      new ListGroupItemElementType._internal(Dom.span);
}

/// Options for the [Element] that will be used when rendering a [ListGroupItemProps.header].
class ListGroupItemHeaderElementSize {
  final BuilderOnlyUiFactory<DomProps> componentBuilderFactory;
  ListGroupItemHeaderElementSize._internal(this.componentBuilderFactory);

  /// A [Dom.h1] (HTML `<h1>` element)
  static final ListGroupItemHeaderElementSize H1 =
      new ListGroupItemHeaderElementSize._internal(Dom.h1);

  /// A [Dom.h2] (HTML `<h2>` element)
  static final ListGroupItemHeaderElementSize H2 =
      new ListGroupItemHeaderElementSize._internal(Dom.h2);

  /// A [Dom.h3] (HTML `<h3>` element)
  static final ListGroupItemHeaderElementSize H3 =
      new ListGroupItemHeaderElementSize._internal(Dom.h3);

  /// A [Dom.h4] (HTML `<h4>` element)
  static final ListGroupItemHeaderElementSize H4 =
      new ListGroupItemHeaderElementSize._internal(Dom.h4);

  /// A [Dom.h5] (HTML `<h5>` element)
  static final ListGroupItemHeaderElementSize H5 =
      new ListGroupItemHeaderElementSize._internal(Dom.h5);

  /// A [Dom.h6] (HTML `<h6>` element)
  static final ListGroupItemHeaderElementSize H6 =
      new ListGroupItemHeaderElementSize._internal(Dom.h6);
}

Tags

Nest a Tag component within any `ListGroupItem` to show unread counts, activity, etc.

Rendering OverReact Demo . . .
part of over_react.web.demos;

ReactElement listGroupTagsDemo() =>
  ListGroup()(
    ListGroupItem()(
      (Tag()
        ..className = 'float-xs-right'
        ..isPill = true
      )(14),
      'Cras justo odio'
    ),
    ListGroupItem()(
      (Tag()
        ..className = 'float-xs-right'
        ..isPill = true
      )(2),
      'Dapibus ac facilisis in'
    ),
    ListGroupItem()(
      (Tag()
        ..className = 'float-xs-right'
        ..isPill = true
      )(1),
      'Morbi leo risus'
    )
  );

Anchors and buttons

Set props.onClick to render an HTML <button> element, or props.href to render an HTML <a> element with hover, disabled, and active states.

Set props.isDisabled to disable an item, and props.isActive to make an item appear active.

Rendering OverReact Demo . . .
part of over_react.web.demos;

ReactElement listGroupAnchorsAndButtonsDemo() =>
  ListGroup()(
    (ListGroupItem()
      ..isActive = true
      ..href = '#'
    )('Cras justo odio'),
    (ListGroupItem()
      ..onClick = (_) {}
    )('Dapibus ac facilisis in'),
    (ListGroupItem()
      ..onClick = (_) {}
    )('Morbi leo risus'),
    (ListGroupItem()
      ..onClick = (_) {}
    )('Porta ac consectetur ac'),
    (ListGroupItem()
      ..isDisabled = true
      ..onClick = (_) {}
    )('Vestibulum at eros')
  );

Contextual skins

Set props.skin to style a ListGroupItem using contextual colors.

Rendering OverReact Demo . . .
part of over_react.web.demos;

ReactElement listGroupContextualSkinDemo() =>
  ListGroup()(
    (ListGroupItem()
      ..onClick = (_) {}
      ..skin = ListGroupItemSkin.SUCCESS
    )('Dapibus ac facilisis in'),
    (ListGroupItem()
      ..onClick = (_) {}
      ..skin = ListGroupItemSkin.INFO
    )('Cras sit amet nibh libero'),
    (ListGroupItem()
      ..onClick = (_) {}
      ..skin = ListGroupItemSkin.WARNING
    )('Porta ac consectetur ac'),
    (ListGroupItem()
      ..onClick = (_) {}
      ..skin = ListGroupItemSkin.DANGER
    )('Vestibulum at eros')
  );

Headers

Set props.header to render a ListGroupItem that has a distinct header and body content region.

Optionally, set props.headerSize to modify the size of the header itself.

Rendering OverReact Demo . . .
part of over_react.web.demos;

ReactElement listGroupHeaderDemo() =>
  ListGroup()(
    (ListGroupItem()
      ..header = 'List group item heading'
      ..onClick = (_) {}
      ..isActive = true
    )(
      'Donec id elit non mi porta gravida at eget metus. '
      'Maecenas sed diam eget risus varius blandit.'
    ),
    (ListGroupItem()
      ..header = 'List group item heading'
      ..headerSize = ListGroupItemHeaderElementSize.H4
      ..onClick = (_) {}
    )(
      'Donec id elit non mi porta gravida at eget metus. '
      'Maecenas sed diam eget risus varius blandit.'
    ),
    (ListGroupItem()
      ..header = 'List group item heading'
      ..headerSize = ListGroupItemHeaderElementSize.H3
      ..onClick = (_) {}
    )(
      'Donec id elit non mi porta gravida at eget metus. '
      'Maecenas sed diam eget risus varius blandit.'
    )
  );