Skip to content

Commit

Permalink
feat: CrowdStrike/30-create-input-field
Browse files Browse the repository at this point in the history
 Adds Form::InputField component (in tests it's InputField)
 Adds tests for Form::InputField
 Adds docs
  • Loading branch information
nicolechung authored Mar 6, 2023
2 parents 1f9b444 + 9c05643 commit ff0489b
Showing 13 changed files with 576 additions and 4 deletions.
22 changes: 22 additions & 0 deletions docs/components/input-field/demo/base-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
```hbs template
<Form::InputField
@label="Label"
@hint="extra information about the field"
type="text"
@value="hello i am an input field"
@onChange={{this.handleChange}}
/>
```

```js component
import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class extends Component {

@action
handleChange(value, event) {
console.log({ value, event });
}
}
```
120 changes: 120 additions & 0 deletions docs/components/input-field/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Input field

Provides an underlying `<input>` element building on top of the Field component.

## Label

Provide a string to `@label` to render the text into the `<label>` of the Field.

## Hint

Provide a string to `@hint` to render the text into the Hint section of the Field. This is optional.

## Error

Provide a string to `@error` to render the text into the Error section of the Field. This is optional.

## Value and onChange

To tie into the input event, provide `@onChange`. `@onChange` will return two arguments, the first being the value, while the second being the raw event object. It's most common to use this in combination with `@value` which will set the value for the input based on the input received from the change event.

```hbs
<Form::InputField
@label='Label'
@value={{this.value}}
@onChange={{this.handleChange}}
/>
```

```js
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class extends Component {
@tracked value;
@action
handleChange(value, e) {
console.log({ e, value });
this.value = value;
}
}
```

## Disabled State

Set the `@isDisabled` argument to disable the `<input>`.

## Attributes and Modifiers

Consumers have direct access to the underlying [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input), so all attributes are supported. Modifiers can also be added directly to the input as shown in the demo.

## Test Selectors

### Root Element

Provide a custom selector via `@rootTestSelector`. This test selector will be used as the value for the `data-root-field` attribute. The Field can be targeted via:

```hbs
<Form::InputField @label='Label' @rootTestSelector='example' />
```

```js
assert.dom('[data-root-field="example"]');
// targeting this field's specific label
assert.dom('[data-root-field="example"] > [data-label]');
```

### Label

Target the label element via `data-label`.

### Hint

Target the hint block via `data-hint`.

### Error

Target the error block via `data-error`.

## UI States

### InputField with Label
<div class="mb-4 w-64">
<Form::InputField
@label="Label"
type="text"
/>
</div>

### InputField with Label and hint
<div class="mb-4 w-64">
<Form::InputField
@label="Label"
@hint="With hint text"
type="text"
/>
</div>

### InputField with Label and error

<div class="mb-4 w-64">
<Form::InputField
@label="Label"
type="text"
@error="With error text"
/>
</div>

### InputField with Label and isDisabled

<div class="mb-4 w-64">
<Form::InputField
@label="Label"
type="text"
@isDisabled={{true}}
value="I am a disabled input field"
/>
</div>



9 changes: 9 additions & 0 deletions docs/components/input/demo/base-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
```hbs template
<Form::Controls::Input />
```

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

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

Provides a Toucan-styled [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). If you are building forms, you may be interested in the InputField component instead.

## Value

To set the `value` attribute of the `<input>`, provide `@value`.

```hbs
<Form::Controls::Input @value='value' />
```

## onChange

To tie into the input event, provide `@onChange`. `@onChange` will return two arguments, the first being the value, while the second being the raw event.

```hbs
<Form::Controls::Input
@value={{this.value}}
@onChange={{this.handleChange}}
/>
```

```js
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class extends Component {
@tracked value;
@action
handleChange(value, e) {
console.log({ e, value });
this.value = value;
}
}
```

## Disabled State

Set the `@isDisabled` argument to disable the `<input>`.

## Error State
Set the `@hasError` argument to apply an error box shadow to the `<input>`.
2 changes: 2 additions & 0 deletions ember-toucan-core/package.json
Original file line number Diff line number Diff line change
@@ -117,9 +117,11 @@
"./components/button.js": "./dist/_app_/components/button.js",
"./components/form/checkbox-field.js": "./dist/_app_/components/form/checkbox-field.js",
"./components/form/controls/checkbox.js": "./dist/_app_/components/form/controls/checkbox.js",
"./components/form/controls/input.js": "./dist/_app_/components/form/controls/input.js",
"./components/form/controls/textarea.js": "./dist/_app_/components/form/controls/textarea.js",
"./components/form/field.js": "./dist/_app_/components/form/field.js",
"./components/form/fieldset.js": "./dist/_app_/components/form/fieldset.js",
"./components/form/input-field.js": "./dist/_app_/components/form/input-field.js",
"./components/form/textarea-field.js": "./dist/_app_/components/form/textarea-field.js"
}
},
9 changes: 9 additions & 0 deletions ember-toucan-core/src/components/form/controls/input.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<input
class="bg-overlay-1 focus:outline-none focus:shadow-focus-outline block rounded-sm p-1
{{if @isDisabled 'text-disabled' 'text-titles-and-attributes'}}
{{if @hasError 'shadow-error-outline' 'shadow-focusable-outline'}}"
disabled={{@isDisabled}}
value={{@value}}
...attributes
{{on "input" this.handleInput}}
/>
24 changes: 24 additions & 0 deletions ember-toucan-core/src/components/form/controls/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Component from '@glimmer/component';
import { assert } from '@ember/debug';
import { action } from '@ember/object';

