mirror of
https://github.com/discourse/discourse.git
synced 2026-08-05 02:43:08 -05:00
DEV: adds a new dropdown widget usable in any widget (#9297)
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
import hbs from "discourse/widgets/hbs-compiler";
|
||||
|
||||
/*
|
||||
|
||||
widget-dropdown
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
{{attach
|
||||
widget="widget-dropdown"
|
||||
attrs=(hash
|
||||
id=id
|
||||
label=label
|
||||
content=content
|
||||
onChange=onChange
|
||||
options=(hash)
|
||||
)
|
||||
}}
|
||||
|
||||
Mandatory attributes:
|
||||
|
||||
- id: must be unique in the application
|
||||
|
||||
- label or translatedLabel:
|
||||
- label: an i18n key to be translated and displayed on the header
|
||||
- translatedLabel: an already translated label to display on the header
|
||||
|
||||
- onChange: action called when a click happens on a row, content[rowIndex] will be passed as params
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- class: adds css class to the dropdown
|
||||
- content: list of items to display, if undefined or empty dropdown won't display
|
||||
Example content:
|
||||
|
||||
```
|
||||
[
|
||||
{ id: 1, label: "foo.bar" },
|
||||
"separator",
|
||||
{ id: 2, translatedLabel: "FooBar" },
|
||||
{ id: 3 label: "foo.baz", icon: "times" },
|
||||
{ id: 4, html: "<b>foo</b>" }
|
||||
]
|
||||
```
|
||||
|
||||
- options: accepts a hash of optional attributes
|
||||
- headerClass: adds css class to the dropdown header
|
||||
- bodyClass: adds css class to the dropdown header
|
||||
*/
|
||||
|
||||
export const WidgetDropdownHeaderClass = {
|
||||
tagName: "button",
|
||||
|
||||
transform(attrs) {
|
||||
return {
|
||||
label: attrs.translatedLabel ? attrs.translatedLabel : I18n.t(attrs.label)
|
||||
};
|
||||
},
|
||||
|
||||
buildClasses(attrs) {
|
||||
let classes = ["widget-dropdown-header", "btn", "btn-default"];
|
||||
if (attrs.class) {
|
||||
classes = classes.concat(attrs.class.split(" "));
|
||||
}
|
||||
return classes.filter(Boolean).join(" ");
|
||||
},
|
||||
|
||||
click(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.sendWidgetAction("_onTrigger");
|
||||
},
|
||||
|
||||
template: hbs`
|
||||
{{#if attrs.icon}}
|
||||
{{d-icon attrs.icon}}
|
||||
{{/if}}
|
||||
<span class="label">
|
||||
{{transformed.label}}
|
||||
</span>
|
||||
`
|
||||
};
|
||||
|
||||
createWidget("widget-dropdown-header", WidgetDropdownHeaderClass);
|
||||
|
||||
export const WidgetDropdownItemClass = {
|
||||
tagName: "div",
|
||||
|
||||
transform(attrs) {
|
||||
return {
|
||||
content:
|
||||
attrs.item === "separator"
|
||||
? "<hr>"
|
||||
: attrs.item.html
|
||||
? attrs.item.html
|
||||
: attrs.item.translatedLabel
|
||||
? attrs.item.translatedLabel
|
||||
: I18n.t(attrs.item.label)
|
||||
};
|
||||
},
|
||||
|
||||
buildAttributes(attrs) {
|
||||
return { "data-id": attrs.item.id };
|
||||
},
|
||||
|
||||
buildClasses(attrs) {
|
||||
return [
|
||||
"widget-dropdown-item",
|
||||
attrs.item === "separator" ? "separator" : `item-${attrs.item.id}`
|
||||
].join(" ");
|
||||
},
|
||||
|
||||
click(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.sendWidgetAction("_onChange", this.attrs.item);
|
||||
},
|
||||
|
||||
template: hbs`
|
||||
{{#if attrs.item.icon}}
|
||||
{{d-icon attrs.item.icon}}
|
||||
{{/if}}
|
||||
{{{transformed.content}}}
|
||||
`
|
||||
};
|
||||
|
||||
createWidget("widget-dropdown-item", WidgetDropdownItemClass);
|
||||
|
||||
export const WidgetDropdownClass = {
|
||||
tagName: "div",
|
||||
|
||||
init(attrs) {
|
||||
if (!attrs) {
|
||||
throw "A widget-dropdown expects attributes.";
|
||||
}
|
||||
|
||||
if (!attrs.id) {
|
||||
throw "A widget-dropdown expects a unique `id` attribute.";
|
||||
}
|
||||
|
||||
if (!attrs.label && !attrs.translatedLabel) {
|
||||
throw "A widget-dropdown expects at least a `label` or `translatedLabel`";
|
||||
}
|
||||
},
|
||||
|
||||
buildKey: attrs => {
|
||||
return attrs.id;
|
||||
},
|
||||
|
||||
buildAttributes(attrs) {
|
||||
return { id: attrs.id };
|
||||
},
|
||||
|
||||
defaultState() {
|
||||
return {
|
||||
opened: false
|
||||
};
|
||||
},
|
||||
|
||||
buildClasses(attrs) {
|
||||
const classes = ["widget-dropdown"];
|
||||
classes.push(this.state.opened ? "opened" : "closed");
|
||||
return classes.join(" ") + " " + (attrs.class || "");
|
||||
},
|
||||
|
||||
transform(attrs) {
|
||||
const options = attrs.options || {};
|
||||
|
||||
return {
|
||||
options,
|
||||
bodyClass: `widget-dropdown-body ${options.bodyClass || ""}`
|
||||
};
|
||||
},
|
||||
|
||||
clickOutside() {
|
||||
this.state.opened = false;
|
||||
this.scheduleRerender();
|
||||
},
|
||||
|
||||
_onChange(params) {
|
||||
this.state.opened = false;
|
||||
if (this.attrs.onChange) {
|
||||
if (typeof this.attrs.onChange === "string") {
|
||||
this.sendWidgetAction(this.attrs.onChange, params);
|
||||
} else {
|
||||
this.attrs.onChange(params);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onTrigger() {
|
||||
if (this.state.opened) {
|
||||
this.state.opened = false;
|
||||
this._closeDropdown(this.attrs.id);
|
||||
} else {
|
||||
this.state.opened = true;
|
||||
this._openDropdown(this.attrs.id);
|
||||
}
|
||||
|
||||
this._popper && this._popper.update();
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this._popper) {
|
||||
this._popper.destroy();
|
||||
this._popper = null;
|
||||
}
|
||||
},
|
||||
|
||||
template: hbs`
|
||||
{{#if attrs.content}}
|
||||
{{attach
|
||||
widget="widget-dropdown-header"
|
||||
attrs=(hash
|
||||
icon=attrs.icon
|
||||
label=attrs.label
|
||||
translatedLabel=attrs.translatedLabel
|
||||
class=this.transformed.options.headerClass
|
||||
)
|
||||
}}
|
||||
|
||||
<div class={{transformed.bodyClass}}>
|
||||
{{#each attrs.content as |item|}}
|
||||
{{attach
|
||||
widget="widget-dropdown-item"
|
||||
attrs=(hash item=item)
|
||||
}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
`,
|
||||
|
||||
_closeDropdown() {
|
||||
this._popper && this._popper.destroy();
|
||||
},
|
||||
|
||||
_openDropdown(id) {
|
||||
const dropdownHeader = document.querySelector(
|
||||
`#${id} .widget-dropdown-header`
|
||||
);
|
||||
const dropdownBody = document.querySelector(`#${id} .widget-dropdown-body`);
|
||||
|
||||
if (dropdownHeader && dropdownBody) {
|
||||
/* global Popper:true */
|
||||
this._popper = Popper.createPopper(dropdownHeader, dropdownBody, {
|
||||
strategy: "fixed",
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 5]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default createWidget("widget-dropdown", WidgetDropdownClass);
|
||||
Reference in New Issue
Block a user