mirror of
https://github.com/flarum/core.git
synced 2025-08-04 15:37:51 +02:00
chore: extract buildSettingComponent
method into a FormGroup
component (#3927)
* chore: extract `buildSettingComponent` method into a `FormGroup` component * chore: typings * feat: move to common
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import app from 'flarum/admin/app';
|
import app from 'flarum/admin/app';
|
||||||
import ItemList from 'flarum/common/utils/ItemList';
|
import ItemList from 'flarum/common/utils/ItemList';
|
||||||
import generateElementId from 'flarum/admin/utils/generateElementId';
|
import generateElementId from 'flarum/common/utils/generateElementId';
|
||||||
import FormModal, { IFormModalAttrs } from 'flarum/common/components/FormModal';
|
import FormModal, { IFormModalAttrs } from 'flarum/common/components/FormModal';
|
||||||
|
|
||||||
import Mithril from 'mithril';
|
import Mithril from 'mithril';
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import { extend } from 'flarum/common/extend';
|
import { extend } from 'flarum/common/extend';
|
||||||
import AdminPage from 'flarum/admin/components/AdminPage';
|
|
||||||
import SelectTagsSettingComponent from './components/SelectTagsSettingComponent';
|
import SelectTagsSettingComponent from './components/SelectTagsSettingComponent';
|
||||||
|
import FormGroup from 'flarum/common/components/FormGroup';
|
||||||
|
import type { IFormGroupAttrs } from 'flarum/common/components/FormGroup';
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
extend(AdminPage.prototype, 'customSettingComponents', function (items) {
|
extend(FormGroup.prototype, 'customFieldComponents', function (items) {
|
||||||
items.add('flarum-tags.select-tags', (attrs) => {
|
items.add('flarum-tags.select-tags', (attrs: IFormGroupAttrs) => {
|
||||||
return <SelectTagsSettingComponent {...attrs} settingValue={this.settings[attrs.setting]} />;
|
return <SelectTagsSettingComponent {...attrs} settingValue={attrs.bidi} />;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import './utils/saveSettings';
|
|||||||
import './utils/ExtensionData';
|
import './utils/ExtensionData';
|
||||||
import './utils/isExtensionEnabled';
|
import './utils/isExtensionEnabled';
|
||||||
import './utils/getCategorizedExtensions';
|
import './utils/getCategorizedExtensions';
|
||||||
import './utils/generateElementId';
|
|
||||||
|
|
||||||
import './components/SettingDropdown';
|
import './components/SettingDropdown';
|
||||||
import './components/EditCustomFooterModal';
|
import './components/EditCustomFooterModal';
|
||||||
@@ -22,7 +21,6 @@ import './components/ExtensionLinkButton';
|
|||||||
import './components/PermissionGrid';
|
import './components/PermissionGrid';
|
||||||
import './components/ExtensionPermissionGrid';
|
import './components/ExtensionPermissionGrid';
|
||||||
import './components/MailPage';
|
import './components/MailPage';
|
||||||
import './components/UploadImageButton';
|
|
||||||
import './components/LoadingModal';
|
import './components/LoadingModal';
|
||||||
import './components/DashboardPage';
|
import './components/DashboardPage';
|
||||||
import './components/BasicsPage';
|
import './components/BasicsPage';
|
||||||
|
@@ -3,17 +3,11 @@ import type Mithril from 'mithril';
|
|||||||
import app from '../app';
|
import app from '../app';
|
||||||
import Page, { IPageAttrs } from '../../common/components/Page';
|
import Page, { IPageAttrs } from '../../common/components/Page';
|
||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
import Switch from '../../common/components/Switch';
|
|
||||||
import Select from '../../common/components/Select';
|
|
||||||
import classList from '../../common/utils/classList';
|
import classList from '../../common/utils/classList';
|
||||||
import Stream from '../../common/utils/Stream';
|
import Stream from '../../common/utils/Stream';
|
||||||
import saveSettings from '../utils/saveSettings';
|
import saveSettings from '../utils/saveSettings';
|
||||||
import AdminHeader from './AdminHeader';
|
import AdminHeader from './AdminHeader';
|
||||||
import generateElementId from '../utils/generateElementId';
|
import FormGroup, { FieldComponentOptions } from '../../common/components/FormGroup';
|
||||||
import ColorPreviewInput from '../../common/components/ColorPreviewInput';
|
|
||||||
import ItemList from '../../common/utils/ItemList';
|
|
||||||
import type { IUploadImageButtonAttrs } from './UploadImageButton';
|
|
||||||
import UploadImageButton from './UploadImageButton';
|
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from '../../common/utils/extractText';
|
||||||
|
|
||||||
export interface AdminHeaderOptions {
|
export interface AdminHeaderOptions {
|
||||||
@@ -28,115 +22,9 @@ export interface AdminHeaderOptions {
|
|||||||
className: string;
|
className: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export type SettingsComponentOptions = FieldComponentOptions & {
|
||||||
* A type that matches any valid value for the `type` attribute on an HTML `<input>` element.
|
|
||||||
*
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type
|
|
||||||
*
|
|
||||||
* Note: this will be exported from a different location in the future.
|
|
||||||
*
|
|
||||||
* @see https://github.com/flarum/core/issues/3039
|
|
||||||
*/
|
|
||||||
export type HTMLInputTypes =
|
|
||||||
| 'button'
|
|
||||||
| 'checkbox'
|
|
||||||
| 'color'
|
|
||||||
| 'date'
|
|
||||||
| 'datetime-local'
|
|
||||||
| 'email'
|
|
||||||
| 'file'
|
|
||||||
| 'hidden'
|
|
||||||
| 'image'
|
|
||||||
| 'month'
|
|
||||||
| 'number'
|
|
||||||
| 'password'
|
|
||||||
| 'radio'
|
|
||||||
| 'range'
|
|
||||||
| 'reset'
|
|
||||||
| 'search'
|
|
||||||
| 'submit'
|
|
||||||
| 'tel'
|
|
||||||
| 'text'
|
|
||||||
| 'time'
|
|
||||||
| 'url'
|
|
||||||
| 'week';
|
|
||||||
|
|
||||||
export interface CommonSettingsItemOptions extends Mithril.Attributes {
|
|
||||||
setting: string;
|
setting: string;
|
||||||
label?: Mithril.Children;
|
};
|
||||||
help?: Mithril.Children;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valid options for the setting component builder to generate an HTML input element.
|
|
||||||
*/
|
|
||||||
export interface HTMLInputSettingsComponentOptions extends CommonSettingsItemOptions {
|
|
||||||
/**
|
|
||||||
* Any valid HTML input `type` value.
|
|
||||||
*/
|
|
||||||
type: HTMLInputTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BooleanSettingTypes = ['bool', 'checkbox', 'switch', 'boolean'] as const;
|
|
||||||
const SelectSettingTypes = ['select', 'dropdown', 'selectdropdown'] as const;
|
|
||||||
const TextareaSettingTypes = ['textarea'] as const;
|
|
||||||
const ColorPreviewSettingType = 'color-preview' as const;
|
|
||||||
const ImageUploadSettingType = 'image-upload' as const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valid options for the setting component builder to generate a Switch.
|
|
||||||
*/
|
|
||||||
export interface SwitchSettingComponentOptions extends CommonSettingsItemOptions {
|
|
||||||
type: typeof BooleanSettingTypes[number];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valid options for the setting component builder to generate a Select dropdown.
|
|
||||||
*/
|
|
||||||
export interface SelectSettingComponentOptions extends CommonSettingsItemOptions {
|
|
||||||
type: typeof SelectSettingTypes[number];
|
|
||||||
/**
|
|
||||||
* Map of values to their labels
|
|
||||||
*/
|
|
||||||
options: { [value: string]: Mithril.Children };
|
|
||||||
default: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valid options for the setting component builder to generate a Textarea.
|
|
||||||
*/
|
|
||||||
export interface TextareaSettingComponentOptions extends CommonSettingsItemOptions {
|
|
||||||
type: typeof TextareaSettingTypes[number];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Valid options for the setting component builder to generate a ColorPreviewInput.
|
|
||||||
*/
|
|
||||||
export interface ColorPreviewSettingComponentOptions extends CommonSettingsItemOptions {
|
|
||||||
type: typeof ColorPreviewSettingType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageUploadSettingComponentOptions extends CommonSettingsItemOptions, IUploadImageButtonAttrs {
|
|
||||||
type: typeof ImageUploadSettingType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomSettingComponentOptions extends CommonSettingsItemOptions {
|
|
||||||
type: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All valid options for the setting component builder.
|
|
||||||
*/
|
|
||||||
export type SettingsComponentOptions =
|
|
||||||
| HTMLInputSettingsComponentOptions
|
|
||||||
| SwitchSettingComponentOptions
|
|
||||||
| SelectSettingComponentOptions
|
|
||||||
| TextareaSettingComponentOptions
|
|
||||||
| ColorPreviewSettingComponentOptions
|
|
||||||
| ImageUploadSettingComponentOptions
|
|
||||||
| CustomSettingComponentOptions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Valid attrs that can be returned by the `headerInfo` function
|
* Valid attrs that can be returned by the `headerInfo` function
|
||||||
@@ -206,41 +94,6 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of extension-defined custom setting components to be available through
|
|
||||||
* {@link AdminPage.buildSettingComponent}.
|
|
||||||
*
|
|
||||||
* The ItemList key represents the value for `type` to be provided when calling
|
|
||||||
* {@link AdminPage.buildSettingComponent}. Other attributes passed are provided
|
|
||||||
* as arguments to the function added to the ItemList.
|
|
||||||
*
|
|
||||||
* ItemList priority has no effect here.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* extend(AdminPage.prototype, 'customSettingComponents', function (items) {
|
|
||||||
* // You can access the AdminPage instance with `this` to access its `settings` property.
|
|
||||||
*
|
|
||||||
* // Prefixing the key with your extension ID is recommended to avoid collisions.
|
|
||||||
* items.add('my-ext.setting-component', (attrs) => {
|
|
||||||
* return (
|
|
||||||
* <div className={attrs.className}>
|
|
||||||
* <label>{attrs.label}</label>
|
|
||||||
* {attrs.help && <p className="helpText">{attrs.help}</p>}
|
|
||||||
*
|
|
||||||
* My setting component!
|
|
||||||
* </div>
|
|
||||||
* );
|
|
||||||
* })
|
|
||||||
* })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
customSettingComponents(): ItemList<(attributes: CommonSettingsItemOptions) => Mithril.Children> {
|
|
||||||
const items = new ItemList<(attributes: CommonSettingsItemOptions) => Mithril.Children>();
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `buildSettingComponent` takes a settings object and turns it into a component.
|
* `buildSettingComponent` takes a settings object and turns it into a component.
|
||||||
* Depending on the type of input, you can set the type to 'bool', 'select', or
|
* Depending on the type of input, you can set the type to 'bool', 'select', or
|
||||||
@@ -284,77 +137,9 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
|
|||||||
return entry.call(this);
|
return entry.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const customSettingComponents = this.customSettingComponents();
|
const { setting, ...attrs } = entry;
|
||||||
|
|
||||||
const { setting, help, type, label, ...componentAttrs } = entry;
|
return <FormGroup bidi={this.setting(setting)} {...attrs} />;
|
||||||
|
|
||||||
const value = this.setting(setting)();
|
|
||||||
|
|
||||||
const [inputId, helpTextId] = [generateElementId(), generateElementId()];
|
|
||||||
|
|
||||||
let settingElement: Mithril.Children;
|
|
||||||
|
|
||||||
// Typescript being Typescript
|
|
||||||
// https://github.com/microsoft/TypeScript/issues/14520
|
|
||||||
if ((BooleanSettingTypes as readonly string[]).includes(type)) {
|
|
||||||
return (
|
|
||||||
// TODO: Add aria-describedby for switch help text.
|
|
||||||
//? Requires changes to Checkbox component to allow providing attrs directly for the element(s).
|
|
||||||
<div className="Form-group">
|
|
||||||
<Switch state={!!value && value !== '0'} onchange={this.settings[setting]} {...componentAttrs}>
|
|
||||||
{label}
|
|
||||||
</Switch>
|
|
||||||
{help ? <div className="helpText">{help}</div> : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if ((SelectSettingTypes as readonly string[]).includes(type)) {
|
|
||||||
const { default: defaultValue, options, ...otherAttrs } = componentAttrs;
|
|
||||||
|
|
||||||
settingElement = (
|
|
||||||
<Select
|
|
||||||
id={inputId}
|
|
||||||
aria-describedby={helpTextId}
|
|
||||||
value={value || defaultValue}
|
|
||||||
options={options}
|
|
||||||
onchange={this.settings[setting]}
|
|
||||||
{...otherAttrs}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (type === ImageUploadSettingType) {
|
|
||||||
const { value, ...otherAttrs } = componentAttrs;
|
|
||||||
|
|
||||||
settingElement = <UploadImageButton value={this.settings[setting]} {...otherAttrs} />;
|
|
||||||
} else if (customSettingComponents.has(type)) {
|
|
||||||
return customSettingComponents.get(type)({ setting, help, label, ...componentAttrs });
|
|
||||||
} else {
|
|
||||||
componentAttrs.className = classList('FormControl', componentAttrs.className);
|
|
||||||
|
|
||||||
if ((TextareaSettingTypes as readonly string[]).includes(type)) {
|
|
||||||
settingElement = <textarea id={inputId} aria-describedby={helpTextId} bidi={this.setting(setting)} {...componentAttrs} />;
|
|
||||||
} else {
|
|
||||||
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} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="Form-group">
|
|
||||||
{label && <label for={inputId}>{label}</label>}
|
|
||||||
{help && (
|
|
||||||
<div id={helpTextId} className="helpText">
|
|
||||||
{help}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{settingElement}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,7 +3,7 @@ import Button from '../../common/components/Button';
|
|||||||
import EditCustomCssModal from './EditCustomCssModal';
|
import EditCustomCssModal from './EditCustomCssModal';
|
||||||
import EditCustomHeaderModal from './EditCustomHeaderModal';
|
import EditCustomHeaderModal from './EditCustomHeaderModal';
|
||||||
import EditCustomFooterModal from './EditCustomFooterModal';
|
import EditCustomFooterModal from './EditCustomFooterModal';
|
||||||
import UploadImageButton from './UploadImageButton';
|
import UploadImageButton from '../../common/components/UploadImageButton';
|
||||||
import AdminPage from './AdminPage';
|
import AdminPage from './AdminPage';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from '../../common/utils/ItemList';
|
||||||
import type Mithril from 'mithril';
|
import type Mithril from 'mithril';
|
||||||
|
@@ -36,6 +36,7 @@ import './utils/withAttr';
|
|||||||
import './utils/focusTrap';
|
import './utils/focusTrap';
|
||||||
import './utils/isDark';
|
import './utils/isDark';
|
||||||
import './utils/KeyboardNavigatable';
|
import './utils/KeyboardNavigatable';
|
||||||
|
import './utils/generateElementId';
|
||||||
|
|
||||||
import './models/Notification';
|
import './models/Notification';
|
||||||
import './models/User';
|
import './models/User';
|
||||||
@@ -76,6 +77,8 @@ import './components/TextEditorButton';
|
|||||||
import './components/Tooltip';
|
import './components/Tooltip';
|
||||||
import './components/AutocompleteDropdown';
|
import './components/AutocompleteDropdown';
|
||||||
import './components/GambitsAutocompleteDropdown';
|
import './components/GambitsAutocompleteDropdown';
|
||||||
|
import './components/UploadImageButton';
|
||||||
|
import './components/FormGroup';
|
||||||
|
|
||||||
import './helpers/fullTime';
|
import './helpers/fullTime';
|
||||||
import './components/Avatar';
|
import './components/Avatar';
|
||||||
|
259
framework/core/js/src/common/components/FormGroup.tsx
Normal file
259
framework/core/js/src/common/components/FormGroup.tsx
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import Component from '../Component';
|
||||||
|
import generateElementId from '../utils/generateElementId';
|
||||||
|
import Switch from './Switch';
|
||||||
|
import Select from './Select';
|
||||||
|
import UploadImageButton from './UploadImageButton';
|
||||||
|
import classList from '../utils/classList';
|
||||||
|
import ColorPreviewInput from './ColorPreviewInput';
|
||||||
|
import Stream from '../utils/Stream';
|
||||||
|
import ItemList from '../utils/ItemList';
|
||||||
|
import type { IUploadImageButtonAttrs } from './UploadImageButton';
|
||||||
|
import type { ComponentAttrs } from '../Component';
|
||||||
|
import type Mithril from 'mithril';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that matches any valid value for the `type` attribute on an HTML `<input>` element.
|
||||||
|
*
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-type
|
||||||
|
*
|
||||||
|
* Note: this will be exported from a different location in the future.
|
||||||
|
*
|
||||||
|
* @see https://github.com/flarum/core/issues/3039
|
||||||
|
*/
|
||||||
|
export type HTMLInputTypes =
|
||||||
|
| 'button'
|
||||||
|
| 'checkbox'
|
||||||
|
| 'color'
|
||||||
|
| 'date'
|
||||||
|
| 'datetime-local'
|
||||||
|
| 'email'
|
||||||
|
| 'file'
|
||||||
|
| 'hidden'
|
||||||
|
| 'image'
|
||||||
|
| 'month'
|
||||||
|
| 'number'
|
||||||
|
| 'password'
|
||||||
|
| 'radio'
|
||||||
|
| 'range'
|
||||||
|
| 'reset'
|
||||||
|
| 'search'
|
||||||
|
| 'submit'
|
||||||
|
| 'tel'
|
||||||
|
| 'text'
|
||||||
|
| 'time'
|
||||||
|
| 'url'
|
||||||
|
| 'week';
|
||||||
|
|
||||||
|
export interface CommonFieldOptions extends Mithril.Attributes {
|
||||||
|
label?: Mithril.Children;
|
||||||
|
help?: Mithril.Children;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid options for the setting component builder to generate an HTML input element.
|
||||||
|
*/
|
||||||
|
export interface HTMLInputFieldComponentOptions extends CommonFieldOptions {
|
||||||
|
/**
|
||||||
|
* Any valid HTML input `type` value.
|
||||||
|
*/
|
||||||
|
type: HTMLInputTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BooleanSettingTypes = ['bool', 'checkbox', 'switch', 'boolean'] as const;
|
||||||
|
const SelectSettingTypes = ['select', 'dropdown', 'selectdropdown'] as const;
|
||||||
|
const TextareaSettingTypes = ['textarea'] as const;
|
||||||
|
const ColorPreviewSettingType = 'color-preview' as const;
|
||||||
|
const ImageUploadSettingType = 'image-upload' as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid options for the setting component builder to generate a Switch.
|
||||||
|
*/
|
||||||
|
export interface SwitchFieldComponentOptions extends CommonFieldOptions {
|
||||||
|
type: typeof BooleanSettingTypes[number];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid options for the setting component builder to generate a Select dropdown.
|
||||||
|
*/
|
||||||
|
export interface SelectFieldComponentOptions extends CommonFieldOptions {
|
||||||
|
type: typeof SelectSettingTypes[number];
|
||||||
|
/**
|
||||||
|
* Map of values to their labels
|
||||||
|
*/
|
||||||
|
options: { [value: string]: Mithril.Children };
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid options for the setting component builder to generate a Textarea.
|
||||||
|
*/
|
||||||
|
export interface TextareaFieldComponentOptions extends CommonFieldOptions {
|
||||||
|
type: typeof TextareaSettingTypes[number];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid options for the setting component builder to generate a ColorPreviewInput.
|
||||||
|
*/
|
||||||
|
export interface ColorPreviewFieldComponentOptions extends CommonFieldOptions {
|
||||||
|
type: typeof ColorPreviewSettingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImageUploadFieldComponentOptions extends CommonFieldOptions, IUploadImageButtonAttrs {
|
||||||
|
type: typeof ImageUploadSettingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomFieldComponentOptions extends CommonFieldOptions {
|
||||||
|
type: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All valid options for the setting component builder.
|
||||||
|
*/
|
||||||
|
export type FieldComponentOptions =
|
||||||
|
| HTMLInputFieldComponentOptions
|
||||||
|
| SwitchFieldComponentOptions
|
||||||
|
| SelectFieldComponentOptions
|
||||||
|
| TextareaFieldComponentOptions
|
||||||
|
| ColorPreviewFieldComponentOptions
|
||||||
|
| ImageUploadFieldComponentOptions
|
||||||
|
| CustomFieldComponentOptions;
|
||||||
|
|
||||||
|
export type IFormGroupAttrs = ComponentAttrs &
|
||||||
|
FieldComponentOptions & {
|
||||||
|
bidi?: Stream<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a field component based on the provided attributes.
|
||||||
|
* Depending on the type of input, you can set the type to 'bool', 'select', or
|
||||||
|
* any standard <input> type. Any values inside the 'extra' object will be added
|
||||||
|
* to the component as an attribute.
|
||||||
|
*
|
||||||
|
* Alternatively, you can pass a callback that will be executed in ExtensionPage's
|
||||||
|
* context to include custom JSX elements.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* <FormGroup key="acme.checkbox"
|
||||||
|
* label={app.translator.trans('acme.admin.setting_label')}
|
||||||
|
* type="bool"
|
||||||
|
* help={app.translator.trans('acme.admin.setting_help')}
|
||||||
|
* className="Setting-item" />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* <FormGroup key="acme.select"
|
||||||
|
* label={app.translator.trans('acme.admin.setting_label')}
|
||||||
|
* type="select"
|
||||||
|
* options={{
|
||||||
|
* 'option1': 'Option 1 label',
|
||||||
|
* 'option2': 'Option 2 label',
|
||||||
|
* }}
|
||||||
|
* default="option1" />
|
||||||
|
*/
|
||||||
|
export default class FormGroup<CustomAttrs extends IFormGroupAttrs = IFormGroupAttrs> extends Component<CustomAttrs> {
|
||||||
|
view(vnode: Mithril.Vnode<CustomAttrs, this>): Mithril.Children {
|
||||||
|
const customFieldComponents = this.customFieldComponents();
|
||||||
|
|
||||||
|
const { help, type, label, bidi, ...componentAttrs } = this.attrs;
|
||||||
|
|
||||||
|
// TypeScript being TypeScript
|
||||||
|
const attrs = componentAttrs as unknown as Omit<IFormGroupAttrs, 'bidi' | 'label' | 'help' | 'type'>;
|
||||||
|
|
||||||
|
const value = bidi ? bidi() : null;
|
||||||
|
|
||||||
|
const [inputId, helpTextId] = [generateElementId(), generateElementId()];
|
||||||
|
|
||||||
|
let settingElement: Mithril.Children;
|
||||||
|
|
||||||
|
// Typescript being Typescript
|
||||||
|
// https://github.com/microsoft/TypeScript/issues/14520
|
||||||
|
if ((BooleanSettingTypes as readonly string[]).includes(type)) {
|
||||||
|
return (
|
||||||
|
// TODO: Add aria-describedby for switch help text.
|
||||||
|
//? Requires changes to Checkbox component to allow providing attrs directly for the element(s).
|
||||||
|
<div className="Form-group">
|
||||||
|
<Switch state={!!value && value !== '0'} onchange={bidi} {...attrs}>
|
||||||
|
{label}
|
||||||
|
</Switch>
|
||||||
|
{help ? <div className="helpText">{help}</div> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if ((SelectSettingTypes as readonly string[]).includes(type)) {
|
||||||
|
const { default: defaultValue, options, ...otherAttrs } = attrs;
|
||||||
|
|
||||||
|
settingElement = (
|
||||||
|
<Select id={inputId} aria-describedby={helpTextId} value={value || defaultValue} options={options} onchange={bidi} {...otherAttrs} />
|
||||||
|
);
|
||||||
|
} else if (type === ImageUploadSettingType) {
|
||||||
|
const { value, ...otherAttrs } = attrs;
|
||||||
|
|
||||||
|
settingElement = <UploadImageButton value={bidi} {...otherAttrs} />;
|
||||||
|
} else if (customFieldComponents.has(type)) {
|
||||||
|
return customFieldComponents.get(type)(this.attrs);
|
||||||
|
} else {
|
||||||
|
attrs.className = classList('FormControl', attrs.className);
|
||||||
|
|
||||||
|
if ((TextareaSettingTypes as readonly string[]).includes(type)) {
|
||||||
|
settingElement = <textarea id={inputId} aria-describedby={helpTextId} bidi={bidi} {...attrs} />;
|
||||||
|
} else {
|
||||||
|
let Tag: VnodeElementTag = 'input';
|
||||||
|
|
||||||
|
if (type === ColorPreviewSettingType) {
|
||||||
|
Tag = ColorPreviewInput;
|
||||||
|
} else {
|
||||||
|
attrs.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
settingElement = <Tag id={inputId} aria-describedby={helpTextId} bidi={bidi} {...attrs} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Form-group">
|
||||||
|
{label && <label for={inputId}>{label}</label>}
|
||||||
|
{help && (
|
||||||
|
<div id={helpTextId} className="helpText">
|
||||||
|
{help}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{settingElement}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of extension-defined custom setting components to be available.
|
||||||
|
*
|
||||||
|
* The ItemList key represents the value for the `type` attribute.
|
||||||
|
* All attributes passed are provided as arguments to the function added to the ItemList.
|
||||||
|
*
|
||||||
|
* ItemList priority has no effect here.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* extend(AdminPage.prototype, 'customFieldComponents', function (items) {
|
||||||
|
* // You can access the AdminPage instance with `this` to access its `settings` property.
|
||||||
|
*
|
||||||
|
* // Prefixing the key with your extension ID is recommended to avoid collisions.
|
||||||
|
* items.add('my-ext.setting-component', (attrs) => {
|
||||||
|
* return (
|
||||||
|
* <div className={attrs.className}>
|
||||||
|
* <label>{attrs.label}</label>
|
||||||
|
* {attrs.help && <p className="helpText">{attrs.help}</p>}
|
||||||
|
*
|
||||||
|
* My setting component!
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
* })
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
customFieldComponents(): ItemList<(attributes: CustomAttrs) => Mithril.Children> {
|
||||||
|
const items = new ItemList<(attributes: CustomAttrs) => Mithril.Children>();
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user