mirror of
https://github.com/flarum/core.git
synced 2025-08-06 08:27:42 +02:00
start
This commit is contained in:
@@ -1,27 +1,61 @@
|
|||||||
|
import type Mithril from 'mithril';
|
||||||
|
|
||||||
import app from '../../common/app';
|
import app from '../../common/app';
|
||||||
import Component from '../Component';
|
import Component, { ComponentAttrs } from '../Component';
|
||||||
import icon from '../helpers/icon';
|
import icon from '../helpers/icon';
|
||||||
import listItems from '../helpers/listItems';
|
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
|
* The `Dropdown` component displays a button which, when clicked, shows a
|
||||||
* dropdown menu beneath it.
|
* 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.
|
* The children will be displayed as a list inside of the dropdown menu.
|
||||||
*/
|
*/
|
||||||
export default class Dropdown extends Component {
|
export default class Dropdown<CustomAttrs extends IDropdownAttrs = IDropdownAttrs> extends Component<CustomAttrs> {
|
||||||
static initAttrs(attrs) {
|
static initAttrs(attrs: IDropdownAttrs) {
|
||||||
attrs.className = attrs.className || '';
|
attrs.className = attrs.className || '';
|
||||||
attrs.buttonClassName = attrs.buttonClassName || '';
|
attrs.buttonClassName = attrs.buttonClassName || '';
|
||||||
attrs.menuClassName = attrs.menuClassName || '';
|
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');
|
attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.lib.dropdown.toggle_dropdown_accessible_label');
|
||||||
}
|
}
|
||||||
|
|
||||||
oninit(vnode) {
|
protected showing = false;
|
||||||
super.oninit(vnode);
|
|
||||||
|
|
||||||
this.showing = false;
|
view(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
|
||||||
}
|
|
||||||
|
|
||||||
view(vnode) {
|
|
||||||
const items = vnode.children ? listItems(vnode.children) : [];
|
const items = vnode.children ? listItems(vnode.children) : [];
|
||||||
const renderItems = this.attrs.lazyDraw ? this.showing : true;
|
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);
|
super.oncreate(vnode);
|
||||||
|
|
||||||
// When opening the dropdown menu, work out if the menu goes beyond the
|
// 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.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.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', () => {
|
this.$().on('hidden.bs.dropdown', () => {
|
||||||
@@ -102,11 +132,8 @@ export default class Dropdown extends Component {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the template for the button.
|
* Get the template for the button.
|
||||||
*
|
|
||||||
* @return {*}
|
|
||||||
* @protected
|
|
||||||
*/
|
*/
|
||||||
getButton(children) {
|
protected getButton(children: Mithril.Children): Mithril.Children {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
|
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
|
||||||
@@ -122,11 +149,8 @@ export default class Dropdown extends Component {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the template for the button's content.
|
* Get the template for the button's content.
|
||||||
*
|
|
||||||
* @return {*}
|
|
||||||
* @protected
|
|
||||||
*/
|
*/
|
||||||
getButtonContent(children) {
|
protected getButtonContent(children: Mithril.Children): Mithril.Children {
|
||||||
return [
|
return [
|
||||||
this.attrs.icon ? icon(this.attrs.icon, { className: 'Button-icon' }) : '',
|
this.attrs.icon ? icon(this.attrs.icon, { className: 'Button-icon' }) : '',
|
||||||
<span className="Button-label">{this.attrs.label}</span>,
|
<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>;
|
return <ul className={'Dropdown-menu dropdown-menu ' + this.attrs.menuClassName}>{items}</ul>;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
import Dropdown from './Dropdown';
|
import type Mithril from 'mithril';
|
||||||
|
import Dropdown, { IDropdownAttrs } from './Dropdown';
|
||||||
import icon from '../helpers/icon';
|
import icon from '../helpers/icon';
|
||||||
|
import { ModdedVnode } from '../helpers/listItems';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines via a vnode is currently "active".
|
* 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.
|
* This is a temporary patch, and as so, is not exported / placed in utils.
|
||||||
*/
|
*/
|
||||||
function isActive(vnode) {
|
function isActive(vnode: ModdedVnode<{}>) {
|
||||||
const tag = vnode.tag;
|
const tag = vnode.tag as VnodeElementTag;
|
||||||
|
|
||||||
// Allow non-selectable dividers/headers to be added.
|
// Allow non-selectable dividers/headers to be added.
|
||||||
if (typeof tag === 'string' && tag !== 'a' && tag !== 'button') return false;
|
if (typeof tag === 'string' && tag !== 'a' && tag !== 'button') return false;
|
||||||
@@ -19,21 +21,29 @@ function isActive(vnode) {
|
|||||||
tag.initAttrs(vnode.attrs);
|
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
|
* 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
|
* button's label is set as the label of the first child which has a truthy
|
||||||
* `active` prop.
|
* `active` prop.
|
||||||
*
|
|
||||||
* ### Attrs
|
|
||||||
*
|
|
||||||
* - `caretIcon`
|
|
||||||
* - `defaultLabel`
|
|
||||||
*/
|
*/
|
||||||
export default class SelectDropdown extends Dropdown {
|
export default class SelectDropdown<CustomAttrs extends ISelectDropdownAttrs = ISelectDropdownAttrs> extends Dropdown<CustomAttrs> {
|
||||||
static initAttrs(attrs) {
|
static initAttrs(attrs: ISelectDropdownAttrs) {
|
||||||
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-sort';
|
attrs.caretIcon = typeof attrs.caretIcon !== 'undefined' ? attrs.caretIcon : 'fas fa-sort';
|
||||||
|
|
||||||
super.initAttrs(attrs);
|
super.initAttrs(attrs);
|
||||||
@@ -41,12 +51,12 @@ export default class SelectDropdown extends Dropdown {
|
|||||||
attrs.className += ' Dropdown--select';
|
attrs.className += ' Dropdown--select';
|
||||||
}
|
}
|
||||||
|
|
||||||
getButtonContent(children) {
|
protected getButtonContent(children: Mithril.Children): Mithril.ChildArray {
|
||||||
const activeChild = children.find(isActive);
|
const activeChild = Array.isArray(children) ? children.find(isActive) : children;
|
||||||
let label = (activeChild && activeChild.children) || this.attrs.defaultLabel;
|
let label = (activeChild && typeof activeChild === 'object' && 'children' in activeChild && activeChild.children) || this.attrs.defaultLabel;
|
||||||
|
|
||||||
if (label instanceof Array) label = label[0];
|
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' })];
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,20 +1,25 @@
|
|||||||
import Dropdown from './Dropdown';
|
import type Mithril from 'mithril';
|
||||||
|
import Dropdown, { IDropdownAttrs } from './Dropdown';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import icon from '../helpers/icon';
|
import icon from '../helpers/icon';
|
||||||
|
|
||||||
|
export interface ISplitDropdownAttrs extends IDropdownAttrs {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `SplitDropdown` component is similar to `Dropdown`, but the first child
|
* The `SplitDropdown` component is similar to `Dropdown`, but the first child
|
||||||
* is displayed as its own button prior to the toggle button.
|
* is displayed as its own button prior to the toggle button.
|
||||||
*/
|
*/
|
||||||
export default class SplitDropdown extends Dropdown {
|
export default class SplitDropdown<CustomAttrs extends ISplitDropdownAttrs = ISplitDropdownAttrs> extends Dropdown<CustomAttrs> {
|
||||||
static initAttrs(attrs) {
|
static initAttrs(attrs: ISplitDropdownAttrs) {
|
||||||
super.initAttrs(attrs);
|
super.initAttrs(attrs);
|
||||||
|
|
||||||
attrs.className += ' Dropdown--split';
|
attrs.className += ' Dropdown--split';
|
||||||
attrs.menuClassName += ' Dropdown-menu--right';
|
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
|
// 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
|
// these attrs to a new button, so that it has exactly the same behaviour as
|
||||||
// the first child.
|
// 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
|
* Get the first child. If the first child is an array, the first item in that
|
||||||
* array will be returned.
|
* array will be returned.
|
||||||
*
|
|
||||||
* @return {*}
|
|
||||||
* @protected
|
|
||||||
*/
|
*/
|
||||||
getFirstChild(children) {
|
protected getFirstChild(children: Mithril.Children): Mithril.Vnode {
|
||||||
let firstChild = children;
|
let firstChild = children;
|
||||||
|
|
||||||
while (firstChild instanceof Array) firstChild = firstChild[0];
|
while (firstChild instanceof Array) firstChild = firstChild[0];
|
Reference in New Issue
Block a user