mirror of
https://github.com/flarum/core.git
synced 2025-07-24 18:21:33 +02:00
Major CSS revamp
- Get rid of Bootstrap (except we still rely on some JS) - Use BEM class names - Rework variables/theme config - Fix various bugs, including some on mobile The CSS is still not ideal – it needs to be cleaned up some more. But that can be a focus for after beta.
This commit is contained in:
@@ -197,8 +197,8 @@ export default class App {
|
||||
return m.request(options).then(null, response => {
|
||||
if (response instanceof Error) {
|
||||
this.alerts.show(this.requestError = new Alert({
|
||||
type: 'warning',
|
||||
message: response.message
|
||||
type: 'error',
|
||||
children: response.message
|
||||
}));
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import extract from 'flarum/utils/extract';
|
||||
* The alert may have the following special props:
|
||||
*
|
||||
* - `type` The type of alert this is. Will be used to give the alert a class
|
||||
* name of `alert-{type}`.
|
||||
* name of `Alert--{type}`.
|
||||
* - `controls` An array of controls to show in the alert.
|
||||
* - `dismissible` Whether or not the alert can be dismissed.
|
||||
* - `ondismiss` A callback to run when the alert is dismissed.
|
||||
@@ -22,7 +22,7 @@ export default class Alert extends Component {
|
||||
const attrs = Object.assign({}, this.props);
|
||||
|
||||
const type = extract(attrs, 'type');
|
||||
attrs.className = 'alert alert-' + type + ' ' + (attrs.className || '');
|
||||
attrs.className = 'Alert Alert--' + type + ' ' + (attrs.className || '');
|
||||
|
||||
const children = extract(attrs, 'children');
|
||||
const controls = extract(attrs, 'controls') || [];
|
||||
@@ -37,17 +37,17 @@ export default class Alert extends Component {
|
||||
if (dismissible || dismissible === undefined) {
|
||||
dismissControl.push(Button.component({
|
||||
icon: 'times',
|
||||
className: 'btn btn-link btn-icon dismiss',
|
||||
className: 'Button Button--link Button--icon Alert-dismiss',
|
||||
onclick: ondismiss
|
||||
}));
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...attrs}>
|
||||
<span className="alert-body">
|
||||
<span className="Alert-body">
|
||||
{children}
|
||||
</span>
|
||||
<ul className="alert-controls">
|
||||
<ul className="Alert-controls">
|
||||
{listItems(controls.concat(dismissControl))}
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -2,10 +2,10 @@ import Component from 'flarum/Component';
|
||||
import Alert from 'flarum/components/Alert';
|
||||
|
||||
/**
|
||||
* The `Alerts` component provides an area in which `Alert` components can be
|
||||
* shown and dismissed.
|
||||
* The `AlertManager` component provides an area in which `Alert` components can
|
||||
* be shown and dismissed.
|
||||
*/
|
||||
export default class Alerts extends Component {
|
||||
export default class AlertManager extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
@@ -20,8 +20,8 @@ export default class Alerts extends Component {
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div className="alerts">
|
||||
{this.components.map(component => <div className="alerts-item">{component}</div>)}
|
||||
<div className="AlertManager">
|
||||
{this.components.map(component => <div className="AlertManager-alert">{component}</div>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export default class Alerts extends Component {
|
||||
*/
|
||||
show(component) {
|
||||
if (!(component instanceof Alert)) {
|
||||
throw new Error('The Alerts component can only show Alert components');
|
||||
throw new Error('The AlertManager component can only show Alert components');
|
||||
}
|
||||
|
||||
component.props.ondismiss = this.dismiss.bind(this, component);
|
@@ -9,7 +9,7 @@ import extract from 'flarum/utils/extract';
|
||||
* A badge may have the following special props:
|
||||
*
|
||||
* - `type` The type of badge this is. This will be used to give the badge a
|
||||
* class name of `badge-{type}`.
|
||||
* class name of `Badge--{type}`.
|
||||
* - `icon` The name of an icon to show inside the badge.
|
||||
*
|
||||
* All other props will be assigned as attributes on the badge element.
|
||||
@@ -20,7 +20,8 @@ export default class Badge extends Component {
|
||||
const type = extract(attrs, 'type');
|
||||
const iconName = extract(attrs, 'icon');
|
||||
|
||||
attrs.className = 'badge badge-' + type + ' ' + (attrs.className || '');
|
||||
attrs.className = 'Badge Badge--' + type + ' ' + (attrs.className || '');
|
||||
attrs.title = extract(attrs, 'label');
|
||||
|
||||
// Give the badge a unique key so that when badges are displayed together,
|
||||
// and then one is added/removed, Mithril will correctly redraw the series
|
||||
@@ -29,7 +30,7 @@ export default class Badge extends Component {
|
||||
|
||||
return (
|
||||
<span {...attrs}>
|
||||
{iconName ? icon(iconName, {className: 'icon'}) : ''}
|
||||
{iconName ? icon(iconName, {className: 'Badge-icon'}) : ''}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@@ -24,10 +24,9 @@ export default class Button extends Component {
|
||||
delete attrs.children;
|
||||
|
||||
attrs.className = (attrs.className || '');
|
||||
attrs.href = attrs.href || 'javascript:;';
|
||||
|
||||
const iconName = extract(attrs, 'icon');
|
||||
if (iconName) attrs.className += ' has-icon';
|
||||
if (iconName) attrs.className += ' hasIcon';
|
||||
|
||||
const disabled = extract(attrs, 'disabled');
|
||||
if (disabled) {
|
||||
@@ -35,7 +34,7 @@ export default class Button extends Component {
|
||||
delete attrs.onclick;
|
||||
}
|
||||
|
||||
return <a {...attrs}>{this.getButtonContent()}</a>;
|
||||
return <button {...attrs}>{this.getButtonContent()}</button>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,8 +47,8 @@ export default class Button extends Component {
|
||||
const iconName = this.props.icon;
|
||||
|
||||
return [
|
||||
iconName ? icon(iconName) : '',
|
||||
<span className="label">{this.props.children}</span>
|
||||
iconName ? icon(iconName, {className: 'Button-icon'}) : '',
|
||||
this.props.children ? <span className="Button-label">{this.props.children}</span> : ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ export default class Checkbox extends Component {
|
||||
}
|
||||
|
||||
view() {
|
||||
let className = 'checkbox ' + (this.props.state ? 'on' : 'off') + ' ' + (this.props.className || '');
|
||||
let className = 'Checkbox ' + (this.props.state ? 'on' : 'off') + ' ' + (this.props.className || '');
|
||||
if (this.loading) className += ' loading';
|
||||
if (this.props.disabled) className += ' disabled';
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class Checkbox extends Component {
|
||||
checked={this.props.state}
|
||||
disabled={this.props.disabled}
|
||||
onchange={m.withAttr('checked', this.onchange.bind(this))}/>
|
||||
<div className="checkbox-display">
|
||||
<div className="Checkbox-display">
|
||||
{this.getDisplay()}
|
||||
</div>
|
||||
{this.props.children}
|
||||
|
@@ -18,6 +18,8 @@ import listItems from 'flarum/helpers/listItems';
|
||||
*/
|
||||
export default class Dropdown extends Component {
|
||||
static initProps(props) {
|
||||
super.initProps(props);
|
||||
|
||||
props.className = props.className || '';
|
||||
props.buttonClassName = props.buttonClassName || '';
|
||||
props.contentClassName = props.contentClassName || '';
|
||||
@@ -26,11 +28,13 @@ export default class Dropdown extends Component {
|
||||
}
|
||||
|
||||
view() {
|
||||
const items = listItems(this.props.children);
|
||||
|
||||
return (
|
||||
<div className={'dropdown btn-group ' + this.props.className}>
|
||||
<div className={'ButtonGroup Dropdown dropdown ' + this.props.className + ' itemCount' + items.length}>
|
||||
{this.getButton()}
|
||||
<ul className={'dropdown-menu ' + this.props.menuClassName}>
|
||||
{listItems(this.props.children)}
|
||||
<ul className={'Dropdown-menu dropdown-menu ' + this.props.menuClassName}>
|
||||
{items}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
@@ -44,12 +48,12 @@ export default class Dropdown extends Component {
|
||||
*/
|
||||
getButton() {
|
||||
return (
|
||||
<a href="javascript:;"
|
||||
className={'dropdown-toggle ' + this.props.buttonClassName}
|
||||
<button
|
||||
className={'Dropdown-toggle ' + this.props.buttonClassName}
|
||||
data-toggle="dropdown"
|
||||
onclick={this.props.onclick}>
|
||||
{this.getButtonContent()}
|
||||
</a>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,9 +65,9 @@ export default class Dropdown extends Component {
|
||||
*/
|
||||
getButtonContent() {
|
||||
return [
|
||||
icon(this.props.icon),
|
||||
<span className="label">{this.props.label}</span>,
|
||||
icon('caret-down', {className: 'caret'})
|
||||
icon(this.props.icon, {className: 'Button-icon'}),
|
||||
<span className="Button-label">{this.props.label}</span>, ' ',
|
||||
icon('caret-down', {className: 'Button-caret'})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,14 @@ export default class LinkButton extends Button {
|
||||
props.config = props.config || m.route;
|
||||
}
|
||||
|
||||
view() {
|
||||
const vdom = super.view();
|
||||
|
||||
vdom.tag = 'a';
|
||||
|
||||
return vdom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a component with the given props is 'active'.
|
||||
*
|
||||
|
@@ -12,7 +12,7 @@ export default class LoadingIndicator extends Component {
|
||||
view() {
|
||||
const attrs = Object.assign({}, this.props);
|
||||
|
||||
attrs.className = 'loading-indicator ' + (attrs.className || '');
|
||||
attrs.className = 'LoadingIndicator ' + (attrs.className || '');
|
||||
delete attrs.size;
|
||||
|
||||
return <div {...attrs}>{m.trust(' ')}</div>;
|
||||
|
139
framework/core/js/lib/components/Modal.js
Normal file
139
framework/core/js/lib/components/Modal.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import Component from 'flarum/Component';
|
||||
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||
import Alert from 'flarum/components/Alert';
|
||||
import Button from 'flarum/components/Button';
|
||||
import icon from 'flarum/helpers/icon';
|
||||
|
||||
/**
|
||||
* The `Modal` component displays a modal dialog, wrapped in a form. Subclasses
|
||||
* should implement the `className`, `title`, and `content` methods.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class Modal extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
/**
|
||||
* An alert component to show below the header.
|
||||
*
|
||||
* @type {Alert}
|
||||
*/
|
||||
this.alert = null;
|
||||
|
||||
/**
|
||||
* Whether or not the form is processing.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
view() {
|
||||
if (this.alert) {
|
||||
this.alert.props.dismissible = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'Modal modal-dialog ' + this.className()}>
|
||||
<div className="Modal-content">
|
||||
<div className="Modal-close Page-backControl">
|
||||
{Button.component({
|
||||
icon: 'times',
|
||||
onclick: this.hide.bind(this),
|
||||
className: 'Button Button--icon Button--link'
|
||||
})}
|
||||
</div>
|
||||
|
||||
<form onsubmit={this.onsubmit.bind(this)}>
|
||||
<div className="Modal-header">
|
||||
<h3 className="Page-titleControl Page-titleControl--text">{this.title()}</h3>
|
||||
</div>
|
||||
|
||||
{alert ? <div className="Modal-alert">{this.alert}</div> : ''}
|
||||
|
||||
{this.content()}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{LoadingIndicator.component({
|
||||
className: 'Modal-loading ' + (this.loading ? 'active' : '')
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name to apply to the modal.
|
||||
*
|
||||
* @return {String}
|
||||
* @abstract
|
||||
*/
|
||||
className() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title of the modal dialog.
|
||||
*
|
||||
* @return {String}
|
||||
* @abstract
|
||||
*/
|
||||
title() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the modal.
|
||||
*
|
||||
* @return {VirtualElement}
|
||||
* @abstract
|
||||
*/
|
||||
content() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the modal form's submit event.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
onsubmit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus on the first input when the modal is ready to be used.
|
||||
*/
|
||||
onready() {
|
||||
this.$('form :input:first').select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the modal.
|
||||
*/
|
||||
hide() {
|
||||
app.modal.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an alert describing errors returned from the API, and give focus to
|
||||
* the first relevant field.
|
||||
*
|
||||
* @param {Object} response
|
||||
*/
|
||||
handleErrors(response) {
|
||||
const errors = response && response.errors;
|
||||
|
||||
if (errors) {
|
||||
this.alert(new Alert({
|
||||
type: 'warning',
|
||||
message: errors.map((error, k) => [error.detail, k < errors.length - 1 ? m('br') : ''])
|
||||
}));
|
||||
}
|
||||
|
||||
m.redraw();
|
||||
|
||||
if (errors) {
|
||||
this.$('form [name=' + errors[0].path + ']').select();
|
||||
} else {
|
||||
this.$('form :input:first').select();
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ import Modal from 'flarum/components/Modal';
|
||||
export default class ModalManager extends Component {
|
||||
view() {
|
||||
return (
|
||||
<div className="modal">
|
||||
<div className="ModalManager modal fade">
|
||||
{this.component && this.component.render()}
|
||||
</div>
|
||||
);
|
||||
|
@@ -21,14 +21,12 @@ export default class Navigation extends Component {
|
||||
const {history, pane} = app;
|
||||
|
||||
return (
|
||||
<div className={'navigation ' + (this.props.className || '')}
|
||||
<div className={'Navigation ButtonGroup ' + (this.props.className || '')}
|
||||
onmouseenter={pane && pane.show.bind(pane)}
|
||||
onmouseleave={pane && pane.onmouseleave.bind(pane)}>
|
||||
<div className="btn-group">
|
||||
{history.canGoBack()
|
||||
? [this.getBackButton(), this.getPaneButton()]
|
||||
: this.getDrawerButton()}
|
||||
</div>
|
||||
{history.canGoBack()
|
||||
? [this.getBackButton(), this.getPaneButton()]
|
||||
: this.getDrawerButton()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -50,7 +48,7 @@ export default class Navigation extends Component {
|
||||
const {history} = app;
|
||||
|
||||
return Button.component({
|
||||
className: 'btn btn-default btn-icon navigation-back',
|
||||
className: 'Button Button--icon Navigation-back',
|
||||
onclick: history.back.bind(history),
|
||||
icon: 'chevron-left'
|
||||
});
|
||||
@@ -68,7 +66,7 @@ export default class Navigation extends Component {
|
||||
if (!pane || !pane.active) return '';
|
||||
|
||||
return Button.component({
|
||||
className: 'btn btn-default btn-icon navigation-pin' + (pane.pinned ? ' active' : ''),
|
||||
className: 'Button Button--icon Navigation-pin' + (pane.pinned ? ' active' : ''),
|
||||
onclick: pane.togglePinned.bind(pane),
|
||||
icon: 'thumb-tack'
|
||||
});
|
||||
@@ -87,9 +85,12 @@ export default class Navigation extends Component {
|
||||
const user = app.session.user;
|
||||
|
||||
return Button.component({
|
||||
className: 'btn btn-default btn-icon navigation-drawer' +
|
||||
className: 'Button Button--icon Navigation-drawer' +
|
||||
(user && user.unreadNotificationsCount() ? ' unread' : ''),
|
||||
onclick: drawer.toggle.bind(drawer),
|
||||
onclick: e => {
|
||||
e.stopPropagation();
|
||||
drawer.show();
|
||||
},
|
||||
icon: 'reorder'
|
||||
});
|
||||
}
|
||||
|
@@ -14,11 +14,11 @@ export default class Select extends Component {
|
||||
const {options, onchange, value} = this.props;
|
||||
|
||||
return (
|
||||
<span className="select">
|
||||
<select className="form-control" onchange={m.withAttr('value', onchange.bind(this))} value={value}>
|
||||
<span className="Select">
|
||||
<select className="Select-input FormControl" onchange={m.withAttr('value', onchange.bind(this))} value={value}>
|
||||
{Object.keys(options).map(key => <option value={key}>{options[key]}</option>)}
|
||||
</select>
|
||||
{icon('sort', {className: 'caret'})}
|
||||
{icon('sort', {className: 'Select-caret'})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@@ -10,16 +10,18 @@ export default class SelectDropdown extends Dropdown {
|
||||
static initProps(props) {
|
||||
super.initProps(props);
|
||||
|
||||
props.className += ' select-dropdown';
|
||||
props.className += ' Dropdown--select';
|
||||
}
|
||||
|
||||
getButtonContent() {
|
||||
const activeChild = this.props.children.filter(child => child.props.active)[0];
|
||||
const label = activeChild && activeChild.props.label;
|
||||
let label = activeChild && activeChild.props.children;
|
||||
|
||||
if (label instanceof Array) label = label[0];
|
||||
|
||||
return [
|
||||
<span className="label">{label}</span>,
|
||||
icon('sort', {className: 'caret'})
|
||||
<span className="Button-label">{label}</span>, ' ',
|
||||
icon('sort', {className: 'Button-caret'})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import Component from 'flarum/Component';
|
||||
*/
|
||||
class Separator extends Component {
|
||||
view() {
|
||||
return <li className="divider"/>;
|
||||
return <li className="Dropdown-separator"/>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,8 +10,8 @@ export default class SplitDropdown extends Dropdown {
|
||||
static initProps(props) {
|
||||
super.initProps(props);
|
||||
|
||||
props.className += ' split-dropdown';
|
||||
props.menuClassName += ' dropdown-menu-right';
|
||||
props.className += ' Dropdown--split';
|
||||
props.menuClassName += ' Dropdown-menu--right';
|
||||
}
|
||||
|
||||
getButton() {
|
||||
@@ -20,16 +20,16 @@ export default class SplitDropdown extends Dropdown {
|
||||
// the first child.
|
||||
const firstChild = this.getFirstChild();
|
||||
const buttonProps = Object.assign({}, firstChild.props);
|
||||
buttonProps.className = (buttonProps.className || '') + ' ' + this.props.buttonClassName;
|
||||
buttonProps.className = (buttonProps.className || '') + ' SplitDropdown-button Button ' + this.props.buttonClassName;
|
||||
|
||||
return [
|
||||
Button.component(buttonProps),
|
||||
<a href="javascript:;"
|
||||
className={'dropdown-toggle btn-icon ' + this.props.buttonClassName}
|
||||
<button
|
||||
className={'Dropdown-toggle Button Button--icon ' + this.props.buttonClassName}
|
||||
data-toggle="dropdown">
|
||||
{icon(this.props.icon)}
|
||||
{icon('caret-down', {className: 'caret'})}
|
||||
</a>
|
||||
{icon(this.props.icon, {className: 'Button-icon'})}
|
||||
{icon('caret-down', {className: 'Button-caret'})}
|
||||
</button>
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -8,10 +8,10 @@ export default class Switch extends Checkbox {
|
||||
static initProps(props) {
|
||||
super.initProps(props);
|
||||
|
||||
props.className += ' switch';
|
||||
props.className = (props.className || '') + ' Checkbox--switch';
|
||||
}
|
||||
|
||||
getDisplay() {
|
||||
return '';
|
||||
return this.loading ? super.getDisplay() : '';
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function avatar(user, attrs = {}) {
|
||||
attrs.className = 'avatar ' + (attrs.className || '');
|
||||
attrs.className = 'Avatar ' + (attrs.className || '');
|
||||
let content = '';
|
||||
|
||||
// If the `title` attribute is set to null or false, we don't want to give the
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import Separator from 'flarum/components/Separator';
|
||||
import classList from 'flarum/utils/classList';
|
||||
|
||||
function isSeparator(item) {
|
||||
return item && item.component === Separator;
|
||||
@@ -28,10 +29,20 @@ function withoutUnnecessarySeparators(items) {
|
||||
export default function listItems(items) {
|
||||
return withoutUnnecessarySeparators(items).map(item => {
|
||||
const isListItem = item.component && item.component.isListItem;
|
||||
const active = item.component && item.component.isActive && item.component.isActive(item.props);
|
||||
const className = item.props ? item.props.itemClassName : item.itemClassName;
|
||||
|
||||
return isListItem
|
||||
? item
|
||||
: <li className={(item.itemName ? 'item-' + item.itemName : '') + ' ' + (className || '')}>{item}</li>;
|
||||
return [
|
||||
isListItem
|
||||
? item
|
||||
: <li className={classList([
|
||||
(item.itemName ? 'item-' + item.itemName : ''),
|
||||
className,
|
||||
(active ? 'active' : '')
|
||||
])}>
|
||||
{item}
|
||||
</li>,
|
||||
' '
|
||||
];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@@ -10,10 +10,16 @@
|
||||
* @return {String}
|
||||
*/
|
||||
export default function classList(classes) {
|
||||
const classNames = [];
|
||||
let classNames;
|
||||
|
||||
for (const i in classes) {
|
||||
if (classes[i]) classNames.push(i);
|
||||
if (classes instanceof Array) {
|
||||
classNames = classes.filter(name => name);
|
||||
} else {
|
||||
classNames = [];
|
||||
|
||||
for (const i in classes) {
|
||||
if (classes[i]) classNames.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return classNames.join(' ');
|
||||
|
Reference in New Issue
Block a user