import type { OnChangeCallback } from '../../../-private/types';

interface ToucanFormControlsInputComponentSignature {
Element: HTMLInputElement;
Args: {
hasError?: boolean;
isDisabled?: boolean;
onChange?: OnChangeCallback<string>;
value?: string;
};
}

export default class ToucanFormControlsInputComponent extends Component<ToucanFormControlsInputComponentSignature> {
@action
handleInput(e: Event | InputEvent): void {
assert('Expected HTMLInputElement', e.target instanceof HTMLInputElement);

this.args.onChange?.(e.target.value, e);
}
}
27 changes: 27 additions & 0 deletions ember-toucan-core/src/components/form/input-field.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<div
class="flex flex-col {{if @isDisabled 'text-disabled'}}"
data-root-field={{if @rootTestSelector @rootTestSelector null}}
>
<Form::Field as |field|>
<field.Label for={{field.id}} data-label>{{@label}}</field.Label>

{{#if @hint}}
<field.Hint id={{field.hintId}} data-hint>{{@hint}}</field.Hint>
{{/if}}

<field.Control class="flex">
<Form::Controls::Input
id={{field.id}}
aria-describedby="{{if @hint field.hintId}} {{if @error field.errorId}}"
aria-invalid={{if @error "true"}}
@isDisabled={{@isDisabled}}
@value={{@value}}
@onChange={{@onChange}}
...attributes
/>
</field.Control>
{{#if @error}}
<field.Error id={{field.errorId}} data-error>{{@error}}</field.Error>
{{/if}}
</Form::Field>
</div>
30 changes: 30 additions & 0 deletions ember-toucan-core/src/components/form/input-field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Component from '@glimmer/component';
import { assert } from '@ember/debug';

import type { OnChangeCallback } from '../../-private/types';

interface ToucanFormInputFieldComponentSignature {
Element: HTMLInputElement;
Args: {
error?: string;
label: string;
hint?: string;
isDisabled?: boolean;
onChange?: OnChangeCallback<string>;
rootTestSelector?: string;
value?: string;
};
Blocks: {
default: [];
};
}

export default class ToucanFormInputFieldComponent extends Component<ToucanFormInputFieldComponentSignature> {
constructor(
owner: unknown,
args: ToucanFormInputFieldComponentSignature['Args']
) {
assert('input field requires a label', args.label !== undefined);
super(owner, args);
}
}
10 changes: 7 additions & 3 deletions ember-toucan-core/src/template-registry.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import type ButtonComponent from './components/button';
import type CheckboxFieldComponent from './components/form/checkbox-field';
import type CheckboxConrolComponent from './components/form/controls/checkbox';
import type CheckboxControlComponent from './components/form/controls/checkbox';
import type InputControlComponent from './components/form/controls/input';
import type TextareaControlComponent from './components/form/controls/textarea';
import type FieldComponent from './components/form/field';
import type FieldsetComponent from './components/form/fieldset';
import type InputFieldComponent from './components/form/input-field';
import type TextareaFieldComponent from './components/form/textarea-field';

export default interface Registry {
Button: typeof ButtonComponent;
'Form::Field': typeof FieldComponent;
'Form::Fieldset': typeof FieldsetComponent;
'Form::CheckboxField': typeof CheckboxFieldComponent;
'Form::TextareaField': typeof TextareaFieldComponent;
'Form::Controls::Checkbox': typeof CheckboxConrolComponent;
'Form::InputField': typeof InputFieldComponent;
'Form::Controls::Checkbox': typeof CheckboxControlComponent;
'Form::Controls::Input': typeof InputControlComponent;
'Form::Controls::Textarea': typeof TextareaControlComponent;
'Form::TextareaField': typeof TextareaFieldComponent;
}
35 changes: 34 additions & 1 deletion test-app/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
<h2 id="title">Welcome to Ember</h2>

{{outlet}}
{{! template-lint-disable require-input-label }}
<Input />
{{! template-lint-disable require-input-label }}
<Input hasError={{true}} />
<Input isDisabled={{true}} />

<Form::InputField @label="Label" @hint="hint text" type="text" />

<Form::InputField @label="Label" type="text" @error="There is an error" />
<Form::InputField @label="Label" type="text" @isDisabled={{true}} />

<Textarea />
<Textarea hasError={{true}} />
<Textarea isDisabled={{true}} />

<Form::TextareaField @label="Label" @hint="Hint text" />
<Form::TextareaField @label="Label" @hint="Hint text" @error="Error text" />
<Form::TextareaField
@label="Label"
@hint="Hint text"
@error="Error text"
isDisabled={{true}}
/>

<Form::Field as |field|>
<field.Label>label</field.Label>
<field.Hint>hint</field.Hint>
{{! we'll handle the wiring of the label }}
{{! template-lint-disable require-input-label }}
<field.Control>
<input type="text" />
</field.Control>
<field.Error>error</field.Error>
</Form::Field>
Loading

0 comments on commit ff0489b

Please sign in to comment.