refactor: 💡 refactor checkbox to align with existing inputs
This commit is contained in:
16 files changed
+236
-210
No files matched your search
@@ -1,21 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../templates/components/rose/checkbox';
|
||||
|
||||
/**
|
||||
A simple ember input `<Input @type="checkbox">` element with associated `label`.
|
||||
|
||||
@example
|
||||
<Rose::Checkbox />
|
||||
<Rose::Checkbox @id="fancy">Label</Rose::Checkbox>
|
||||
<Rose::Checkbox @name={{define-me}}>Name</Rose::Checkbox>
|
||||
<Rose::Checkbox @form={{@form}}>Form</Rose::Checkbox>
|
||||
<Rose::Checkbox @value={{@value}}>Value</Rose::Checkbox>
|
||||
<Rose::Checkbox @error={{true}}>Error</Rose::Checkbox>
|
||||
<Rose::Checkbox @checked={{true}}>Checked</Rose::Checkbox>
|
||||
<Rose::Checkbox @disabled={{true}}>Disabled</Rose::Checkbox>
|
||||
<Rose::Checkbox @disabled={{true}} @checked={{true}}>Disabled + Checked</Rose::Checkbox>
|
||||
**/
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../../templates/components/rose/form/checkbox';
|
||||
|
||||
/**
|
||||
* A simple HTML `<input type="checkbox">` element.
|
||||
*
|
||||
* @example
|
||||
* <Rose::Form::Checkbox
|
||||
* @label="Label"
|
||||
* @id="fieldId"
|
||||
* name="fieldName"
|
||||
* disabled={{true}}
|
||||
* @error={{true}}
|
||||
* @checked={{true}}
|
||||
* />
|
||||
*/
|
||||
export default Component.extend({
|
||||
layout,
|
||||
|
||||
tagName: '',
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
@use '../variables/sizing';
|
||||
@use '../utilities/type' as typography;
|
||||
|
||||
$checkbox-check-height: sizing.rems(xs) - sizing.rems(xxxs);
|
||||
$checkbox-check-size: sizing.rems(xxxxs) + (sizing.rems(xxxs) / 3);
|
||||
$checkbox-check-left-position: sizing.rems(xxs) - (sizing.rems(xxs) / 3);
|
||||
|
||||
.rose-checkbox {
|
||||
@include typography.type(m);
|
||||
color: var(--stark);
|
||||
background-color: var(--subtle);
|
||||
|
||||
.rose-checkbox-input {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: sizing.rems(xxxxs) solid var(--ui-border);
|
||||
border-radius: sizing.rems(xxxs);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-color: var(--subtle);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: $checkbox-check-left-position;
|
||||
width: sizing.rems(xxxs);
|
||||
height: $checkbox-check-height;
|
||||
border: solid;
|
||||
border-width: 0 $checkbox-check-size $checkbox-check-size 0;
|
||||
border-color: var(--info);
|
||||
border-radius: sizing.rems(xxxxs);
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
border-color: var(--info);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
border-color: var(--ui-border);
|
||||
background-color: var(--ui-gray-subtler-4);
|
||||
|
||||
&::before {
|
||||
border-color: var(--ui-border);
|
||||
}
|
||||
}
|
||||
|
||||
&:checked::before {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--info);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--ui-gray);
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.rose-checkbox-input {
|
||||
border-color: var(--failure);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--info);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
border-color: var(--ui-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
@import 'button';
|
||||
@import 'form';
|
||||
@import 'checkbox';
|
||||
@@ -0,0 +1,122 @@
|
||||
@use '../../variables/sizing';
|
||||
@use '../../utilities/type';
|
||||
|
||||
.rose-form-checkbox {
|
||||
position: relative;
|
||||
margin-bottom: sizing.rems(xs);
|
||||
color: var(--ui-gray);
|
||||
|
||||
.rose-form-checkbox-label {
|
||||
// label styles
|
||||
@include type.type(s, semibold);
|
||||
color: currentColor;
|
||||
padding-left: sizing.rems(l);
|
||||
|
||||
// custom checkbox styles
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: sizing.rems(xxxs);
|
||||
left: 0;
|
||||
display: block;
|
||||
width: sizing.rems(m);
|
||||
height: sizing.rems(m);
|
||||
pointer-events: none;
|
||||
content: ' ';
|
||||
border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
|
||||
border-radius: sizing.rems(xxxs);
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: -#{sizing.rems(xxxxs)};
|
||||
left: -#{sizing.rems(xxxs) + sizing.rems(xxxxs)};
|
||||
display: block;
|
||||
width: sizing.rems(l);
|
||||
height: sizing.rems(l);
|
||||
transform: scale(0.9);
|
||||
pointer-events: none;
|
||||
content: ' ';
|
||||
background: no-repeat 50%/50% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.rose-form-checkbox-field {
|
||||
// mostly this just visually hides the native checkbox
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: sizing.rems(m);
|
||||
height: sizing.rems(m);
|
||||
opacity: 0;
|
||||
|
||||
// Visual states for custom checkbox.
|
||||
// Yeah. It's complicated.
|
||||
&:not(:disabled) {
|
||||
&:hover {
|
||||
~ .rose-form-checkbox-label {
|
||||
&::before {
|
||||
border-color: var(--ui-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
~ .rose-form-checkbox-label {
|
||||
&::before {
|
||||
border-color: var(--info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:checked {
|
||||
~ .rose-form-checkbox-label {
|
||||
&::before {
|
||||
border-color: var(--info);
|
||||
}
|
||||
&::after {
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 8 8'><path fill='%231563ff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/></svg>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
~ .rose-form-checkbox-label {
|
||||
&::before {
|
||||
border-color: var(--ui-gray-subtler-2);
|
||||
background-color: var(--ui-gray-subtler-4);
|
||||
}
|
||||
}
|
||||
|
||||
&:checked {
|
||||
~ .rose-form-checkbox-label {
|
||||
&::after {
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 8 8'><path fill='%23bac1cc' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/></svg>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: var(--failure);
|
||||
|
||||
.rose-form-checkbox-field {
|
||||
&:not(:disabled):not(:focus) {
|
||||
~ .rose-form-checkbox-label {
|
||||
&::before {
|
||||
border-color: var(--failure);
|
||||
}
|
||||
}
|
||||
|
||||
&:checked {
|
||||
~ .rose-form-checkbox-label {
|
||||
&::after {
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 8 8'><path fill='%23c73445' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/></svg>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
@import 'input';
|
||||
@import 'checkbox';
|
||||
@@ -23,7 +23,7 @@ $size-coefficients: (
|
||||
// 4
|
||||
xxxs: 0.125,
|
||||
// 2
|
||||
xxxxs: 0.0625 // 1,,,
|
||||
xxxxs: 0.0625 // 1,,,,
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,7 @@ $font-sizes: (
|
||||
// 20
|
||||
h3: sizing.size(m),
|
||||
// 16
|
||||
button: sizing.size(m) * 0.8125 // 13,,,
|
||||
button: sizing.size(m) * 0.8125 // 13,,,,
|
||||
);
|
||||
|
||||
// Line height expressed as ratio.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<div class="rose-checkbox{{if @error " error"}}">
|
||||
<Input @type="checkbox"
|
||||
id={{if @id (concat @id "-checkbox")}}
|
||||
...attributes
|
||||
name={{@name}}
|
||||
disabled={{@disabled}}
|
||||
form={{@form}}
|
||||
class="rose-checkbox-input"
|
||||
value={{@value}}
|
||||
@checked={{@checked}} />
|
||||
|
||||
<label class="rose-checkbox-label" for={{if @id (concat @id "-checkbox")}}>
|
||||
{{yield}}
|
||||
</label>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="rose-form-checkbox{{if @error " error"}}">
|
||||
|
||||
<Input
|
||||
...attributes
|
||||
class="rose-form-checkbox-field"
|
||||
id={{@id}}
|
||||
@type="checkbox"
|
||||
@checked={{if @checked true false}} />
|
||||
|
||||
<label class="rose-form-checkbox-label" for={{@id}}>
|
||||
{{@label}}
|
||||
</label>
|
||||
|
||||
{{yield}}
|
||||
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from 'rose/components/rose/checkbox';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/form/checkbox';
|
||||
@@ -64,8 +64,28 @@
|
||||
/>
|
||||
|
||||
<h2>Checkbox</h2>
|
||||
<Rose::Checkbox @id="default">Label</Rose::Checkbox>
|
||||
<Rose::Checkbox @error={{true}}>Error</Rose::Checkbox>
|
||||
<Rose::Checkbox @checked={{true}}>Checked</Rose::Checkbox>
|
||||
<Rose::Checkbox @disabled={{true}}>Disabled</Rose::Checkbox>
|
||||
<Rose::Checkbox @disabled={{true}} @checked={{true}}>Disabled + Checked</Rose::Checkbox>
|
||||
<Rose::Form::Checkbox
|
||||
@id="checkbox-1"
|
||||
@label="Label"
|
||||
/>
|
||||
<Rose::Form::Checkbox
|
||||
@id="checkbox-2"
|
||||
@error={{true}}
|
||||
@label="Error"
|
||||
/>
|
||||
<Rose::Form::Checkbox
|
||||
@id="checkbox-3"
|
||||
@checked={{true}}
|
||||
@label="Checked"
|
||||
/>
|
||||
<Rose::Form::Checkbox
|
||||
@id="checkbox-4"
|
||||
@label="Disabled"
|
||||
disabled={{true}}
|
||||
/>
|
||||
<Rose::Form::Checkbox
|
||||
@id="checkbox-5"
|
||||
@checked={{true}}
|
||||
@label="Disabled + Checked"
|
||||
disabled={{true}}
|
||||
/>
|
||||
+9
-12
@@ -2,29 +2,26 @@ import { hbs } from 'ember-cli-htmlbars';
|
||||
import { text, boolean } from '@storybook/addon-knobs';
|
||||
|
||||
export default {
|
||||
title: 'Rose::Checkbox',
|
||||
title: 'Rose::Form::Checkbox',
|
||||
};
|
||||
|
||||
export const Normal = () => ({
|
||||
template: hbs`
|
||||
<Rose::Checkbox
|
||||
<Rose::Form::Checkbox
|
||||
@id={{id}}
|
||||
@name={{name}}
|
||||
@form={{form}}
|
||||
name={{name}}
|
||||
@label={{label}}
|
||||
@checked={{checked}}
|
||||
@disabled={{disabled}}
|
||||
@error={{error}}
|
||||
>
|
||||
{{label}}
|
||||
</Rose::Checkbox>
|
||||
disabled={{disabled}}
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
label: text('text', 'Checkbox 🛎'),
|
||||
id: text('text', 'Identifier'),
|
||||
name: text('text', 'name'),
|
||||
form: text('text', 'form'),
|
||||
id: text('id', 'checkbox-1'),
|
||||
name: text('name', 'checkbox-1'),
|
||||
checked: boolean('checked', false),
|
||||
error: boolean('error', false),
|
||||
disabled: boolean('disabled', false),
|
||||
error: boolean('error', false)
|
||||
},
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | rose/checkbox', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders without label', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox />`);
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
});
|
||||
|
||||
test('it renders a checkbox', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox />`);
|
||||
assert.equal(this.element.querySelector('input').type, 'checkbox');
|
||||
});
|
||||
|
||||
test('it renders with label', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox>Label</Rose::Checkbox>`);
|
||||
assert.equal(this.element.textContent.trim(), 'Label');
|
||||
});
|
||||
|
||||
test('it renders in a div wrapper', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox />`);
|
||||
assert.ok(this.element.querySelector('div.rose-checkbox'));
|
||||
});
|
||||
|
||||
test('it renders a checkbox class', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox />`);
|
||||
assert.ok(this.element.querySelector('input.rose-checkbox-input'));
|
||||
});
|
||||
|
||||
test('it renders a label class', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox />`);
|
||||
assert.ok(this.element.querySelector('label.rose-checkbox-label'));
|
||||
});
|
||||
|
||||
test('it is not checked', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox />`);
|
||||
assert.equal(this.element.querySelector('input').checked, false);
|
||||
});
|
||||
|
||||
test('it is not disabled', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox />`);
|
||||
assert.equal(this.element.querySelector('input').disabled, false);
|
||||
});
|
||||
|
||||
test('it is checked when @checked={{true}}', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox @checked={{true}} />`);
|
||||
assert.equal(this.element.querySelector('input').checked, true);
|
||||
});
|
||||
|
||||
test('it marks error when @error={{true}}', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox @error={{true}} />`);
|
||||
assert.ok(this.element.querySelector('div.error'));
|
||||
});
|
||||
|
||||
test('it is disabled when @disabled={{true}}', async function (assert) {
|
||||
await render(hbs`<Rose::Checkbox @disabled={{true}} />`);
|
||||
assert.equal(this.element.querySelector('input').disabled, true);
|
||||
});
|
||||
|
||||
test('it is disabled and checked when @disabled={{true}} and @checked={{true}}', async function (assert) {
|
||||
await render(
|
||||
hbs`<Rose::Checkbox @disabled={{true}} @checked={{true}}></Rose::Checkbox>`
|
||||
);
|
||||
assert.equal(this.element.querySelector('input').checked, true);
|
||||
assert.equal(this.element.querySelector('input').disabled, true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, find } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | rose/form/checkbox', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Checkbox @label="Label" />`);
|
||||
assert.equal(await find('label').textContent.trim(), 'Label');
|
||||
assert.ok(await find('input'));
|
||||
});
|
||||
|
||||
test('it is not checked by default', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Checkbox />`);
|
||||
assert.equal(await find('input').checked, false);
|
||||
});
|
||||
|
||||
test('it is not disabled by default', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Checkbox />`);
|
||||
assert.equal(await find('input').disabled, false);
|
||||
});
|
||||
|
||||
test('it is checked when @checked={{true}}', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Checkbox @checked={{true}} />`);
|
||||
assert.equal(await find('input').checked, true);
|
||||
});
|
||||
|
||||
test('it marks error when @error={{true}}', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Checkbox @error={{true}} />`);
|
||||
assert.ok(await find('.error'));
|
||||
});
|
||||
|
||||
test('it is disabled when disabled={{true}}', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Checkbox disabled={{true}} />`);
|
||||
assert.equal(await find('input').disabled, true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user