1
0
mirror of https://github.com/flarum/core.git synced 2025-08-06 08:27:42 +02:00
This commit is contained in:
Alexander Skvortsov
2021-11-25 17:42:37 -05:00
parent d2a755c515
commit fbd7ff6683
3 changed files with 92 additions and 56 deletions

View File

@@ -1,27 +1,61 @@
import type Mithril from 'mithril';
import app from '../../common/app';
import Component from '../Component';
import Component, { ComponentAttrs } from '../Component';
import icon from '../helpers/icon';
import listItems from '../helpers/listItems';
export interface IDropdownAttrs extends ComponentAttrs{
/**
* A class name to apply to the dropdown toggle button.
*/
buttonClassName?: string;
/**
* A class name to apply to the dropdown menu.
*/
menuClassName?: string;
/**
* The name of an icon to show in the dropdown toggle button.
*/
icon?: string;
/**
* The name of an icon to show on the right of the button.
*/
caretIcon?: string;
/**
* The label of the dropdown toggle button. Defaults to 'Controls'.
*/
label?: Mithril.Children;
/**
* The label used to describe the dropdown toggle button to assistive readers.
* Defaults to 'Toggle dropdown menu'.
*/
accessibleToggleLabel?: string;
/**
* A callback to run when the dropdown is shown.
*/
onshow?: () => void;
/**
* A callback to run when the dropdown is hidden.
*/
onhide?: () => void;
}
/**
* The `Dropdown` component displays a button which, when clicked, shows a
* dropdown menu beneath it.
*
* ### Attrs
*
* - `buttonClassName` A class name to apply to the dropdown toggle button.
* - `menuClassName` A class name to apply to the dropdown menu.
* - `icon` The name of an icon to show in the dropdown toggle button.
* - `caretIcon` The name of an icon to show on the right of the button.
* - `label` The label of the dropdown toggle button. Defaults to 'Controls'.
* - `accessibleToggleLabel` The label used to describe the dropdown toggle button to assistive readers. Defaults to 'Toggle dropdown menu'.
* - `onhide`
* - `onshow`
*
* The children will be displayed as a list inside of the dropdown menu.
*/
export default class Dropdown extends Component {
static initAttrs(attrs) {
export default class Dropdown<CustomAttrs extends IDropdownAttrs = IDropdownAttrs> extends Component<CustomAttrs> {
static initAttrs(attrs: IDropdownAttrs) {
attrs.className = attrs.className || '';
attrs.buttonClassName = attrs.buttonClassName || '';
attrs.menuClassName = attrs.menuClassName || '';
@@ -30,13 +64,9 @@ export default class Dropdown extends Component {
attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.lib.dropdown.toggle_dropdown_accessible_label');
}
oninit(vnode) {
super.oninit(vnode);
protected showing = false;
this.showing = false;
}
view(vnode) {
view(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
const items = vnode.children ? listItems(vnode.children) : [];
const renderItems = this.attrs.lazyDraw ? this.showing : true;
@@ -48,7 +78,7 @@ export default class Dropdown extends Component {
);
}
oncreate(vnode) {
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
super.oncreate(vnode);
// When opening the dropdown menu, work out if the menu goes beyond the
@@ -80,13 +110,13 @@ export default class Dropdown extends Component {
$menu.removeClass('Dropdown-menu--top Dropdown-menu--right');
$menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height());
$menu.toggleClass('Dropdown-menu--top', $menu.offset()!.top + $menu.height()! > $(window).scrollTop()! + $(window).height()!);
if ($menu.offset().top < 0) {
if ($menu.offset()!.top < 0) {
$menu.removeClass('Dropdown-menu--top');
}
$menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width());
$menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset()!.left + $menu.width()! > $(window).scrollLeft()! + $(window).width()!);
});
this.$().on('hidden.bs.dropdown', () => {
@@ -102,11 +132,8 @@ export default class Dropdown extends Component {
/**
* Get the template for the button.
*
* @return {*}
* @protected
*/
getButton(children) {
protected getButton(children: Mithril.Children): Mithril.Children {
return (
<button
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
@@ -122,11 +149,8 @@ export default class Dropdown extends Component {
/**
* Get the template for the button's content.
*
* @return {*}
* @protected
*/
getButtonContent(children) {
protected getButtonContent(children: Mithril.Children): Mithril.Children {
return [
this.attrs.icon ? icon(this.attrs.icon, { className: 'Button-icon' }) : '',
<span className="Button-label">{this.attrs.label}</span>,
@@ -134,7 +158,7 @@ export default class Dropdown extends Component {
];
}
getMenu(items) {
protected getMenu(items: Mithril.Children): Mithril.Children {
return <ul className={'Dropdown-menu dropdown-menu ' + this.attrs.menuClassName}>{items}</ul>;
}
}

View File

@@ -1,5 +1,7 @@
import Dropdown from './Dropdown';
import type Mithril from 'mithril';
import Dropdown, { IDropdownAttrs } from './Dropdown';
import icon from '../helpers/icon';
import { ModdedVnode } from '../helpers/listItems';
/**
* Determines via a vnode is currently "active".
@@ -9,8 +11,8 @@ import icon from '../helpers/icon';
*
* This is a temporary patch, and as so, is not exported / placed in utils.
*/
function isActive(vnode) {
const tag = vnode.tag;
function isActive(vnode: ModdedVnode<{}>) {
const tag = vnode.tag as VnodeElementTag;
// Allow non-selectable dividers/headers to be added.
if (typeof tag === 'string' && tag !== 'a' && tag !== 'button') return false;
@@ -19,21 +21,29 @@ function isActive(vnode) {
tag.initAttrs(vnode.attrs);
}
return 'isActive' in tag ? tag.isActive(vnode.attrs) : vnode.attrs.active;
return 'isActive' in tag ? tag.isActive(vnode.attrs) : (vnode.attrs as any).active;
}
export interface ISelectDropdownAttrs extends IDropdownAttrs {
/**
* An icon for the select dropdown's caret.
*/
caretIcon?: string;
/**
* The default label if no child is active.
*/
defaultLabel?: Mithril.Children;
}
/**
* The `SelectDropdown` component is the same as a `Dropdown`, except the toggle
* button's label is set as the label of the first child which has a truthy
* `active` prop.
*
* ### Attrs
*
* - `caretIcon`
* - `defaultLabel`
*/
export default class SelectDropdown extends Dropdown {
static initAttrs(attrs) {
export default class SelectDropdown<CustomAttrs extends ISelectDropdownAttrs = ISelectDropdownAttrs> extends Dropdown<CustomAttrs> {
static initAttrs(attrs: ISelectDropdownAttrs) {
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-sort';
super.initAttrs(attrs);
@@ -41,12 +51,12 @@ export default class SelectDropdown extends Dropdown {
attrs.className += ' Dropdown--select';
}
getButtonContent(children) {
const activeChild = children.find(isActive);
let label = (activeChild && activeChild.children) || this.attrs.defaultLabel;
protected getButtonContent(children: Mithril.Children): Mithril.ChildArray {
const activeChild = Array.isArray(children) ? children.find(isActive) : children;
let label = (activeChild && typeof activeChild === 'object' && 'children' in activeChild && activeChild.children) || this.attrs.defaultLabel;
if (label instanceof Array) label = label[0];
return [<span className="Button-label">{label}</span>, icon(this.attrs.caretIcon, { className: 'Button-caret' })];
return [<span className="Button-label">{label}</span>, icon(this.attrs.caretIcon!, { className: 'Button-caret' })];
}
}

View File

@@ -1,20 +1,25 @@
import Dropdown from './Dropdown';
import type Mithril from 'mithril';
import Dropdown, { IDropdownAttrs } from './Dropdown';
import Button from './Button';
import icon from '../helpers/icon';
export interface ISplitDropdownAttrs extends IDropdownAttrs {
}
/**
* The `SplitDropdown` component is similar to `Dropdown`, but the first child
* is displayed as its own button prior to the toggle button.
*/
export default class SplitDropdown extends Dropdown {
static initAttrs(attrs) {
export default class SplitDropdown<CustomAttrs extends ISplitDropdownAttrs = ISplitDropdownAttrs> extends Dropdown<CustomAttrs> {
static initAttrs(attrs: ISplitDropdownAttrs) {
super.initAttrs(attrs);
attrs.className += ' Dropdown--split';
attrs.menuClassName += ' Dropdown-menu--right';
}
getButton(children) {
getButton(children: Mithril.ChildArray): Mithril.Children {
// Make a copy of the attrs of the first child component. We will assign
// these attrs to a new button, so that it has exactly the same behaviour as
// the first child.
@@ -39,11 +44,8 @@ export default class SplitDropdown extends Dropdown {
/**
* Get the first child. If the first child is an array, the first item in that
* array will be returned.
*
* @return {*}
* @protected
*/
getFirstChild(children) {
protected getFirstChild(children: Mithril.Children): Mithril.Vnode {
let firstChild = children;
while (firstChild instanceof Array) firstChild = firstChild[0];