Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added form field component #27

Merged
merged 17 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@docfy/core": "^0.5.0",
"@docfy/ember": "^0.5.0",
"@ember/optional-features": "^2.0.0",
"@ember/string": "^3.0.1",
"@ember/test-helpers": "^2.8.1",
"@embroider/compat": "^2.0.0",
"@embroider/core": "^2.0.0",
Expand Down
22 changes: 22 additions & 0 deletions docs/components/field/demo/base-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
```hbs template
<Form::Field as |field|>
<field.Label for={{field.id}}>Label</field.Label>
<field.Hint id={{field.hintId}}>Extra information about the field</field.Hint>
<field.Control>
<input
class='border bg-basement text-titles-and-attributes p-1 rounded-sm'
id={{field.id}}
aria-invalid='true'
aria-describedby='{{field.hintId}} {{field.errorId}}'
...attributes
/>
</field.Control>
<field.Error id={{field.errorId}}>Error message</field.Error>
</Form::Field>
```

```js component
import Component from '@glimmer/component';

export default class extends Component {}
```
74 changes: 74 additions & 0 deletions docs/components/field/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Field

Field is a component to aid in creating form components. It provides a label, hint sections for things like help text, a control block, and an error section for rendering errors. It allows users to provide custom controls with a consistent form-element shell and is not opinionated on what underlying control element is used.

## Yielded Items

- `id`: A unique ID to provide to the control element `id` attribute and the Label component `for` attribute.
- `hintId`: A unique ID to provide to the control element `aria-describedby` attribute and the Hint component `id` attribute.
- `errorId`: A unique ID to provide to the control element `aria-describedby` or `aria-errormessage` attribute and the Error component `id` attribute.
- `Label`: Renders a `<label>` element. Form element label text is normally rendered here.
- `Hint`: Renders a `<div>` element. Help text or supplemental information is normally rendered here.
- `Control`: A control block where a form element is rendered, for example, an `<input>`, `<textarea>`, etc.
- `Error`: Renders a `<div>` element. Error information is normally rendered here.

## Accessibility

The Field component does not handle accessibility automatically for the label, hint, and error sections of the component; however, it does provide identifiers to assist here. Each code example on this page also shows how to take advantage of these IDs.

- [for](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/for)
- [aria-describedby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby)
- [aria-errormessage](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-errormessage)
- [aria-invalid](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid)

## Optionally Rendering Components

The yielded components from Field can be optionally rendered by using the `{{#if}}` helper. The below example optionally renders the Hint and Error based on component arguments.

```hbs
<Form::Field as |field|>
<field.Label for={{field.id}}>{{@label}}</field.Label>

{{#if @helperText}}
<field.Hint id={{field.hintId}}>{{@helperText}}</field.Hint>
{{/if}}

<field.Control>
<input id={{field.id}} aria-describedby={{field.hintId}} aria-invalid={{if @error "true"}} aria-errormessage={{if @error field.errorId}}class='border-critical bg-blue' ...attributes />
</field.Control>

{{#if @error}}
<field.Error id={{field.errorId}}>{{@error}}</field.Error>
{{/if}}
</Form::Field>
```

### Attributes and Modifiers

Attributes are spread to each sub component of the Field via `...attributes`, so HTML attributes and Ember modifiers can be added.

```hbs
<Form::Field as |field|>
<field.Label
for={{field.id}}
class='mt-3'
data-test-label
{{on 'hover' this.onLabelHover}}
>First name</field.Label>
{{! other components below ...}}
</Form::Field>
```

### Ordering of Components

Ordering of the elements can be changed by adjusting the order of the children. For example, if it is preferred to put the hint block underneath the control.

```hbs
<Form::Field as |field|>
<field.Label for={{field.id}}>First name</field.Label>
<field.Control>
<input id={{field.id}} aria-describedby={{field.hintId}} ...attributes />
</field.Control>
<field.Hint id={{field.hintId}}>Hint text below the input</field.Hint>
</Form::Field>
```
10 changes: 6 additions & 4 deletions ember-toucan-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
"@embroider/addon-dev": "^3.0.0",
"@glimmer/component": "^1.1.2",
"@glimmer/tracking": "^1.1.2",
"@glint/core": "^0.9.7",
"@glint/environment-ember-loose": "^0.9.7",
"@glint/template": "^0.9.7",
"@glint/core": "^1.0.0-beta.3",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beta versions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! Pairing with @NullVoxPopuli we hit a few issues with the current version that this version seemed to solve. By the time we release, maybe @glint/core will be proper 1.0.0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, the beta.3 is very close to full 1.0.0

