feat: 🎸 add key/value list component
This commit is contained in:
15 files changed
+269
-23
No files matched your search
@@ -0,0 +1,7 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../../templates/components/rose/list/key-value';
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
tagName: ''
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
<Meta title="Rose|List|KeyValue" component="List/KeyValue" />
|
||||
|
||||
# Key/Value List
|
||||
|
||||
A simple key/value list with support for embedded form fields and buttons.
|
||||
|
||||
<Preview>
|
||||
<Story name="Basic Key/Value List" height="17rem">{{
|
||||
template: hbs`
|
||||
<Rose::List::KeyValue as |list|>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<list.item as |item|>
|
||||
<item.key>
|
||||
<input.label>Key</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button @style="secondary">Add</Rose::Button>
|
||||
</item.cell>
|
||||
</list.item>
|
||||
</Rose::Form::Input>
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<list.item as |item|>
|
||||
<item.key>
|
||||
<input.label>Key</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
|
||||
</item.cell>
|
||||
</list.item>
|
||||
</Rose::Form::Input>
|
||||
</Rose::List::KeyValue>
|
||||
`,
|
||||
}}</Story>
|
||||
</Preview>
|
||||
|
||||
```html
|
||||
<Rose::List::KeyValue as |list|>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
|
||||
{{!-- In actual use, form fields should be yielded from an outer form component --}}
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<list.item as |item|>
|
||||
<item.key>
|
||||
<input.label>Key</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button @style="secondary">Add</Rose::Button>
|
||||
</item.cell>
|
||||
</list.item>
|
||||
</Rose::Form::Input>
|
||||
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<list.item as |item|>
|
||||
<item.key>
|
||||
<input.label>Key</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
|
||||
</item.cell>
|
||||
</list.item>
|
||||
</Rose::Form::Input>
|
||||
|
||||
</Rose::List::KeyValue>
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
import Component from '@ember/component';
|
||||
import layout from '../../../../templates/components/rose/list/key-value/item';
|
||||
import ComponentAutoId from '../../../../mixins/component-auto-id';
|
||||
|
||||
export default Component.extend(ComponentAutoId, {
|
||||
layout,
|
||||
tagName: ''
|
||||
});
|
||||
@@ -4,6 +4,7 @@
|
||||
@import 'header';
|
||||
@import 'layout';
|
||||
@import 'icon';
|
||||
@import 'list';
|
||||
@import 'notification';
|
||||
@import 'card';
|
||||
@import 'cards';
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
@include type.type(m);
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin-bottom: sizing.rems(xs);
|
||||
padding: sizing.rems(xs);
|
||||
color: var(--stark);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin-bottom: sizing.rems(xs);
|
||||
padding: sizing.rems(xs) sizing.rems(xxl) sizing.rems(xs) sizing.rems(xs);
|
||||
border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@import 'key-value';
|
||||
@@ -0,0 +1,45 @@
|
||||
@use '../../variables/sizing';
|
||||
@use '../../variables/typography';
|
||||
@use '../../utilities/type';
|
||||
|
||||
.rose-list-key-value-item {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--ui-gray-subtler-3);
|
||||
@include type.type(s);
|
||||
$lineHeight: 36 / 14;
|
||||
line-height: $lineHeight;
|
||||
padding: (sizing.rems(s) / 2) 0;
|
||||
|
||||
.rose-list-key-value-item-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: sizing.rems(m);
|
||||
|
||||
// Eliminate top/bottom margin from children
|
||||
> * {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
&,
|
||||
& > * {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// First cell (key) is approximately 25%
|
||||
&:nth-child(1) {
|
||||
width: 25%;
|
||||
font-weight: typography.font-weight(semibold);
|
||||
}
|
||||
|
||||
// Second cell (value) is approximately 75%
|
||||
&:nth-child(2) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
// Remaining cells will take up as little space as necessary to
|
||||
// suit their content.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<ul class="rose-list-key-value">
|
||||
{{yield (hash
|
||||
item=(component 'rose/list/key-value/item')
|
||||
)}}
|
||||
</ul>
|
||||
@@ -0,0 +1,13 @@
|
||||
<li class="rose-list-key-value-item" aria-labelledby={{concat "item-key-" this.id}}>
|
||||
{{yield (hash
|
||||
key=(component 'rose/anonymous'
|
||||
id=(concat "item-key-" this.id)
|
||||
tagName='div'
|
||||
classNames='rose-list-key-value-item-cell'
|
||||
)
|
||||
cell=(component 'rose/anonymous'
|
||||
tagName='div'
|
||||
classNames='rose-list-key-value-item-cell'
|
||||
)
|
||||
)}}
|
||||
</li>
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/list/key-value';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/list/key-value/item';
|
||||
@@ -1,21 +1,6 @@
|
||||
<Rose::Badge>Default</Rose::Badge>
|
||||
<Rose::Badge @style="outline">Default Outline</Rose::Badge>
|
||||
<Rose::Badge @icon="user-organization">Default with Icon</Rose::Badge>
|
||||
|
||||
<Rose::Badge @style="light">Light</Rose::Badge>
|
||||
<Rose::Badge @style="dark">Dark</Rose::Badge>
|
||||
|
||||
<Rose::Badge @style="informational">Informational</Rose::Badge>
|
||||
<Rose::Badge @style="informational-outline">Informational Outline</Rose::Badge>
|
||||
|
||||
<Rose::Badge @style="alert">Alert</Rose::Badge>
|
||||
<Rose::Badge @style="alert-outline">Alert Outline</Rose::Badge>
|
||||
|
||||
<Rose::Badge @style="error">Error</Rose::Badge>
|
||||
<Rose::Badge @style="error-outline">Error Outline</Rose::Badge>
|
||||
|
||||
<Rose::Badge @style="success">Success</Rose::Badge>
|
||||
<Rose::Badge @style="success-outline">Success Outline</Rose::Badge>
|
||||
|
||||
<Rose::Badge @style="warning">Warning</Rose::Badge>
|
||||
<Rose::Badge @style="warning-outline">Warning Outline</Rose::Badge>
|
||||
<Rose::List::KeyValue as |list|>
|
||||
<list.item as |item|>
|
||||
<item.cell>Key</item.cell>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
</Rose::List::KeyValue>
|
||||
@@ -33,7 +33,7 @@
|
||||
<layout.body>
|
||||
|
||||
<Rose::Layout::Sidebar as |layout|>
|
||||
|
||||
|
||||
<layout.sidebar>
|
||||
<Rose::Nav::Sidebar @title="Title" as |nav|>
|
||||
<nav.link @route="index">Item Name</nav.link>
|
||||
@@ -44,7 +44,51 @@
|
||||
</layout.sidebar>
|
||||
|
||||
<layout.body @tagName="main">
|
||||
Body content
|
||||
|
||||
<Rose::List::KeyValue as |list|>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<list.item as |item|>
|
||||
<item.key>
|
||||
<input.label>Key</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button @style="secondary">Add</Rose::Button>
|
||||
</item.cell>
|
||||
</list.item>
|
||||
</Rose::Form::Input>
|
||||
|
||||
<Rose::Form::Input @contextual={{true}} as |input|>
|
||||
<list.item as |item|>
|
||||
<item.key>
|
||||
<input.label>Key</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button @style="secondary" @iconOnly="trash" title="Remove">Remove</Rose::Button>
|
||||
</item.cell>
|
||||
</list.item>
|
||||
</Rose::Form::Input>
|
||||
|
||||
</Rose::List::KeyValue>
|
||||
|
||||
</layout.body>
|
||||
|
||||
</Rose::Layout::Sidebar>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, find, findAll } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | rose/list/key-value', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function(assert) {
|
||||
assert.expect(2);
|
||||
await render(hbs`
|
||||
<Rose::List::KeyValue as |list|>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
<list.item as |item|>
|
||||
<item.key>Key</item.key>
|
||||
<item.cell>Value</item.cell>
|
||||
</list.item>
|
||||
</Rose::List::KeyValue>
|
||||
`);
|
||||
const itemLabelledBy = find('.rose-list-key-value-item:first-child').getAttribute('aria-labelledby');
|
||||
assert.equal(findAll('.rose-list-key-value-item').length, 2, 'it has two items');
|
||||
assert.ok(
|
||||
find(`.rose-list-key-value-item:first-child > .rose-list-key-value-item-cell:first-child#${itemLabelledBy}`),
|
||||
'The key cell has an ID associated with the `aria-labelledby` attribute of its parent item element'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user