Merge pull request #822 from hashicorp/feature-checkbox-group
feat: 🎸 implement checkbox group component
This commit is contained in:
9 files changed
+317
-35
No files matched your search
@@ -0,0 +1,12 @@
|
||||
{{#each @items as |item|}}
|
||||
{{yield (hash
|
||||
item=item
|
||||
checkbox=(component 'rose/form/checkbox'
|
||||
name=@name
|
||||
disabled=@disabled
|
||||
inline=@inline
|
||||
checked=(includes item @selectedItems)
|
||||
onChange=(fn this.toggleItem item)
|
||||
)
|
||||
)}}
|
||||
{{/each}}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
/**
|
||||
* A checkbox group represents an array of arbitrary items, which differs from
|
||||
* single checkboxes which represent boolean values. When checkboxes within a
|
||||
* checkbox are toggled, their associated item is toggled within the group's
|
||||
* array value.
|
||||
*
|
||||
* The term "item" is used to disambiguate it from "value", which has an
|
||||
* existing meaning in the context of form fields. However, items
|
||||
* are just arbitrary values.
|
||||
*/
|
||||
export default class RoseFormCheckboxGroupComponent extends Component {
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* Returns a new copy of the @selectedItems array where the given item is:
|
||||
* - removed from the array, if it was already present
|
||||
* - or added to the array, if it was not already present
|
||||
*/
|
||||
@action
|
||||
toggleItem(item) {
|
||||
const selectedItems = this.args.selectedItems || [];
|
||||
const currentItems = [...selectedItems];
|
||||
|
||||
if (currentItems.includes(item)) {
|
||||
const i = currentItems.indexOf(item);
|
||||
currentItems.splice(i, 1);
|
||||
} else {
|
||||
currentItems.push(item);
|
||||
}
|
||||
|
||||
if (this.args.onChange) this.args.onChange(currentItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { text, boolean, select, object } from '@storybook/addon-knobs';
|
||||
|
||||
<Meta title="Rose|Form/Checkbox/Group" component="Form/Checkbox/Group" />
|
||||
|
||||
# Form checkboxfield group
|
||||
|
||||
Renders a form checkboxfield group.
|
||||
|
||||
```htmlbars
|
||||
<Rose::Form::Checkbox::Group
|
||||
@name='checkbox-group'
|
||||
@items={{this.items}}
|
||||
@selectedItems={{this.selectedItems}}
|
||||
@onChange={{fn this.onChange}}
|
||||
as |group|
|
||||
>
|
||||
<group.checkbox
|
||||
@label={{group.item.name}}
|
||||
value={{group.item.id}} />
|
||||
</Rose::Form::Checkbox::Group>
|
||||
```
|
||||
<Preview>
|
||||
<Story name="Basic" height="10rem">{{
|
||||
template: hbs`
|
||||
<Rose::Form::Checkbox::Group
|
||||
@name={{name}}
|
||||
@inline={{inline}}
|
||||
@disabled={{disabled}}
|
||||
@items={{items}}
|
||||
@selectedItems={{selectedItems}}
|
||||
@onChange={{onChange}}
|
||||
as |group|
|
||||
>
|
||||
<group.checkbox
|
||||
@label={{group.item.name}}
|
||||
value={{group.item.id}} />
|
||||
</Rose::Form::Checkbox::Group>
|
||||
`,
|
||||
context: {
|
||||
inline: boolean('inline', true),
|
||||
disabled: boolean('disabled', false),
|
||||
items: object('items', [
|
||||
{ id: 1, name: 'Foo' },
|
||||
{ id: 2, name: 'Bar' },
|
||||
{ id: 3, name: 'Baz' },
|
||||
]),
|
||||
selectedItems: object('selectedItems', []),
|
||||
onChange: action('changed'),
|
||||
}
|
||||
}}</Story>
|
||||
</Preview>
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'rose/components/rose/form/checkbox/group';
|
||||
@@ -65,6 +65,7 @@
|
||||
"ember-cli-sass": "^10.0.1",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-terser": "^4.0.2",
|
||||
"ember-composable-helpers": "^4.5.0",
|
||||
"ember-disable-prototype-extensions": "^1.1.3",
|
||||
"ember-export-application-global": "^2.0.1",
|
||||
"ember-inline-svg": "^1.0.0",
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
const possibleItems = [
|
||||
{ id: 1, name: 'foo' },
|
||||
{ id: 2, name: 'bar' },
|
||||
{ id: 3, name: 'baz' },
|
||||
];
|
||||
|
||||
export default class IndexController extends Controller {
|
||||
color = 'green';
|
||||
// =attributes
|
||||
|
||||
@tracked items = [...possibleItems];
|
||||
@tracked selectedItems = [possibleItems[2]];
|
||||
|
||||
// =actions
|
||||
|
||||
@action
|
||||
actionHandler(/*value*/) {
|
||||
// eslint-disable-next-line no-console
|
||||
//console.log('action handled');
|
||||
checkboxGroupChanged(selected) {
|
||||
this.selectedItems = [...selected];
|
||||
}
|
||||
|
||||
@action
|
||||
clearSelected() {
|
||||
this.selectedItems = [];
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,28 @@
|
||||
<h2>Checkbox Group</h2>
|
||||
<Rose::Form::Checkbox::Group
|
||||
@items={{this.items}}
|
||||
@selectedItems={{this.selectedItems}}
|
||||
@onChange={{this.checkboxGroupChanged}}
|
||||
as |group|
|
||||
>
|
||||
<group.checkbox
|
||||
@label={{group.item.name}}
|
||||
value={{group.item.id}} />
|
||||
</Rose::Form::Checkbox::Group>
|
||||
|
||||
<p>
|
||||
Items selected:
|
||||
{{#each this.selectedItems as |selected|}}
|
||||
{{selected.id}},
|
||||
{{/each}}
|
||||
</p>
|
||||
|
||||
<Rose::Button @style='primary' {{on 'click' this.clearSelected}}>
|
||||
Clear Selected Items
|
||||
</Rose::Button>
|
||||
|
||||
<Rose::Separator />
|
||||
|
||||
<h2>KeyValue Dropdown</h2>
|
||||
<Rose::Dropdown
|
||||
@text="Credential library details"
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, findAll, click } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | rose/form/checkbox/group', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
let items;
|
||||
let selectedItems;
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
items = [
|
||||
{ id: 1, name: 'Foo' },
|
||||
{ id: 2, name: 'Bar' },
|
||||
{ id: 3, name: 'Baz' },
|
||||
];
|
||||
selectedItems = [items[2]];
|
||||
});
|
||||
|
||||
test('it renders checkboxes equal in length to an array of items', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.items = items;
|
||||
await render(hbs`
|
||||
<Rose::Form::Checkbox::Group
|
||||
@items={{this.items}}
|
||||
as |group|
|
||||
>
|
||||
<group.checkbox
|
||||
@label={{group.item.name}}
|
||||
value={{group.item.id}} />
|
||||
</Rose::Form::Checkbox::Group>
|
||||
`);
|
||||
assert.equal(findAll('input').length, 3);
|
||||
});
|
||||
|
||||
test('it checks checkboxes for items present in a selectedItems array', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.items = items;
|
||||
this.selectedItems = selectedItems;
|
||||
await render(hbs`
|
||||
<Rose::Form::Checkbox::Group
|
||||
@items={{this.items}}
|
||||
@selectedItems={{this.selectedItems}}
|
||||
as |group|
|
||||
>
|
||||
<group.checkbox
|
||||
@label={{group.item.name}}
|
||||
value={{group.item.id}} />
|
||||
</Rose::Form::Checkbox::Group>
|
||||
`);
|
||||
assert.equal(findAll(':checked').length, 1);
|
||||
});
|
||||
|
||||
test('it fires onChange when items are checked, passing an array of selected items', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.items = items;
|
||||
this.selectedItems = selectedItems;
|
||||
this.onChange = (newlySelectedItems) => {
|
||||
assert.equal(selectedItems.length, 1);
|
||||
assert.equal(newlySelectedItems.length, 2);
|
||||
};
|
||||
await render(hbs`
|
||||
<Rose::Form::Checkbox::Group
|
||||
@items={{this.items}}
|
||||
@selectedItems={{this.selectedItems}}
|
||||
@onChange={{this.onChange}}
|
||||
as |group|
|
||||
>
|
||||
<group.checkbox
|
||||
@label={{group.item.name}}
|
||||
value={{group.item.id}} />
|
||||
</Rose::Form::Checkbox::Group>
|
||||
`);
|
||||
await click('.rose-form-checkbox:first-child input');
|
||||
});
|
||||
|
||||
test('it fires onChange when items are unchecked, passing an array of selected items', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.items = items;
|
||||
this.selectedItems = selectedItems;
|
||||
this.onChange = (newlySelectedItems) => {
|
||||
assert.equal(selectedItems.length, 1);
|
||||
assert.equal(newlySelectedItems.length, 0);
|
||||
};
|
||||
await render(hbs`
|
||||
<Rose::Form::Checkbox::Group
|
||||
@items={{this.items}}
|
||||
@selectedItems={{this.selectedItems}}
|
||||
@onChange={{this.onChange}}
|
||||
as |group|
|
||||
>
|
||||
<group.checkbox
|
||||
@label={{group.item.name}}
|
||||
value={{group.item.id}} />
|
||||
</Rose::Form::Checkbox::Group>
|
||||
`);
|
||||
await click('.rose-form-checkbox:last-child input');
|
||||
});
|
||||
});
|
||||
@@ -78,6 +78,27 @@
|
||||
semver "^6.3.0"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.15.8":
|
||||
version "7.15.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.8.tgz#195b9f2bffe995d2c6c159e72fe525b4114e8c10"
|
||||
integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.15.8"
|
||||
"@babel/generator" "^7.15.8"
|
||||
"@babel/helper-compilation-targets" "^7.15.4"
|
||||
"@babel/helper-module-transforms" "^7.15.8"
|
||||
"@babel/helpers" "^7.15.4"
|
||||
"@babel/parser" "^7.15.8"
|
||||
"@babel/template" "^7.15.4"
|
||||
"@babel/traverse" "^7.15.4"
|
||||
"@babel/types" "^7.15.6"
|
||||
convert-source-map "^1.7.0"
|
||||
debug "^4.1.0"
|
||||
gensync "^1.0.0-beta.2"
|
||||
json5 "^2.1.2"
|
||||
semver "^6.3.0"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/core@^7.14.8", "@babel/core@^7.15.5":
|
||||
version "7.15.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.5.tgz#f8ed9ace730722544609f90c9bb49162dc3bf5b9"
|
||||
@@ -99,27 +120,6 @@
|
||||
semver "^6.3.0"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/core@^7.15.8":
|
||||
version "7.15.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.8.tgz#195b9f2bffe995d2c6c159e72fe525b4114e8c10"
|
||||
integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.15.8"
|
||||
"@babel/generator" "^7.15.8"
|
||||
"@babel/helper-compilation-targets" "^7.15.4"
|
||||
"@babel/helper-module-transforms" "^7.15.8"
|
||||
"@babel/helpers" "^7.15.4"
|
||||
"@babel/parser" "^7.15.8"
|
||||
"@babel/template" "^7.15.4"
|
||||
"@babel/traverse" "^7.15.4"
|
||||
"@babel/types" "^7.15.6"
|
||||
convert-source-map "^1.7.0"
|
||||
debug "^4.1.0"
|
||||
gensync "^1.0.0-beta.2"
|
||||
json5 "^2.1.2"
|
||||
semver "^6.3.0"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.15.0":
|
||||
version "7.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15"
|
||||
@@ -6832,6 +6832,25 @@ broccoli-funnel-reducer@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/broccoli-funnel-reducer/-/broccoli-funnel-reducer-1.0.0.tgz#11365b2a785aec9b17972a36df87eef24c5cc0ea"
|
||||
integrity sha1-ETZbKnha7JsXlyo234fu8kxcwOo=
|
||||
|
||||
broccoli-funnel@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-2.0.1.tgz#6823c73b675ef78fffa7ab800f083e768b51d449"
|
||||
integrity sha512-C8Lnp9TVsSSiZMGEF16C0dCiNg2oJqUKwuZ1K4kVC6qRPG/2Cj/rtB5kRCC9qEbwqhX71bDbfHROx0L3J7zXQg==
|
||||
dependencies:
|
||||
array-equal "^1.0.0"
|
||||
blank-object "^1.0.1"
|
||||
broccoli-plugin "^1.3.0"
|
||||
debug "^2.2.0"
|
||||
fast-ordered-set "^1.0.0"
|
||||
fs-tree-diff "^0.5.3"
|
||||
heimdalljs "^0.2.0"
|
||||
minimatch "^3.0.0"
|
||||
mkdirp "^0.5.0"
|
||||
path-posix "^1.0.0"
|
||||
rimraf "^2.4.3"
|
||||
symlink-or-copy "^1.0.0"
|
||||
walk-sync "^0.3.1"
|
||||
|
||||
broccoli-funnel@^1.0.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-1.2.0.tgz#cddc3afc5ff1685a8023488fff74ce6fb5a51296"
|
||||
@@ -7651,7 +7670,7 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001265:
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001271:
|
||||
version "1.0.30001271"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001271.tgz#0dda0c9bcae2cf5407cd34cac304186616cc83e8"
|
||||
integrity sha512-BBruZFWmt3HFdVPS8kceTBIguKxu4f99n5JNp06OlPD/luoAMIaIK5ieV5YjnBLH3Nysai9sxj9rpJj4ZisXOA==
|
||||
@@ -9491,10 +9510,10 @@ electron-rebuild@^2.0.3:
|
||||
tar "^6.0.5"
|
||||
yargs "^16.0.0"
|
||||
|
||||
electron-to-chromium@^1.3.867:
|
||||
version "1.3.868"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.868.tgz#ed835023b57ecf0ba63dfe7d50e16b53758ab1da"
|
||||
integrity sha512-kZYCHqwJ1ctGrYDlOcWQH+/AftAm/KD4lEnLDNwS0kKwx1x6dU4zv+GuDjsPPOGn/2TjnKBaZjDyjXaoix0q/A==
|
||||
electron-to-chromium@^1.3.878:
|
||||
version "1.3.878"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.878.tgz#baa9fb5c24b9b580f08fb245cbb52a22f8fc8fa8"
|
||||
integrity sha512-O6yxWCN9ph2AdspAIszBnd9v8s11hQx8ub9w4UGApzmNRnoKhbulOWqbO8THEQec/aEHtvy+donHZMlh6l1rbA==
|
||||
|
||||
element-resize-detector@^1.2.2:
|
||||
version "1.2.3"
|
||||
@@ -10153,6 +10172,16 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-co
|
||||
fs-extra "^9.1.0"
|
||||
semver "^5.4.1"
|
||||
|
||||
ember-composable-helpers@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-composable-helpers/-/ember-composable-helpers-4.5.0.tgz#94febbdf4348e64f45f7a6f993f326e32540a61e"
|
||||
integrity sha512-XjpDLyVPsLCy6kd5dIxZonOECCO6AA5sY5Hr6tYUbJg3s5ghFAiFWaNcYraYC+fL2yPJQAswwpfwGlQORUJZkw==
|
||||
dependencies:
|
||||
"@babel/core" "^7.0.0"
|
||||
broccoli-funnel "2.0.1"
|
||||
ember-cli-babel "^7.26.3"
|
||||
resolve "^1.10.0"
|
||||
|
||||
ember-concurrency@^0.10.0:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-0.10.1.tgz#aae4528540391a7278e2c193a146b5474cad5ec2"
|
||||
@@ -16603,13 +16632,20 @@ minipass@^2.2.0, minipass@^2.3.5:
|
||||
safe-buffer "^5.1.2"
|
||||
yallist "^3.0.0"
|
||||
|
||||
minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3:
|
||||
minipass@^3.0.0, minipass@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
|
||||
integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minipass@^3.1.0, minipass@^3.1.3:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732"
|
||||
integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minizlib@^2.0.0, minizlib@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||
@@ -17023,10 +17059,10 @@ node-pre-gyp@^0.11.0:
|
||||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-releases@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.0.tgz#67dc74903100a7deb044037b8a2e5f453bb05400"
|
||||
integrity sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA==
|
||||
node-releases@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
|
||||
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
|
||||
|
||||
node-uuid@~1.4.7:
|
||||
version "1.4.8"
|
||||
|
||||
Reference in new issue
Block a user