mirror of
https://github.com/discourse/discourse.git
synced 2025-05-30 07:11:34 +08:00
DEV: form-kit
This PR introduces FormKit, a component-based form library designed to simplify form creation and management. This library provides a single `Form` component, various field components, controls, validation mechanisms, and customization options. Additionally, it includes helpers to facilitate testing and writing specifications for forms. 1. **Form Component**: - The main component that encapsulates form logic and structure. - Yields various utilities like `Field`, `Submit`, `Alert`, etc. **Example Usage**: ```gjs import Form from "discourse/form"; <template> <Form as |form|> <form.Field @name="username" @title="Username" @validation="required" as |field| > <field.Input /> </form.Field> <form.Field @name="age" @title="Age" as |field|> <field.Input @type="number" /> </form.Field> <form.Submit /> </Form> </template> ``` 2. **Validation**: - Built-in validation rules such as `required`, `number`, `length`, and `url`. - Custom validation callbacks for more complex validation logic. **Example Usage**: ```javascript validateUsername(name, value, data, { addError }) { if (data.bar / 2 === value) { addError(name, "That's not how maths work."); } } ``` ```hbs <form.Field @name="username" @validate={{this.validateUsername}} /> ``` 3. **Customization**: - Plugin outlets for extending form functionality. - Styling capabilities through propagated attributes. - Custom controls with properties provided by `form` and `field`. **Example Usage**: ```hbs <Form class="my-form" as |form|> <form.Field class="my-field" as |field|> <MyCustomControl id={{field.id}} @onChange={{field.set}} /> </form.Field> </Form> ``` 4. **Helpers for Testing**: - Test assertions for form and field validation. **Example usage**: ```javascript assert.form().hasErrors("the form shows errors"); assert.form().field("foo").hasValue("bar", "user has set the value"); ``` - Helper for interacting with he form **Example usage**: ```javascript await formKit().field("foo").fillIn("bar"); ``` 5. **Page Object for System Specs**: - Page objects for interacting with forms in system specs. - Methods for submitting forms, checking alerts, and interacting with fields. **Example Usage**: ```ruby form = PageObjects::Components::FormKit.new(".my-form") form.submit expect(form).to have_an_alert("message") ``` **Field Interactions**: ```ruby field = form.field("foo") expect(field).to have_value("bar") field.fill_in("bar") ``` 6. **Collections handling**: - A specific component to handle array of objects **Example Usage**: ```gjs <Form @data={{hash foo=(array (hash bar=1) (hash bar=2))}} as |form|> <form.Collection @name="foo" as |collection|> <collection.Field @name="bar" @title="Bar" as |field|> <field.Input /> </collection.Field> </form.Collection> </Form> ```
This commit is contained in:
@ -0,0 +1,250 @@
|
||||
<h2>Controls</h2>
|
||||
<StyleguideExample @title="Input">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Username" @name="username" as |field|>
|
||||
<field.Input placeholder="Username" />
|
||||
</form.Field>
|
||||
<form.Field @title="Age" @name="age" as |field|>
|
||||
<field.Input placeholder="Age" @type="number" @format="small" />
|
||||
</form.Field>
|
||||
<form.Field @title="Website" @name="website" as |field|>
|
||||
<field.Input @before="https://" @after=".com" @format="large" />
|
||||
</form.Field>
|
||||
<form.Field @title="After" @name="after" as |field|>
|
||||
<field.Input @after=".com" />
|
||||
</form.Field>
|
||||
<form.Field @title="Before" @name="before" as |field|>
|
||||
<field.Input @before="https://" />
|
||||
</form.Field>
|
||||
<form.Field @title="Secret" @name="secret" as |field|>
|
||||
<field.Password />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Question">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Enabled" @name="enabled" as |field|>
|
||||
<field.Question />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Toggle">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Enabled" @name="enabled" as |field|>
|
||||
<field.Toggle />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Composer">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Query" @name="query" as |field|>
|
||||
<field.Composer />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Code">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Query" @name="query" as |field|>
|
||||
<field.Code />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Textarea">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Query" @name="query" as |field|>
|
||||
<field.Textarea />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Select">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Enabled" @name="enabled" as |field|>
|
||||
<field.Select as |select|>
|
||||
<select.Option @value="true">Yes</select.Option>
|
||||
<select.Option @value="false">No</select.Option>
|
||||
</field.Select>
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Checkbox">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Contract" @name="contract" as |field|>
|
||||
<field.Checkbox>Accept the contract</field.Checkbox>
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Image">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Image" @name="image" as |field|>
|
||||
<field.Image />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Icon">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Icon" @name="icon" as |field|>
|
||||
<field.Icon />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Menu">
|
||||
<Form as |form data|>
|
||||
<form.Field @title="Enabled" @name="enabled" as |field|>
|
||||
<field.Menu @selection={{data.enabled}} as |menu|>
|
||||
<menu.Item @value="true">Yes</menu.Item>
|
||||
<menu.Divider />
|
||||
<menu.Item @value="false">No</menu.Item>
|
||||
</field.Menu>
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="RadioGroup">
|
||||
<Form as |form|>
|
||||
<form.Field @title="Enabled" @name="enabled" as |field|>
|
||||
<field.RadioGroup as |radioGroup|>
|
||||
<radioGroup.Radio @value="true">Yes</radioGroup.Radio>
|
||||
<radioGroup.Radio @value="false">No</radioGroup.Radio>
|
||||
</field.RadioGroup>
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<h2>Layout</h2>
|
||||
|
||||
<StyleguideExample @title="Section">
|
||||
<Form as |form|>
|
||||
<form.Section @title="Section title">
|
||||
Content
|
||||
</form.Section>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Alert">
|
||||
<Form as |form|>
|
||||
<form.Alert @icon="pencil-alt">
|
||||
You can edit this form.
|
||||
</form.Alert>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="InputGroup">
|
||||
<Form as |form|>
|
||||
<form.InputGroup as |inputGroup|>
|
||||
<inputGroup.Field @title="Username" @name="username" as |field|>
|
||||
<field.Input />
|
||||
</inputGroup.Field>
|
||||
<inputGroup.Field @title="Email" @name="email" as |field|>
|
||||
<field.Input />
|
||||
</inputGroup.Field>
|
||||
</form.InputGroup>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Collection">
|
||||
<Form
|
||||
@data={{hash foo=(array (hash bar=1 baz=2) (hash bar=3 baz=4))}}
|
||||
as |form|
|
||||
>
|
||||
<form.Button @action={{fn form.addItemToCollection "foo"}} @icon="plus" />
|
||||
|
||||
<form.Collection @name="foo" as |collection index|>
|
||||
<form.Row as |row|>
|
||||
<row.Col @size={{6}}>
|
||||
<collection.Field @title="Bar" @name="bar" as |field|>
|
||||
<field.Input />
|
||||
</collection.Field>
|
||||
</row.Col>
|
||||
|
||||
<row.Col @size={{4}}>
|
||||
<collection.Field @title="Baz" @name="baz" as |field|>
|
||||
<field.Input />
|
||||
</collection.Field>
|
||||
</row.Col>
|
||||
|
||||
<row.Col @size={{2}}>
|
||||
<form.Button @action={{fn collection.remove index}} @icon="minus" />
|
||||
</row.Col>
|
||||
</form.Row>
|
||||
</form.Collection>
|
||||
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Row/Col">
|
||||
<Form as |form|>
|
||||
<form.Row as |row|>
|
||||
<row.Col @size={{6}}>
|
||||
<form.Field
|
||||
@title="Username"
|
||||
@name="username"
|
||||
@validation="required"
|
||||
as |field|
|
||||
>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</row.Col>
|
||||
<row.Col @size={{4}}>
|
||||
<form.Field @title="Email" @name="email" as |field|>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</row.Col>
|
||||
<row.Col @size={{2}}>
|
||||
<form.Submit />
|
||||
</row.Col>
|
||||
</form.Row>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="Multiline">
|
||||
<Form as |form|>
|
||||
<form.Row as |row|>
|
||||
<row.Col @size={{6}}>
|
||||
<form.Field
|
||||
@title="Username"
|
||||
@name="username"
|
||||
@validation="required"
|
||||
as |field|
|
||||
>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</row.Col>
|
||||
<row.Col @size={{6}}>
|
||||
<form.Field @title="Email" @name="email" as |field|>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</row.Col>
|
||||
|
||||
<row.Col @size={{12}}>
|
||||
<form.Field @title="Adress" @name="adress" as |field|>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</row.Col>
|
||||
</form.Row>
|
||||
</Form>
|
||||
</StyleguideExample>
|
||||
|
||||
<h2>Validation</h2>
|
||||
|
||||
<StyleguideExample @title="Input">
|
||||
<Form @validateOn="change" as |form|>
|
||||
<form.Field
|
||||
@title="Username"
|
||||
@name="username"
|
||||
@validation="required"
|
||||
as |field|
|
||||
>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
</Form>
|
||||
</StyleguideExample>
|
@ -1,108 +0,0 @@
|
||||
<StyleguideExample @title="text-field">
|
||||
<TextField @placeholder="Placeholder" />
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="password">
|
||||
<PasswordField type="password" placeholder="Placeholder" />
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="textarea">
|
||||
<Textarea placeholder="Placeholder" />
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="inline-form">
|
||||
<div class="inline-form">
|
||||
<TextField @placeholder="Placeholder" />
|
||||
<DButton
|
||||
@icon="search"
|
||||
@translatedLabel="Submit"
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="inline-form with icon button">
|
||||
<div class="inline-form">
|
||||
<TextField @placeholder="Placeholder" />
|
||||
<DButton @icon="search" type="submit" class="btn-primary" />
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="full-width inline-form with single input">
|
||||
<div class="inline-form full-width">
|
||||
<TextField @placeholder="Placeholder" />
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="full-width inline-form with input and icon button">
|
||||
<div class="inline-form full-width">
|
||||
<TextField @placeholder="Placeholder" />
|
||||
<DButton @icon="search" type="submit" class="btn-primary" />
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample
|
||||
@title="inline-form with <ComboBox>"
|
||||
@initialValue={{get @dummy "options.0.name"}}
|
||||
as |value|
|
||||
>
|
||||
<div class="inline-form">
|
||||
<TextField @placeholder="Placeholder" />
|
||||
<ComboBox
|
||||
@content={{@dummy.options}}
|
||||
@value={{value}}
|
||||
@onChange={{fn (mut value)}}
|
||||
/>
|
||||
<DButton
|
||||
@icon="search"
|
||||
@translatedLabel="Submit"
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="inline-form with <MultiSelect>">
|
||||
<div class="inline-form">
|
||||
<TextField />
|
||||
<MultiSelect @content={{@dummy.options}} @onChange={{@dummyAction}} />
|
||||
<DButton
|
||||
@icon="search"
|
||||
@translatedLabel="Submit"
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="inline-form with <MultiSelect> and label">
|
||||
<div class="inline-form">
|
||||
<label>Text:</label>
|
||||
<TextField />
|
||||
<MultiSelect @content={{@dummy.options}} @onChange={{@dummyAction}} />
|
||||
<DButton
|
||||
@icon="search"
|
||||
@translatedLabel="Submit"
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="full-width inline-form with search type input">
|
||||
<div class="inline-form full-width">
|
||||
<Input placeholder="Search type input" @type="search" />
|
||||
</div>
|
||||
</StyleguideExample>
|
||||
|
||||
<StyleguideExample @title="<CategoryNotificationsButton> and regular button">
|
||||
<div class="inline-form">
|
||||
<CategoryNotificationsButton
|
||||
@category={{get @dummy.categories 0}}
|
||||
@value={{1}}
|
||||
@onChange={{@dummyAction}}
|
||||
/>
|
||||
<DButton @icon="reply" @type="submit" @translatedLabel="Button" />
|
||||
</div>
|
||||
</StyleguideExample>
|
@ -3,7 +3,7 @@ import fontScale from "../components/sections/atoms/01-font-scale";
|
||||
import buttons from "../components/sections/atoms/02-buttons";
|
||||
import colors from "../components/sections/atoms/03-colors";
|
||||
import icons from "../components/sections/atoms/04-icons";
|
||||
import inputFields from "../components/sections/atoms/05-input-fields";
|
||||
import forms from "../components/sections/atoms/05-forms";
|
||||
import spinners from "../components/sections/atoms/06-spinners";
|
||||
import dateTimeInputs from "../components/sections/atoms/date-time-inputs";
|
||||
import dropdowns from "../components/sections/atoms/dropdowns";
|
||||
@ -51,9 +51,9 @@ const SECTIONS = [
|
||||
{ component: colors, category: "atoms", id: "colors", priority: 3 },
|
||||
{ component: icons, category: "atoms", id: "icons", priority: 4 },
|
||||
{
|
||||
component: inputFields,
|
||||
component: forms,
|
||||
category: "atoms",
|
||||
id: "input-fields",
|
||||
id: "forms",
|
||||
priority: 5,
|
||||
},
|
||||
{ component: spinners, category: "atoms", id: "spinners", priority: 6 },
|
||||
|
@ -30,8 +30,8 @@ en:
|
||||
icons:
|
||||
title: "Icons"
|
||||
full_list: "See the full list of Font Awesome Icons"
|
||||
input_fields:
|
||||
title: "Input Fields"
|
||||
forms:
|
||||
title: "Forms"
|
||||
buttons:
|
||||
title: "Buttons"
|
||||
dropdowns:
|
||||
|
Reference in New Issue
Block a user