"@glint/environment-ember-loose": "^1.0.0-beta.3",
"@glint/template": "^1.0.0-beta.3",
"@nullvoxpopuli/eslint-configs": "^3.0.4",
"@tsconfig/ember": "^2.0.0",
"@types/ember": "^4.0.0",
Expand Down Expand Up @@ -113,7 +113,8 @@
"type": "addon",
"main": "addon-main.cjs",
"app-js": {
"./components/button.js": "./dist/_app_/components/button.js"
"./components/button.js": "./dist/_app_/components/button.js",
"./components/form/field.js": "./dist/_app_/components/form/field.js"
}
},
"exports": {
Expand All @@ -122,6 +123,7 @@
"types": "./dist/*.d.ts",
"default": "./dist/*.js"
},
"./components/**/-private/*": null,
"./test-support": "./dist/test-support/index.js",
"./addon-main.js": "./addon-main.cjs"
},
Expand Down
9 changes: 4 additions & 5 deletions ember-toucan-core/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ export default {
// These are the modules that users should be able to import from your
// addon. Anything not listed here may get optimized away.
addon.publicEntrypoints([
'components/**/*.js',
'index.js',
'template-registry.js',
'test-support/index.js',
// For our own build we treat all JS modules as entry points, to not cause rollup-plugin-ts to mess things up badly when trying to tree-shake TS declarations
// but the actual importable modules are further restricted by the package.json entry points!
'**/*.js',
]),

// These are the modules that should get reexported into the traditional
// "app" tree. Things in here should also be in publicEntrypoints above, but
// not everything in publicEntrypoints necessarily needs to go here.
addon.appReexports(['components/**/*.js']),
addon.appReexports(['components/*.js', 'components/form/*.js']),

// compile TypeScript to latest JavaScript, including Babel transpilation
typescript({
Expand Down
3 changes: 3 additions & 0 deletions ember-toucan-core/src/components/form/-private/control.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="mt-1" ...attributes>
{{yield}}
</div>
11 changes: 11 additions & 0 deletions ember-toucan-core/src/components/form/-private/control.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import templateOnlyComponent from '@ember/component/template-only';

export interface ToucanFormControlComponentSignature {
Element: HTMLDivElement;
Args: {};
Blocks: {
default: [];
};
}

export default templateOnlyComponent<ToucanFormControlComponentSignature>();
22 changes: 22 additions & 0 deletions ember-toucan-core/src/components/form/-private/error.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div class="type-xs-tight text-critical flex items-center mt-1" ...attributes>
{{! Icon taken from Tony's open source icon set, this is temporary! }}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
class="w-3 h-3 mr-1"
>
<path
d="M12 3a9 9 0 11-6.364 2.636A8.972 8.972 0 0112 3zm0 4.7v5.2m0 3.39v.01"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>

{{yield}}
</div>
11 changes: 11 additions & 0 deletions ember-toucan-core/src/components/form/-private/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import templateOnlyComponent from '@ember/component/template-only';

export interface ToucanFormErrorComponentSignature {
Element: HTMLDivElement;
Args: {};
Blocks: {
default: [];
};
}

export default templateOnlyComponent<ToucanFormErrorComponentSignature>();
1 change: 1 addition & 0 deletions ember-toucan-core/src/components/form/-private/hint.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="type-xs-tight text-body-and-labels" ...attributes>{{yield}}</div>
11 changes: 11 additions & 0 deletions ember-toucan-core/src/components/form/-private/hint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import templateOnlyComponent from '@ember/component/template-only';

export interface ToucanFormHintComponentSignature {
Element: HTMLDivElement;
Args: {};
Blocks: {
default: [];
};
}

export default templateOnlyComponent<ToucanFormHintComponentSignature>();
3 changes: 3 additions & 0 deletions ember-toucan-core/src/components/form/-private/label.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<label class="type-md-tight text-body-and-labels block" ...attributes>
{{yield}}
</label>
11 changes: 11 additions & 0 deletions ember-toucan-core/src/components/form/-private/label.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import templateOnlyComponent from '@ember/component/template-only';

export interface ToucanFormLabelComponentSignature {
Element: HTMLLabelElement;
Args: {};
Blocks: {
default: [];
};
}

export default templateOnlyComponent<ToucanFormLabelComponentSignature>();
13 changes: 13 additions & 0 deletions ember-toucan-core/src/components/form/field.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{#let (unique-id) (unique-id) (unique-id) as |uniqueId hintId errorId|}}
{{yield
(hash
Label=this.Label
Hint=this.Hint
Control=this.Control
Error=this.Error
id=uniqueId
hintId=hintId
errorId=errorId
)
}}
{{/let}}
51 changes: 51 additions & 0 deletions ember-toucan-core/src/components/form/field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Component from '@glimmer/component';

import Control from './-private/control';
import Error from './-private/error';
import Hint from './-private/hint';
import Label from './-private/label';

interface ToucanFormFieldComponentSignature {
Element: null;
Args: {};
Blocks: {
default: [
{
/**
* ID to link the Label and underlying control element for screenreaders.
*
* - Provide this to the `id` attribute on the control element.
* - Provide this to the `for` attribute on the Label component.
*/
id: string;
/**
* A provided ID for element descriptors. Normally used to link the
* hint section with the control so that it is read by screenreaders.
*
* - Provide this to the `id` attribute on the Hint component.
* - Add this to the `aria-describedby` of the control element.
*/
hintId: string;
/**
* A provided ID for element descriptors. Normally used to link the
* error section with the control so that it is read by screenreaders.
*
* - Provide this to the `id` attribute on the Error component.
* - Add this to the `aria-describedby` or `aria-errormessage` of the control element.
*/
errorId: string;
Label: typeof Label;
Hint: typeof Hint;
Control: typeof Control;
Error: typeof Error;
}
];
};
}

export default class ToucanFormFieldComponent extends Component<ToucanFormFieldComponentSignature> {
Label = Label;
Hint = Hint;
Control = Control;
Error = Error;
}
Loading