mirror of
https://github.com/discourse/discourse.git
synced 2025-06-02 04:08:41 +08:00
FIX: better standalone checkbox support (#31130)
Before this commit it was complicated to render a `Checkbox` outside of a `CheckboxGroup` as you would get no title, no description, no optional hint and not tooltip. This commits makes all of this possible by adding a special case for checkboxes, and sharing code for tooltips and optional hint. This commit also uses this opportunity to refactor part of the code to use curryComponent and reduce code duplication.
This commit is contained in:
@ -13,7 +13,6 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import ApiKeyUrlsModal from "admin/components/modal/api-key-urls";
|
||||
import EmailGroupUserChooser from "select-kit/components/email-group-user-chooser";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
export default class AdminConfigAreasApiKeysNew extends Component {
|
||||
@service router;
|
||||
@ -239,7 +238,6 @@ export default class AdminConfigAreasApiKeysNew extends Component {
|
||||
<table class="scopes-table grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>{{i18n "admin.api.scopes.allowed_urls"}}</td>
|
||||
<td>{{i18n
|
||||
@ -265,28 +263,18 @@ export default class AdminConfigAreasApiKeysNew extends Component {
|
||||
<topicsCollection.Field
|
||||
@name="enabled"
|
||||
@title={{collectionData.key}}
|
||||
@showTitle={{false}}
|
||||
as |field|
|
||||
>
|
||||
<field.Checkbox />
|
||||
</topicsCollection.Field>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
class="scope-name"
|
||||
>{{collectionData.name}}</div>
|
||||
<DTooltip
|
||||
@icon="circle-question"
|
||||
@content={{i18n
|
||||
@tooltip={{i18n
|
||||
(concat
|
||||
"admin.api.scopes.descriptions."
|
||||
scopeName
|
||||
"."
|
||||
collectionData.key
|
||||
)
|
||||
class="scope-tooltip"
|
||||
}}
|
||||
/>
|
||||
as |field|
|
||||
>
|
||||
<field.Checkbox />
|
||||
</topicsCollection.Field>
|
||||
</td>
|
||||
<td>
|
||||
<DButton
|
||||
|
@ -257,13 +257,11 @@ export default class AdminConfigAreasWebhookForm extends Component {
|
||||
</field.Custom>
|
||||
</form.Field>
|
||||
|
||||
<span>
|
||||
<PluginOutlet
|
||||
@name="web-hook-fields"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash model=this.webhook}}
|
||||
/>
|
||||
</span>
|
||||
<PluginOutlet
|
||||
@name="web-hook-fields"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash model=this.webhook}}
|
||||
/>
|
||||
|
||||
<form.Field
|
||||
@name="verify_certificate"
|
||||
|
@ -1,31 +1,28 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { concat, fn } from "@ember/helper";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||
import { eq } from "truth-helpers";
|
||||
import FKLabel from "discourse/form-kit/components/fk/label";
|
||||
import FKMeta from "discourse/form-kit/components/fk/meta";
|
||||
import FKOptional from "discourse/form-kit/components/fk/optional";
|
||||
import FKText from "discourse/form-kit/components/fk/text";
|
||||
import FKTooltip from "discourse/form-kit/components/fk/tooltip";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
export default class FKControlWrapper extends Component {
|
||||
get controlType() {
|
||||
if (this.args.component.controlType === "input") {
|
||||
return this.args.component.controlType + "-" + (this.args.type || "text");
|
||||
return this.args.field.type + "-" + (this.args.type || "text");
|
||||
}
|
||||
|
||||
return this.args.component.controlType;
|
||||
return this.args.field.type;
|
||||
}
|
||||
|
||||
get error() {
|
||||
return (this.args.errors ?? {})[this.args.field.name];
|
||||
}
|
||||
|
||||
get isComponentTooltip() {
|
||||
return typeof this.args.field.tooltip === "object";
|
||||
}
|
||||
|
||||
get titleFormat() {
|
||||
return this.args.field.titleFormat || this.args.field.format;
|
||||
}
|
||||
@ -52,42 +49,34 @@ export default class FKControlWrapper extends Component {
|
||||
data-disabled={{@field.disabled}}
|
||||
data-name={{@field.name}}
|
||||
data-control-type={{this.controlType}}
|
||||
{{didInsert (fn @registerField @field.name @field)}}
|
||||
{{willDestroy (fn @unregisterField @field.name)}}
|
||||
>
|
||||
{{#if @field.showTitle}}
|
||||
<FKLabel
|
||||
class={{concatClass
|
||||
"form-kit__container-title"
|
||||
(if this.titleFormat (concat "--" this.titleFormat))
|
||||
}}
|
||||
@fieldId={{@field.id}}
|
||||
>
|
||||
<span>{{@field.title}}</span>
|
||||
{{#unless (eq @field.type "checkbox")}}
|
||||
{{#if @field.showTitle}}
|
||||
<FKLabel
|
||||
class={{concatClass
|
||||
"form-kit__container-title"
|
||||
(if this.titleFormat (concat "--" this.titleFormat))
|
||||
}}
|
||||
@fieldId={{@field.id}}
|
||||
>
|
||||
<span>{{@field.title}}</span>
|
||||
|
||||
{{#unless @field.required}}
|
||||
<span class="form-kit__container-optional">({{i18n
|
||||
"form_kit.optional"
|
||||
}})</span>
|
||||
{{/unless}}
|
||||
<FKOptional @field={{@field}} />
|
||||
<FKTooltip @field={{@field}} />
|
||||
</FKLabel>
|
||||
{{/if}}
|
||||
|
||||
{{#if @field.tooltip}}
|
||||
{{#if this.isComponentTooltip}}
|
||||
<@field.tooltip />
|
||||
{{else}}
|
||||
<DTooltip @icon="circle-question" @content={{@field.tooltip}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</FKLabel>
|
||||
{{/if}}
|
||||
|
||||
{{#if @field.description}}
|
||||
<FKText
|
||||
class={{concatClass
|
||||
"form-kit__container-description"
|
||||
(if this.descriptionFormat (concat "--" this.descriptionFormat))
|
||||
}}
|
||||
>{{@field.description}}</FKText>
|
||||
{{/if}}
|
||||
{{#if @field.description}}
|
||||
<FKText
|
||||
class={{concatClass
|
||||
"form-kit__container-description"
|
||||
(if this.descriptionFormat (concat "--" this.descriptionFormat))
|
||||
}}
|
||||
>{{@field.description}}</FKText>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
<div
|
||||
class={{concatClass
|
||||
|
@ -3,6 +3,8 @@ import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { eq } from "truth-helpers";
|
||||
import FKLabel from "discourse/form-kit/components/fk/label";
|
||||
import FKOptional from "discourse/form-kit/components/fk/optional";
|
||||
import FKTooltip from "discourse/form-kit/components/fk/tooltip";
|
||||
|
||||
export default class FKControlCheckbox extends Component {
|
||||
static controlType = "checkbox";
|
||||
@ -24,7 +26,9 @@ export default class FKControlCheckbox extends Component {
|
||||
/>
|
||||
<span class="form-kit__control-checkbox-content">
|
||||
<span class="form-kit__control-checkbox-title">
|
||||
{{@field.title}}
|
||||
<span>{{@field.title}}</span>
|
||||
<FKOptional @field={{@field}} />
|
||||
<FKTooltip @field={{@field}} />
|
||||
</span>
|
||||
{{#if (has-block)}}
|
||||
<span class="form-kit__control-checkbox-description">{{yield}}</span>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import ValidationParser from "discourse/form-kit/lib/validation-parser";
|
||||
import Validator from "discourse/form-kit/lib/validator";
|
||||
@ -8,6 +9,12 @@ import uniqueId from "discourse/helpers/unique-id";
|
||||
* Represents a field in a form with validation, registration, and field data management capabilities.
|
||||
*/
|
||||
export default class FKFieldData extends Component {
|
||||
/**
|
||||
* Type of the field.
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked type;
|
||||
|
||||
/**
|
||||
* Unique identifier for the field.
|
||||
* @type {string}
|
||||
@ -20,12 +27,6 @@ export default class FKFieldData extends Component {
|
||||
*/
|
||||
errorId = uniqueId();
|
||||
|
||||
/**
|
||||
* Type of the field.
|
||||
* @type {string}
|
||||
*/
|
||||
type;
|
||||
|
||||
/**
|
||||
* Initializes the FKFieldData component.
|
||||
* Validates the presence of required arguments and registers the field.
|
||||
@ -37,8 +38,6 @@ export default class FKFieldData extends Component {
|
||||
if (!this.args.title?.length) {
|
||||
throw new Error("@title is required on `<form.Field />`.");
|
||||
}
|
||||
|
||||
this.args.registerField(this.name, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,8 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { getOwner } from "@ember/owner";
|
||||
import curryComponent from "ember-curry-component";
|
||||
import FKControlCheckbox from "discourse/form-kit/components/fk/control/checkbox";
|
||||
import FKControlCode from "discourse/form-kit/components/fk/control/code";
|
||||
import FKControlComposer from "discourse/form-kit/components/fk/control/composer";
|
||||
@ -40,6 +43,30 @@ export default class FKField extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
componentFor(component, field) {
|
||||
const instance = this;
|
||||
const baseArguments = {
|
||||
get errors() {
|
||||
return instance.args.errors;
|
||||
},
|
||||
unregisterField: instance.args.unregisterField,
|
||||
registerField: instance.args.registerField,
|
||||
component,
|
||||
field,
|
||||
};
|
||||
|
||||
if (!component.controlType) {
|
||||
throw new Error(
|
||||
`Static property \`controlType\` is required on component:\n\n ${component}`
|
||||
);
|
||||
}
|
||||
|
||||
field.type = component.controlType;
|
||||
|
||||
return curryComponent(FKControlWrapper, baseArguments, getOwner(this));
|
||||
}
|
||||
|
||||
<template>
|
||||
<FKFieldData
|
||||
@name={{@name}}
|
||||
@ -66,104 +93,20 @@ export default class FKField extends Component {
|
||||
<this.wrapper @size={{@size}}>
|
||||
{{yield
|
||||
(hash
|
||||
Custom=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlCustom
|
||||
field=field
|
||||
)
|
||||
Code=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlCode
|
||||
field=field
|
||||
)
|
||||
Question=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlQuestion
|
||||
field=field
|
||||
)
|
||||
Textarea=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlTextarea
|
||||
field=field
|
||||
)
|
||||
Checkbox=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlCheckbox
|
||||
field=field
|
||||
)
|
||||
Image=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlImage
|
||||
field=field
|
||||
)
|
||||
Password=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlPassword
|
||||
field=field
|
||||
)
|
||||
Composer=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlComposer
|
||||
field=field
|
||||
)
|
||||
Icon=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlIcon
|
||||
field=field
|
||||
)
|
||||
Toggle=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlToggle
|
||||
field=field
|
||||
)
|
||||
Menu=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlMenu
|
||||
field=field
|
||||
)
|
||||
Select=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlSelect
|
||||
field=field
|
||||
)
|
||||
Input=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlInput
|
||||
field=field
|
||||
)
|
||||
RadioGroup=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlRadioGroup
|
||||
field=field
|
||||
)
|
||||
Custom=(this.componentFor FKControlCustom field)
|
||||
Code=(this.componentFor FKControlCode field)
|
||||
Question=(this.componentFor FKControlQuestion field)
|
||||
Textarea=(this.componentFor FKControlTextarea field)
|
||||
Checkbox=(this.componentFor FKControlCheckbox field)
|
||||
Image=(this.componentFor FKControlImage field)
|
||||
Password=(this.componentFor FKControlPassword field)
|
||||
Composer=(this.componentFor FKControlComposer field)
|
||||
Icon=(this.componentFor FKControlIcon field)
|
||||
Toggle=(this.componentFor FKControlToggle field)
|
||||
Menu=(this.componentFor FKControlMenu field)
|
||||
Select=(this.componentFor FKControlSelect field)
|
||||
Input=(this.componentFor FKControlInput field)
|
||||
RadioGroup=(this.componentFor FKControlRadioGroup field)
|
||||
errorId=field.errorId
|
||||
id=field.id
|
||||
name=field.name
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
const FKOptional = <template>
|
||||
{{#unless @field.required}}
|
||||
<span class="form-kit__container-optional">({{i18n
|
||||
"form_kit.optional"
|
||||
}})</span>
|
||||
{{/unless}}
|
||||
</template>;
|
||||
|
||||
export default FKOptional;
|
@ -0,0 +1,22 @@
|
||||
import Component from "@glimmer/component";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
export default class FKTooltip extends Component {
|
||||
get isComponentTooltip() {
|
||||
return typeof this.args.field.tooltip === "object";
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @field.tooltip}}
|
||||
{{#if this.isComponentTooltip}}
|
||||
<@field.tooltip />
|
||||
{{else}}
|
||||
<DTooltip
|
||||
class="form-kit__tooltip"
|
||||
@icon="circle-question"
|
||||
@content={{@field.tooltip}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
@ -44,5 +44,31 @@ module(
|
||||
|
||||
assert.dom(".form-kit__control-checkbox").hasAttribute("disabled");
|
||||
});
|
||||
|
||||
test("@tooltip", async function (assert) {
|
||||
await render(<template>
|
||||
<Form as |form|>
|
||||
<form.Field @tooltip="test" @name="foo" @title="Foo" as |field|>
|
||||
<field.Checkbox />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</template>);
|
||||
|
||||
assert
|
||||
.dom(".form-kit__control-checkbox-content .form-kit__tooltip")
|
||||
.exists();
|
||||
});
|
||||
|
||||
test("optional", async function (assert) {
|
||||
await render(<template>
|
||||
<Form as |form|>
|
||||
<form.Field @name="foo" @title="Foo" as |field|>
|
||||
<field.Checkbox />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</template>);
|
||||
|
||||
assert.form().field("foo").hasTitle("Foo (optional)");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -83,8 +83,8 @@ module("Integration | Component | FormKit | Field", function (hooks) {
|
||||
|
||||
await render(<template>
|
||||
<Form as |form|>
|
||||
<form.Field @name="foo.bar" @title="Foo" @size={{8}}>
|
||||
Test
|
||||
<form.Field @name="foo.bar" @title="Foo" @size={{8}} as |field|>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</template>);
|
||||
@ -102,8 +102,8 @@ module("Integration | Component | FormKit | Field", function (hooks) {
|
||||
|
||||
await render(<template>
|
||||
<Form as |form|>
|
||||
<form.Field @name="foo" @size={{8}}>
|
||||
Test
|
||||
<form.Field @name="foo" @size={{8}} as |field|>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</template>);
|
||||
|
@ -22,9 +22,41 @@ module(
|
||||
</Form>
|
||||
</template>);
|
||||
|
||||
assert.form().field("foo").hasTitle("Foo");
|
||||
assert.form().field("bar").hasTitle("Bar");
|
||||
assert.form().field("foo").hasTitle("Foo (optional)");
|
||||
assert.form().field("bar").hasTitle("Bar (optional)");
|
||||
assert.form().field("bar").hasDescription("A description");
|
||||
});
|
||||
|
||||
test("@title", async function (assert) {
|
||||
await render(<template>
|
||||
<Form as |form|>
|
||||
<form.CheckboxGroup @title="bar" as |checkboxGroup|>
|
||||
<checkboxGroup.Field @name="foo" @title="Foo" as |field|>
|
||||
<field.Checkbox />
|
||||
</checkboxGroup.Field>
|
||||
</form.CheckboxGroup>
|
||||
</Form>
|
||||
</template>);
|
||||
|
||||
assert
|
||||
.dom(".form-kit__checkbox-group .form-kit__fieldset-title")
|
||||
.hasText("bar");
|
||||
});
|
||||
|
||||
test("@description", async function (assert) {
|
||||
await render(<template>
|
||||
<Form as |form|>
|
||||
<form.CheckboxGroup @description="bar" as |checkboxGroup|>
|
||||
<checkboxGroup.Field @name="foo" @title="Foo" as |field|>
|
||||
<field.Checkbox />
|
||||
</checkboxGroup.Field>
|
||||
</form.CheckboxGroup>
|
||||
</Form>
|
||||
</template>);
|
||||
|
||||
assert
|
||||
.dom(".form-kit__checkbox-group .form-kit__fieldset-description")
|
||||
.hasText("bar");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -811,14 +811,6 @@ $mobile-breakpoint: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
.badges,
|
||||
.web-hook-container {
|
||||
input[type="text"],
|
||||
textarea {
|
||||
min-width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-successful {
|
||||
color: var(--success);
|
||||
}
|
||||
|
@ -24,6 +24,10 @@
|
||||
|
||||
// Api keys
|
||||
.admin-api-keys {
|
||||
.form-kit__container-optional {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.api-key-show {
|
||||
.form-element,
|
||||
.form-element-desc {
|
||||
@ -136,16 +140,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: calc(100% - 10px);
|
||||
}
|
||||
|
||||
.select-kit,
|
||||
.select-kit.multi-select {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.event-selector {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
|
@ -30,3 +30,4 @@
|
||||
@import "_row";
|
||||
@import "_section";
|
||||
@import "_variables";
|
||||
@import "_tooltip";
|
||||
|
3
app/assets/stylesheets/common/form-kit/_tooltip.scss
Normal file
3
app/assets/stylesheets/common/form-kit/_tooltip.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.form-kit__tooltip {
|
||||
color: var(--primary-medium);
|
||||
}
|
Reference in New Issue
Block a user