diff --git a/addons/rose/addon/components/rose/form/checkbox.js b/addons/rose/addon/components/rose/form/checkbox.js new file mode 100644 index 000000000..5b94c0a1d --- /dev/null +++ b/addons/rose/addon/components/rose/form/checkbox.js @@ -0,0 +1,21 @@ +import Component from '@ember/component'; +import layout from '../../../templates/components/rose/form/checkbox'; + +/** + * A simple HTML `` element. + * + * @example + * + */ +export default Component.extend({ + layout, + + tagName: '', +}); diff --git a/addons/rose/addon/styles/rose/components/form/_checkbox.scss b/addons/rose/addon/styles/rose/components/form/_checkbox.scss new file mode 100644 index 000000000..4b826841e --- /dev/null +++ b/addons/rose/addon/styles/rose/components/form/_checkbox.scss @@ -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,"); + } + } + } + } + + &: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,"); + } + } + } + } + } + + &.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,"); + } + } + } + } + } + } +} diff --git a/addons/rose/addon/styles/rose/components/form/_index.scss b/addons/rose/addon/styles/rose/components/form/_index.scss index fdc60b1cd..55ce75517 100644 --- a/addons/rose/addon/styles/rose/components/form/_index.scss +++ b/addons/rose/addon/styles/rose/components/form/_index.scss @@ -1 +1,2 @@ @import 'input'; +@import 'checkbox'; diff --git a/addons/rose/addon/styles/rose/variables/_sizing.scss b/addons/rose/addon/styles/rose/variables/_sizing.scss index 20fed7233..81b9c94e9 100644 --- a/addons/rose/addon/styles/rose/variables/_sizing.scss +++ b/addons/rose/addon/styles/rose/variables/_sizing.scss @@ -23,7 +23,8 @@ $size-coefficients: ( // 4 xxxs: 0.125, // 2 - xxxxs: 0.0625 // 1,,,,, + xxxxs: 0.0625, + // 1 ); /** diff --git a/addons/rose/addon/styles/rose/variables/_typography.scss b/addons/rose/addon/styles/rose/variables/_typography.scss index 71111ac26..067c74496 100644 --- a/addons/rose/addon/styles/rose/variables/_typography.scss +++ b/addons/rose/addon/styles/rose/variables/_typography.scss @@ -40,7 +40,8 @@ $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. diff --git a/addons/rose/addon/templates/components/rose/form/checkbox.hbs b/addons/rose/addon/templates/components/rose/form/checkbox.hbs new file mode 100644 index 000000000..ab2dec73b --- /dev/null +++ b/addons/rose/addon/templates/components/rose/form/checkbox.hbs @@ -0,0 +1,16 @@ +
+ + + + + + {{yield}} + +
diff --git a/addons/rose/app/components/rose/form/checkbox.js b/addons/rose/app/components/rose/form/checkbox.js new file mode 100644 index 000000000..fecc9b373 --- /dev/null +++ b/addons/rose/app/components/rose/form/checkbox.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/form/checkbox'; diff --git a/addons/rose/app/templates/index.hbs b/addons/rose/app/templates/index.hbs index af9c16fd8..4b4e197a0 100644 --- a/addons/rose/app/templates/index.hbs +++ b/addons/rose/app/templates/index.hbs @@ -1,7 +1,9 @@ Dummy app index template - +

Icon

+ +

Button

Primary
Primary @@ -12,7 +14,7 @@ Dummy app index template Secondary
- +

Form

+ +

Checkbox

+ + + + + diff --git a/addons/rose/stories/form/checkbox.stories.js b/addons/rose/stories/form/checkbox.stories.js new file mode 100644 index 000000000..2292dd816 --- /dev/null +++ b/addons/rose/stories/form/checkbox.stories.js @@ -0,0 +1,27 @@ +import { hbs } from 'ember-cli-htmlbars'; +import { text, boolean } from '@storybook/addon-knobs'; + +export default { + title: 'Rose::Form::Checkbox', +}; + +export const Normal = () => ({ + template: hbs` + + `, + context: { + label: text('text', 'Checkbox 🛎'), + id: text('id', 'checkbox-1'), + name: text('name', 'checkbox-1'), + checked: boolean('checked', false), + error: boolean('error', false), + disabled: boolean('disabled', false), + }, +}); diff --git a/addons/rose/tests/integration/components/rose/form/checkbox-test.js b/addons/rose/tests/integration/components/rose/form/checkbox-test.js new file mode 100644 index 000000000..e341dd0db --- /dev/null +++ b/addons/rose/tests/integration/components/rose/form/checkbox-test.js @@ -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``); + 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``); + assert.equal(await find('input').checked, false); + }); + + test('it is not disabled by default', async function (assert) { + await render(hbs``); + assert.equal(await find('input').disabled, false); + }); + + test('it is checked when @checked={{true}}', async function (assert) { + await render(hbs``); + assert.equal(await find('input').checked, true); + }); + + test('it marks error when @error={{true}}', async function (assert) { + await render(hbs``); + assert.ok(await find('.error')); + }); + + test('it is disabled when disabled={{true}}', async function (assert) { + await render(hbs``); + assert.equal(await find('input').disabled, true); + }); +});