mirror of
https://github.com/flarum/framework.git
synced 2024-11-25 09:41:49 +08:00
feat: remove color validation in appearance admin page & add color indicator (#3140)
* Remove color validation in basics admin page & add color indicator * Create ColorInput common component * Revert 'formGroupAttrs' addition * Rename component CSS classes * Fix input type in ColorInput from AdminPage#buildSettingComponent * Rename component to ColorPreviewInput, remove aliases in admin & export in compat * Remove leftovers from rebase on master * feat: add global type definition for a vnode element tag * fix(a11y): add aria roles to color input * chore: use new type * chore: format Co-authored-by: David Wheatley <hi@davwheat.dev>
This commit is contained in:
parent
c96fa49853
commit
94c4f266e3
2
js/src/@types/global.d.ts
vendored
2
js/src/@types/global.d.ts
vendored
|
@ -21,6 +21,8 @@ declare type KeysOfType<Type extends object, Match> = {
|
|||
*/
|
||||
declare type KeyOfType<Type extends object, Match> = KeysOfType<Type, Match>[keyof Type];
|
||||
|
||||
declare type VnodeElementTag<Attrs = Record<string, unknown>, State = Record<string, unknown>> = string | ComponentTypes<Attrs, State>;
|
||||
|
||||
/**
|
||||
* @deprecated Please import `app` from a namespace instead of using it as a global variable.
|
||||
*
|
||||
|
|
|
@ -10,6 +10,7 @@ import Stream from '../../common/utils/Stream';
|
|||
import saveSettings from '../utils/saveSettings';
|
||||
import AdminHeader from './AdminHeader';
|
||||
import generateElementId from '../utils/generateElementId';
|
||||
import ColorPreviewInput from '../../common/components/ColorPreviewInput';
|
||||
|
||||
export interface AdminHeaderOptions {
|
||||
title: string;
|
||||
|
@ -76,6 +77,7 @@ export interface HTMLInputSettingsComponentOptions extends CommonSettingsItemOpt
|
|||
const BooleanSettingTypes = ['bool', 'checkbox', 'switch', 'boolean'] as const;
|
||||
const SelectSettingTypes = ['select', 'dropdown', 'selectdropdown'] as const;
|
||||
const TextareaSettingTypes = ['textarea'] as const;
|
||||
const ColorPreviewSettingType = 'color-preview';
|
||||
|
||||
/**
|
||||
* Valid options for the setting component builder to generate a Switch.
|
||||
|
@ -103,6 +105,13 @@ export interface TextareaSettingComponentOptions extends CommonSettingsItemOptio
|
|||
type: typeof TextareaSettingTypes[number];
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid options for the setting component builder to generate a ColorPreviewInput.
|
||||
*/
|
||||
export interface ColorPreviewSettingComponentOptions extends CommonSettingsItemOptions {
|
||||
type: typeof ColorPreviewSettingType;
|
||||
}
|
||||
|
||||
/**
|
||||
* All valid options for the setting component builder.
|
||||
*/
|
||||
|
@ -110,7 +119,8 @@ export type SettingsComponentOptions =
|
|||
| HTMLInputSettingsComponentOptions
|
||||
| SwitchSettingComponentOptions
|
||||
| SelectSettingComponentOptions
|
||||
| TextareaSettingComponentOptions;
|
||||
| TextareaSettingComponentOptions
|
||||
| ColorPreviewSettingComponentOptions;
|
||||
|
||||
/**
|
||||
* Valid attrs that can be returned by the `headerInfo` function
|
||||
|
@ -258,7 +268,15 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
|
|||
if ((TextareaSettingTypes as readonly string[]).includes(type)) {
|
||||
settingElement = <textarea id={inputId} aria-describedby={helpTextId} bidi={this.setting(setting)} {...componentAttrs} />;
|
||||
} else {
|
||||
settingElement = <input id={inputId} aria-describedby={helpTextId} type={type} bidi={this.setting(setting)} {...componentAttrs} />;
|
||||
let Tag: VnodeElementTag = 'input';
|
||||
|
||||
if (type === ColorPreviewSettingType) {
|
||||
Tag = ColorPreviewInput;
|
||||
} else {
|
||||
componentAttrs.type = type;
|
||||
}
|
||||
|
||||
settingElement = <Tag id={inputId} aria-describedby={helpTextId} bidi={this.setting(setting)} {...componentAttrs} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,12 +25,12 @@ export default class AppearancePage extends AdminPage {
|
|||
|
||||
<div className="AppearancePage-colors-input">
|
||||
{this.buildSettingComponent({
|
||||
type: 'text',
|
||||
type: 'color-preview',
|
||||
setting: 'theme_primary_color',
|
||||
placeholder: '#aaaaaa',
|
||||
})}
|
||||
{this.buildSettingComponent({
|
||||
type: 'text',
|
||||
type: 'color-preview',
|
||||
setting: 'theme_secondary_color',
|
||||
placeholder: '#aaaaaa',
|
||||
})}
|
||||
|
@ -105,17 +105,4 @@ export default class AppearancePage extends AdminPage {
|
|||
onsaved() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
saveSettings(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const hex = /^#[0-9a-f]{3}([0-9a-f]{3})?$/i;
|
||||
|
||||
if (!hex.test(this.settings['theme_primary_color']()) || !hex.test(this.settings['theme_secondary_color']())) {
|
||||
alert(app.translator.trans('core.admin.appearance.enter_hex_message'));
|
||||
return;
|
||||
}
|
||||
|
||||
super.saveSettings(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ import Alert from './components/Alert';
|
|||
import Link from './components/Link';
|
||||
import LinkButton from './components/LinkButton';
|
||||
import Checkbox from './components/Checkbox';
|
||||
import ColorPreviewInput from './components/ColorPreviewInput';
|
||||
import SelectDropdown from './components/SelectDropdown';
|
||||
import ModalManager from './components/ModalManager';
|
||||
import Button from './components/Button';
|
||||
|
@ -144,6 +145,7 @@ export default {
|
|||
'components/Link': Link,
|
||||
'components/LinkButton': LinkButton,
|
||||
'components/Checkbox': Checkbox,
|
||||
'components/ColorPreviewInput': ColorPreviewInput,
|
||||
'components/SelectDropdown': SelectDropdown,
|
||||
'components/ModalManager': ModalManager,
|
||||
'components/Button': Button,
|
||||
|
|
28
js/src/common/components/ColorPreviewInput.tsx
Normal file
28
js/src/common/components/ColorPreviewInput.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import type Mithril from 'mithril';
|
||||
|
||||
import Component, { ComponentAttrs } from '../Component';
|
||||
import classList from '../utils/classList';
|
||||
import icon from '../helpers/icon';
|
||||
|
||||
export default class ColorPreviewInput extends Component {
|
||||
value?: string;
|
||||
|
||||
view(vnode: Mithril.Vnode<ComponentAttrs, this>) {
|
||||
const { className, ...attrs } = this.attrs;
|
||||
const value = attrs.bidi?.() || attrs.value;
|
||||
|
||||
attrs.type ||= 'text';
|
||||
|
||||
return (
|
||||
<div className="ColorInput">
|
||||
<input className={classList('FormControl', className)} {...attrs} />
|
||||
|
||||
<span className="ColorInput-icon" role="presentation">
|
||||
{icon('fas fa-exclamation-circle')}
|
||||
</span>
|
||||
|
||||
<div className="ColorInput-preview" style={{ '--input-value': value }} role="presentation" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -23,14 +23,6 @@
|
|||
margin-bottom: 24px !important;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
float: left;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 2%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AppearancePage-colors .Checkbox {
|
||||
|
|
22
less/common/ColorInput.less
Normal file
22
less/common/ColorInput.less
Normal file
|
@ -0,0 +1,22 @@
|
|||
.ColorInput {
|
||||
position: relative;
|
||||
|
||||
&-preview, &-icon {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&-preview {
|
||||
background-color: var(--input-value);
|
||||
border-radius: 15%;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
text-align: center;
|
||||
color: @validation-error-color;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
@import "Badge";
|
||||
@import "Button";
|
||||
@import "Checkbox";
|
||||
@import "ColorInput";
|
||||
@import "Dropdown";
|
||||
@import "EditUserModal";
|
||||
@import "Form";
|
||||
|
|
Loading…
Reference in New Issue
Block a user