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

Add lock icon for disabled and readonly states #189

Merged
merged 18 commits into from
Jun 27, 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
6 changes: 6 additions & 0 deletions .changeset/fluffy-cameras-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@crowdstrike/ember-toucan-core': patch
'@crowdstrike/ember-toucan-form': patch
---

Add a lock icon to readonly and disabled states for all form components.
2 changes: 1 addition & 1 deletion docs/components/checkbox-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Target the error block via `data-error`.
<div class='mb-4 w-64'>
<Form::Fields::Checkbox
>
<:label>Label <svg class="inline" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:label>Label <svg class="inline w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:hint>Select <a href="https://www.crowdstrike.com/">link</a></:hint>
</Form::Fields::Checkbox>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/checkbox-group-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ Consumers have direct access to the underlying [checkbox element](https://develo
<group.CheckboxField @label='Option 2' @value='option-2' />
<group.CheckboxField @label='Option 3' @value='option-3' />
</:default>
<:label>Label <svg class="inline" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:label>Label <svg class="inline w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:hint>Select an option <a href="https://www.crowdstrike.com/">link</a></:hint>
</Form::Fields::CheckboxGroup>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/file-input-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ Target the trash icon button via `data-delete-file`.
@deleteLabel='Delete file'
@trigger='Browse Files'
>
<:label>Label <svg class="inline" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:label>Label <svg class="inline w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:hint>Hint text <a href="https://www.crowdstrike.com/">link</a></:hint>
</Form::Fields::FileInput>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/input-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ Target the error block via `data-error`.
<div class='mb-4 w-64'>
<Form::Fields::Input
>
<:label>Label <svg class="inline" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:label>Label <svg class="inline w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:hint>Hint text <a href="https://www.crowdstrike.com/">link</a></:hint>
</Form::Fields::Input>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/radio-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Target the hint block via `data-hint`.
@name='options-b'
@value='option-1'
>
<:label>Label <svg class="inline" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:label>Label <svg class="inline w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:hint>Hint text <a href="https://www.crowdstrike.com/">link</a></:hint>
</Form::Fields::Radio>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/components/radio-group-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ Consumers have direct access to the underlying [radio element](https://developer
<group.RadioField @label='Option 2' @value='option-2' />
<group.RadioField @label='Option 3' @value='option-3' />
</:default>
<:label>Label <svg class="inline" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:label>Label <svg class="inline w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:hint>Select an option <a href="https://www.crowdstrike.com/">link</a></:hint>
</Form::Fields::RadioGroup>
</div>
Expand Down
8 changes: 4 additions & 4 deletions docs/components/textarea-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Target the error block via `data-error`.
<div class='mb-4 w-64'>
<Form::Fields::Textarea
>
<:label>Label <svg class="inline" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:label>Label <svg class="inline w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><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"></path></svg></:label>
<:hint>Select an option <a href="https://www.crowdstrike.com/">link</a></:hint>
</Form::Fields::Textarea>
</div>
Expand Down Expand Up @@ -242,7 +242,7 @@ Target the error block via `data-error`.
/>
</div>

### TextareaField with character count
### TextareaField with character count

<div class='mb-4 w-64'>
<Form::Fields::Textarea
Expand All @@ -256,7 +256,7 @@ Target the error block via `data-error`.
</Form::Fields::Textarea>
</div>

### TextareaField with character count with a single error
### TextareaField with character count with a single error

<div class='mb-4 w-64'>
<Form::Fields::Textarea
Expand All @@ -271,7 +271,7 @@ Target the error block via `data-error`.
</Form::Fields::Textarea>
</div>

### TextareaField with character count with multiple errors
### TextareaField with character count with multiple errors

<div class='mb-4 w-64'>
<Form::Fields::Textarea
Expand Down
19 changes: 19 additions & 0 deletions packages/ember-toucan-core/src/-private/components/lock-icon.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="currentColor"
aria-hidden="true"
class="text-disabled"
data-lock-icon
...attributes
>
<g>
<path d="M5.5 6v3h1V6h-1Z" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3 3.5V3a3 3 0 0 1 6 0v.5h.5A1.5 1.5 0 0 1 11 5v5a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 1 10V5a1.5 1.5 0 0 1 1.5-1.5H3ZM4 3a2 2 0 1 1 4 0v.5H4V3ZM2 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5h-7A.5.5 0 0 1 2 10V5Z"
/>
</g>
</svg>
11 changes: 11 additions & 0 deletions packages/ember-toucan-core/src/-private/components/lock-icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import templateOnlyComponent from '@ember/component/template-only';

export interface ToucanLockIconComponentSignature {
Element: SVGElement;
Args: {};
Blocks: {
Copy link
Contributor

Choose a reason for hiding this comment

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

No way to get the type system to fail to check if the consumer passes a block, eh?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could be missing something somewhere, but I tried Blocks: never and glint still passed

Copy link
Contributor

Choose a reason for hiding this comment

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

Womp

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it has to be Blocks: { default: never }?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah! you were right! TIL! Thanks a bunch, @nicolechung !

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm getting this with Blocks: { default: never }:

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

The error seems to be coming from our use of never rather than the fact that the consumer is passing a default block. I think the error we'd want to see would read something like "Argument of type [...] is not assignable to parameter of type 'never'".

What's less bad? Not producing a typechecking error when we should be or producing a wrong and possibly confusing error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Talked over Zoom - we reverted!

Copy link
Contributor

Choose a reason for hiding this comment

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

Screenshot 2023-06-26 at 2 53 41 PM

According to the RFC it says if you have no blocks then it might work, but I'm not sure if this has been implemented.

https://rfcs.emberjs.com/id/0748-glimmer-component-signature/#blocks

default: [];
};
}

export default templateOnlyComponent<ToucanLockIconComponentSignature>();
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@
)
)
}}
<legend class="type-md-tight text-body-and-labels block" data-label>
<legend
class="type-md-tight text-body-and-labels flex items-center gap-1.5"
data-label
>
{{#if (has-block "label")}}
{{yield to="label"}}
{{else}}
{{@label}}
{{/if}}

{{#if this.isReadOnlyOrDisabled}}
<this.LockIcon />
{{/if}}
</legend>
{{/if}}

Expand Down Expand Up @@ -52,6 +59,7 @@
isDisabled=@isDisabled
isReadOnly=@isReadOnly
selectedValues=@value
isGrouped=true
)
)
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Component from '@glimmer/component';
import { action } from '@ember/object';

import assertBlockOrArgumentExists from '../../../-private/assert-block-or-argument-exists';
import LockIcon from '../../../-private/components/lock-icon';
import CheckboxFieldComponent from './checkbox';

import type { AssertBlockOrArg } from '../../../-private/assert-block-or-argument-exists';
Expand Down Expand Up @@ -74,6 +75,7 @@ export interface ToucanFormCheckboxGroupFieldComponentSignature {

export default class ToucanFormCheckboxGroupFieldComponent extends Component<ToucanFormCheckboxGroupFieldComponentSignature> {
CheckboxFieldComponent = CheckboxFieldComponent;
LockIcon = LockIcon;

assertBlockOrArgumentExists = ({
blockExists,
Expand All @@ -83,6 +85,10 @@ export default class ToucanFormCheckboxGroupFieldComponent extends Component<Tou
}: AssertBlockOrArg) =>
assertBlockOrArgumentExists({ blockExists, argName, arg, isRequired });

get isReadOnlyOrDisabled() {
return this.args?.isDisabled || this.args?.isReadOnly;
}

@action
handleInput(_: boolean, e: Event | InputEvent): void {
let value = this.args.value ? [...this.args.value] : [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
)
}}
<span
class="type-md-tight block leading-4
class="type-md-tight flex items-center gap-1.5 leading-4
{{if @isDisabled 'text-disabled' 'text-titles-and-attributes'}}"
data-label
>
Expand All @@ -46,6 +46,10 @@
{{else}}
{{@label}}
{{/if}}

{{#if this.isDisabledOrDisabledAndNotInAGroup}}
<this.LockIcon />
{{/if}}
</span>
{{/if}}

Expand Down
26 changes: 26 additions & 0 deletions packages/ember-toucan-core/src/components/form/fields/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Component from '@glimmer/component';
import { assert } from '@ember/debug';

import assertBlockOrArgumentExists from '../../../-private/assert-block-or-argument-exists';
import LockIcon from '../../../-private/components/lock-icon';

import type { AssertBlockOrArg } from '../../../-private/assert-block-or-argument-exists';
import type { ErrorMessage } from '../../../-private/types';
Expand Down Expand Up @@ -42,6 +43,14 @@ export interface ToucanFormCheckboxFieldComponentSignature {
*/
isIndeterminate?: ToucanFormCheckboxControlComponentSignature['Args']['isIndeterminate'];

/**
* Helps us determine if we are in a checkbox-group or not. This should only be applied internally
* when using CheckboxGroup.
*
* @internal
*/
isGrouped?: boolean;

/**
* Provide a string to this argument to render inside of the label tag.
*/
Expand Down Expand Up @@ -88,6 +97,8 @@ export interface ToucanFormCheckboxFieldComponentSignature {
}

export default class ToucanFormCheckboxFieldComponent extends Component<ToucanFormCheckboxFieldComponentSignature> {
LockIcon = LockIcon;

assertBlockOrArgumentExists = ({
blockExists,
argName,
Expand Down Expand Up @@ -119,4 +130,19 @@ export default class ToucanFormCheckboxFieldComponent extends Component<ToucanFo

return this.args.selectedValues?.includes(this.args.value);
}

/**
* We want to add a lock icon when a checkbox-field is used by itself and is disabled
* or readonly, but we do *not* want that icon rendered when we are inside of a
* checkbox group.
*/
get isDisabledOrDisabledAndNotInAGroup() {
let { isDisabled, isGrouped, isReadOnly } = this.args;

if (isGrouped) {
return false;
}

return isDisabled || isReadOnly;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
)
)
}}
<span data-label>
<span class="flex items-center gap-1.5" data-label>
{{#if (has-block "label")}}
{{yield to="label"}}
{{else}}
{{@label}}
{{/if}}

{{#if this.isReadOnlyOrDisabled}}
<this.LockIcon />
{{/if}}
</span>
{{/if}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { assert } from '@ember/debug';
import { action } from '@ember/object';

import assertBlockOrArgumentExists from '../../../-private/assert-block-or-argument-exists';
import LockIcon from '../../../-private/components/lock-icon';

import type { AssertBlockOrArg } from '../../../-private/assert-block-or-argument-exists';
import type { ErrorMessage } from '../../../-private/types';
Expand Down Expand Up @@ -81,6 +82,8 @@ export interface ToucanFormFileInputFieldComponentSignature {
}

export default class ToucanFormFileInputFieldComponent extends Component<ToucanFormFileInputFieldComponentSignature> {
LockIcon = LockIcon;

assertBlockOrArgumentExists = ({
blockExists,
argName,
Expand Down Expand Up @@ -110,6 +113,10 @@ export default class ToucanFormFileInputFieldComponent extends Component<ToucanF
return Boolean(this.args?.error);
}

get isReadOnlyOrDisabled() {
return this.args?.isDisabled || this.args?.isReadOnly;
}

@action
handleChange(field: { id: string }) {
if (this.args.isReadOnly) {
Expand Down
10 changes: 9 additions & 1 deletion packages/ember-toucan-core/src/components/form/fields/input.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,20 @@
)
)
}}
<field.Label for={{field.id}} data-label>
<field.Label
class="flex items-center gap-1.5"
for={{field.id}}
data-label
>
{{#if (has-block "label")}}
{{yield to="label"}}
{{else}}
{{@label}}
{{/if}}

{{#if this.isReadOnlyOrDisabled}}
<this.LockIcon />
{{/if}}
</field.Label>
{{/if}}

Expand Down
14 changes: 10 additions & 4 deletions packages/ember-toucan-core/src/components/form/fields/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { assert } from '@ember/debug';
import { action } from '@ember/object';

import assertBlockOrArgumentExists from '../../../-private/assert-block-or-argument-exists';
import LockIcon from '../../../-private/components/lock-icon';
import CharacterCount from '../../../components/form/controls/character-count';

import type { AssertBlockOrArg } from '../../../-private/assert-block-or-argument-exists';
Expand Down Expand Up @@ -69,6 +70,7 @@ export default class ToucanFormInputFieldComponent extends Component<ToucanFormI
@tracked count = this.args.value?.length ?? 0;

CharacterCount = CharacterCount;
LockIcon = LockIcon;

assertBlockOrArgumentExists = ({
blockExists,
Expand All @@ -78,6 +80,14 @@ export default class ToucanFormInputFieldComponent extends Component<ToucanFormI
}: AssertBlockOrArg) =>
assertBlockOrArgumentExists({ blockExists, argName, arg, isRequired });

get hasError() {
return Boolean(this.args?.error);
}

get isReadOnlyOrDisabled() {
return this.args?.isDisabled || this.args?.isReadOnly;
}

@action
handleCount(event: Event | InputEvent): void {
assert(
Expand All @@ -87,8 +97,4 @@ export default class ToucanFormInputFieldComponent extends Component<ToucanFormI

this.count = event.target?.value.length ?? 0;
}

get hasError() {
return Boolean(this.args?.error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@
)
)
}}
<legend class="type-md-tight text-body-and-labels block" data-label>
<legend
class="type-md-tight text-body-and-labels flex items-center gap-1.5"
data-label
>
{{#if (has-block "label")}}
{{yield to="label"}}
{{else}}
{{@label}}
{{/if}}

{{#if this.isReadOnlyOrDisabled}}
<this.LockIcon />
{{/if}}
</legend>
{{/if}}

Expand Down
Loading