chore: 🤖 build out input/text field component
This commit is contained in:
13 files changed
+290
-77
No files matched your search
@@ -0,0 +1,19 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../../templates/components/rose/form/input';
|
||||
|
||||
/**
|
||||
* A simple HTML `<button>` element.
|
||||
*
|
||||
* @example
|
||||
* <Rose::Form::Input
|
||||
* @value="Text"
|
||||
* @label="Label"
|
||||
* @helperText="Helper text"
|
||||
* @error={{true}}
|
||||
* disabled={{true}}
|
||||
* readonly={{true}}
|
||||
* />
|
||||
*/
|
||||
export default Component.extend({
|
||||
layout,
|
||||
});
|
||||
@@ -2,10 +2,10 @@
|
||||
@use '../utilities/type' as typography;
|
||||
@use '../variables/sizing';
|
||||
|
||||
$size-xs: sizing.rems(xs); // 8
|
||||
$size-xxs: sizing.rems(xxs); // 4
|
||||
$size-xxxs: sizing.rems(xxxs); // 2
|
||||
$size-xxxxs: sizing.rems(xxxxs); // 1
|
||||
$size-xs: sizing.rems(xs); // 8
|
||||
$size-xxs: sizing.rems(xxs); // 4
|
||||
$size-xxxs: sizing.rems(xxxs); // 2
|
||||
$size-xxxxs: sizing.rems(xxxxs); // 1
|
||||
|
||||
@include button.button-style(
|
||||
'primary',
|
||||
@@ -13,8 +13,10 @@ $size-xxxxs: sizing.rems(xxxxs); // 1
|
||||
color: var(--white),
|
||||
background-color: var(--info),
|
||||
border-color: var(--info-starker-2),
|
||||
box-shadow: 0 $size-xxs $size-xxs rgba(var(--stark-components), var(--opacity-2)),
|
||||
text-shadow: 0 $size-xxxxs $size-xxxxs rgba(var(--black-components), var(--opacity-2)),
|
||||
box-shadow: 0 $size-xxs $size-xxs
|
||||
rgba(var(--stark-components), var(--opacity-2)),
|
||||
text-shadow: 0 $size-xxxxs $size-xxxxs
|
||||
rgba(var(--black-components), var(--opacity-2)),
|
||||
),
|
||||
(
|
||||
background-color: var(--info-subtler-1),
|
||||
@@ -39,7 +41,8 @@ $size-xxxxs: sizing.rems(xxxxs); // 1
|
||||
color: var(--ui-gray-starker-3),
|
||||
background-color: var(--ui-gray-subtler-5),
|
||||
border-color: var(--ui-gray-subtler-2),
|
||||
box-shadow: 0 $size-xxs $size-xxs rgba(var(--stark-components), var(--opacity-2)),
|
||||
box-shadow: 0 $size-xxs $size-xxs
|
||||
rgba(var(--stark-components), var(--opacity-2)),
|
||||
),
|
||||
(
|
||||
color: var(--ui-gray-starker-4),
|
||||
@@ -68,7 +71,8 @@ $size-xxxxs: sizing.rems(xxxxs); // 1
|
||||
color: var(--failure),
|
||||
background-color: var(--subtle),
|
||||
border-color: var(--failure),
|
||||
box-shadow: 0 $size-xxs $size-xxs rgba(var(--stark-components), var(--opacity-2)),
|
||||
box-shadow: 0 $size-xxs $size-xxs
|
||||
rgba(var(--stark-components), var(--opacity-2)),
|
||||
),
|
||||
(
|
||||
color: var(--white),
|
||||
@@ -78,7 +82,8 @@ $size-xxxxs: sizing.rems(xxxxs); // 1
|
||||
0 $size-xxs $size-xxs rgba(var(--stark-components), var(--opacity-3)),
|
||||
0 $size-xxs $size-xs rgba(var(--stark-components), var(--opacity-4)),
|
||||
),
|
||||
text-shadow: 0 $size-xxxxs $size-xxxxs rgba(var(--black-components), var(--opacity-2)),
|
||||
text-shadow: 0 $size-xxxxs $size-xxxxs
|
||||
rgba(var(--black-components), var(--opacity-2)),
|
||||
),
|
||||
(
|
||||
color: var(--white),
|
||||
@@ -88,7 +93,8 @@ $size-xxxxs: sizing.rems(xxxxs); // 1
|
||||
0 $size-xxs $size-xxs rgba(var(--stark-components), var(--opacity-3)),
|
||||
0 $size-xxs $size-xs rgba(var(--stark-components), var(--opacity-4)),
|
||||
),
|
||||
text-shadow: 0 $size-xxxxs $size-xxxxs rgba(var(--black-components), var(--opacity-2)),
|
||||
text-shadow: 0 $size-xxxxs $size-xxxxs
|
||||
rgba(var(--black-components), var(--opacity-2)),
|
||||
),
|
||||
(
|
||||
border-color: var(--ui-gray-subtler-2),
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
@import 'button';
|
||||
@import 'form';
|
||||
@@ -0,0 +1 @@
|
||||
@import 'input';
|
||||
@@ -0,0 +1,63 @@
|
||||
@use '../../variables/sizing';
|
||||
@use '../../utilities/type';
|
||||
|
||||
.rose-form-input {
|
||||
margin-bottom: sizing.rems(m);
|
||||
|
||||
.rose-form-input-label {
|
||||
@include type.type(s, semibold);
|
||||
display: block;
|
||||
padding-bottom: sizing.rems(xxs);
|
||||
}
|
||||
|
||||
.rose-form-input-helper-text {
|
||||
@include type.type(xs);
|
||||
display: block;
|
||||
color: var(--ui-gray);
|
||||
padding-bottom: sizing.rems(xxs);
|
||||
}
|
||||
|
||||
.rose-form-input-field {
|
||||
@include type.type(m);
|
||||
padding: sizing.rems(xs);
|
||||
color: var(--stark);
|
||||
background-color: var(--subtle);
|
||||
border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
|
||||
border-radius: sizing.rems(xxxs);
|
||||
box-shadow: inset 0px 3px 1px
|
||||
rgba(var(--stark-components), var(--opacity-4));
|
||||
|
||||
&:not(:disabled):not([readonly]) {
|
||||
&:hover {
|
||||
border-color: var(--ui-gray);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--info);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--ui-gray);
|
||||
background-color: var(--ui-gray-subtler-6);
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: var(--failure);
|
||||
|
||||
.rose-form-input-helper-text {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.rose-form-input-field {
|
||||
color: var(--failure);
|
||||
border-color: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,25 @@
|
||||
*/
|
||||
|
||||
$size-coefficients: (
|
||||
xxl: 3, // 48
|
||||
xl: 2.25, // 36
|
||||
l: 1.5, // 24
|
||||
m: 1, // 16 - base
|
||||
s: 0.75, // 12
|
||||
xs: 0.5, // 8
|
||||
off-xs:0.3125, // 5
|
||||
xxs: 0.25, // 4
|
||||
xxxs: 0.125, // 2
|
||||
xxxxs: 0.0625 // 1
|
||||
xxl: 3,
|
||||
// 48
|
||||
xl: 2.25,
|
||||
// 36
|
||||
l: 1.5,
|
||||
// 24
|
||||
m: 1,
|
||||
// 16 - base
|
||||
s: 0.75,
|
||||
// 12
|
||||
xs: 0.5,
|
||||
// 8
|
||||
off-xs: 0.3125,
|
||||
// 5
|
||||
xxs: 0.25,
|
||||
// 4
|
||||
xxxs: 0.125,
|
||||
// 2
|
||||
xxxxs: 0.0625 // 1,,
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,33 +5,18 @@
|
||||
* SASS variables defining typography.
|
||||
*/
|
||||
|
||||
$family-primary:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
'Roboto',
|
||||
'Oxygen',
|
||||
'Ubuntu',
|
||||
'Cantarell',
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
$family-primary: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
$family-monospace:
|
||||
'SFMono-Regular',
|
||||
Consolas,
|
||||
'Liberation Mono',
|
||||
Menlo,
|
||||
Courier,
|
||||
$family-monospace: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier,
|
||||
monospace;
|
||||
|
||||
// Available font weights by name
|
||||
$font-weights: (
|
||||
normal: 400,
|
||||
semibold: 600,
|
||||
bold: 700
|
||||
bold: 700,
|
||||
);
|
||||
|
||||
// unitless value to start, but think pixels or points
|
||||
@@ -39,17 +24,23 @@ $base-font-size: 16;
|
||||
|
||||
// Font sizes don't quite match up with the size scale, so they're recalculated.
|
||||
$font-sizes: (
|
||||
xl: sizing.size(m) * 1.75, // 28
|
||||
l: sizing.size(m) * 1.25, // 20
|
||||
m: sizing.size(m), // 16
|
||||
s: sizing.size(m) * 0.875, // 14
|
||||
xs: sizing.size(m) * 0.75, // 12
|
||||
|
||||
h1: sizing.size(m) * 1.75, // 28
|
||||
h2: sizing.size(m) * 1.25, // 20
|
||||
h3: sizing.size(m), // 16
|
||||
|
||||
button: sizing.size(m) * 0.8125 // 13
|
||||
xl: sizing.size(m) * 1.75,
|
||||
// 28
|
||||
l: sizing.size(m) * 1.25,
|
||||
// 20
|
||||
m: sizing.size(m),
|
||||
// 16
|
||||
s: sizing.size(m) * 0.875,
|
||||
// 14
|
||||
xs: sizing.size(m) * 0.75,
|
||||
// 12
|
||||
h1: sizing.size(m) * 1.75,
|
||||
// 28
|
||||
h2: sizing.size(m) * 1.25,
|
||||
// 20
|
||||
h3: sizing.size(m),
|
||||
// 16
|
||||
button: sizing.size(m) * 0.8125 // 13,,
|
||||
);
|
||||
|
||||
// Line height expressed as ratio.
|
||||
@@ -59,12 +50,10 @@ $line-heights: (
|
||||
m: 20 / 16,
|
||||
s: 18 / 14,
|
||||
xs: 16 / 12,
|
||||
|
||||
h1: 48 / 28,
|
||||
h2: 36 / 20,
|
||||
h3: 24 / 16,
|
||||
|
||||
button: 24 / 13
|
||||
button: 24 / 13,
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="rose-form-input{{if @error " error"}}">
|
||||
|
||||
<label class="rose-form-input-label">
|
||||
{{@label}}
|
||||
</label>
|
||||
|
||||
{{#if @helperText}}
|
||||
<span class="rose-form-input-helper-text">{{@helperText}}</span>
|
||||
{{/if}}
|
||||
|
||||
<Input
|
||||
...attributes
|
||||
class="rose-form-input-field"
|
||||
@type={{@type}}
|
||||
@value={{@value}} />
|
||||
|
||||
{{yield}}
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/form/input';
|
||||
@@ -9,3 +9,30 @@ Dummy app index template
|
||||
<br>
|
||||
<Rose::Button @style="secondary" @disabled={{true}}>Secondary</Rose::Button>
|
||||
<br>
|
||||
|
||||
|
||||
<Rose::Form::Input
|
||||
@label="Label"
|
||||
@value="Read only"
|
||||
readonly={{true}}
|
||||
/>
|
||||
|
||||
<Rose::Form::Input
|
||||
@label="Label"
|
||||
@helperText="Helper text"
|
||||
@value="Text"
|
||||
/>
|
||||
|
||||
<Rose::Form::Input
|
||||
@label="Label"
|
||||
@helperText="Helper text"
|
||||
@value="Text"
|
||||
@error={{true}}
|
||||
/>
|
||||
|
||||
<Rose::Form::Input
|
||||
@label="Label"
|
||||
@helperText="Helper text"
|
||||
@value="Text"
|
||||
disabled={{true}}
|
||||
/>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { text, boolean } from '@storybook/addon-knobs';
|
||||
|
||||
export default {
|
||||
title: 'Rose::Form::Input',
|
||||
};
|
||||
|
||||
export const Normal = () => ({
|
||||
template: hbs`
|
||||
<Rose::Form::Input
|
||||
@value={{value}}
|
||||
@label={{label}}
|
||||
@helperText={{helperText}}
|
||||
@error={{error}}
|
||||
disabled={{disabled}}
|
||||
readonly={{read-only}}
|
||||
/>
|
||||
`,
|
||||
context: {
|
||||
value: text('value', 'Value'),
|
||||
label: text('label', 'Label'),
|
||||
helperText: text('helperText', 'Helper text'),
|
||||
disabled: boolean('disabled', false),
|
||||
'read-only': boolean('readonly', false),
|
||||
error: boolean('error', false),
|
||||
},
|
||||
});
|
||||
@@ -326,18 +326,18 @@ border: sizing.rems(xxxxs) solid;</pre>
|
||||
`,
|
||||
context: {
|
||||
sizes: [
|
||||
{code: 'xxl', pixels: 48},
|
||||
{code: 'xl', pixels: 36},
|
||||
{code: 'l', pixels: 24},
|
||||
{code: 'm', pixels: 16, isBase: true},
|
||||
{code: 's', pixels: 12},
|
||||
{code: 'xs', pixels: 8},
|
||||
{code: 'off-xs', pixels: 5},
|
||||
{code: 'xxs', pixels: 4},
|
||||
{code: 'xxxs', pixels: 2},
|
||||
{code: 'xxxxs', pixels: 1},
|
||||
]
|
||||
}
|
||||
{ code: 'xxl', pixels: 48 },
|
||||
{ code: 'xl', pixels: 36 },
|
||||
{ code: 'l', pixels: 24 },
|
||||
{ code: 'm', pixels: 16, isBase: true },
|
||||
{ code: 's', pixels: 12 },
|
||||
{ code: 'xs', pixels: 8 },
|
||||
{ code: 'off-xs', pixels: 5 },
|
||||
{ code: 'xxs', pixels: 4 },
|
||||
{ code: 'xxxs', pixels: 2 },
|
||||
{ code: 'xxxxs', pixels: 1 },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const Typography = () => ({
|
||||
@@ -424,15 +424,19 @@ export const Typography = () => ({
|
||||
`,
|
||||
context: {
|
||||
sizes: [
|
||||
{code: 'xl', pixels: 28},
|
||||
{code: 'l', pixels: 20},
|
||||
{code: 'm', pixels: 16},
|
||||
{code: 's', pixels: 14},
|
||||
{code: 'xs', pixels: 12},
|
||||
{code: 'h1', pixels: 28, note: 'Same as xl, but different line height.'},
|
||||
{code: 'h2', pixels: 20, note: 'Same as l, but different line height.'},
|
||||
{code: 'h3', pixels: 16, note: 'Same as m, but different line height.'},
|
||||
{code: 'button', pixels: 13}
|
||||
]
|
||||
}
|
||||
{ code: 'xl', pixels: 28 },
|
||||
{ code: 'l', pixels: 20 },
|
||||
{ code: 'm', pixels: 16 },
|
||||
{ code: 's', pixels: 14 },
|
||||
{ code: 'xs', pixels: 12 },
|
||||
{
|
||||
code: 'h1',
|
||||
pixels: 28,
|
||||
note: 'Same as xl, but different line height.',
|
||||
},
|
||||
{ code: 'h2', pixels: 20, note: 'Same as l, but different line height.' },
|
||||
{ code: 'h3', pixels: 16, note: 'Same as m, but different line height.' },
|
||||
{ code: 'button', pixels: 13 },
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
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/input', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Input @value="Text" @label="Label" />`);
|
||||
assert.equal(await find('label').textContent.trim(), 'Label');
|
||||
assert.equal(await find('input').value, 'Text');
|
||||
});
|
||||
|
||||
test('it displays optional helper text', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Input @helperText="Help me" />`);
|
||||
assert.equal(
|
||||
await find('.rose-form-input-helper-text').textContent.trim(),
|
||||
'Help me'
|
||||
);
|
||||
});
|
||||
|
||||
test('it is type="text" by default', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Input />`);
|
||||
assert.equal(await find('input').type, 'text');
|
||||
});
|
||||
|
||||
test('it supports a @type attribute', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Input @type="password" />`);
|
||||
assert.equal(await find('input').type, 'password');
|
||||
});
|
||||
|
||||
test('it supports disabled attribute', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Input disabled={{true}} />`);
|
||||
assert.ok(await find('[disabled]'));
|
||||
});
|
||||
|
||||
test('it supports readonly attribute', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Input readonly={{true}} />`);
|
||||
assert.ok(await find('[readonly]'));
|
||||
});
|
||||
|
||||
test('it gets an `error` class when @error=true', async function (assert) {
|
||||
await render(hbs`<Rose::Form::Input @error={{true}} />`);
|
||||
assert.ok(await find('.error'));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user