-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create dropdown component (#323)
* feat(popover): Add `isShown` to yielded visibility * feat: Copy dropdown styling from Bootstrap 5.3.3 * feat: Create dropdown component * docs: Add page for dropdown * chore: Add tests for dropdown * feat(dropdown): Allow custom controls * fix: Reinstate styling for select components
- Loading branch information
Showing
13 changed files
with
669 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { array, fn } from '@ember/helper'; | ||
import { service } from '@ember/service'; | ||
import Component from '@glimmer/component'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import { Dropdown, Toaster } from '@nrg-ui/core'; | ||
import FreestyleUsage from 'ember-freestyle/components/freestyle/usage'; | ||
import FreestyleSection from 'ember-freestyle/components/freestyle-section'; | ||
|
||
import CodeBlock from '../../code-block'; | ||
|
||
import type { Alignment, Side } from '@floating-ui/dom'; | ||
import type { ToastService } from '@nrg-ui/core/services/toast'; | ||
|
||
export default class extends Component { | ||
@service | ||
declare toast: ToastService; | ||
|
||
@tracked | ||
alignment?: Alignment; | ||
|
||
@tracked | ||
class: string = 'btn-primary'; | ||
|
||
@tracked | ||
closeOnSelect?: boolean; | ||
|
||
@tracked | ||
hasIcon?: boolean; | ||
|
||
@tracked | ||
isOpen?: boolean; | ||
|
||
@tracked | ||
offset?: number; | ||
|
||
@tracked | ||
side?: Side; | ||
|
||
update = (key: string, value: unknown) => { | ||
this[key] = value; | ||
}; | ||
|
||
log = (...args: unknown[]) => { | ||
console.log(...args); | ||
}; | ||
|
||
<template> | ||
<Toaster /> | ||
<FreestyleSection @name="Dropdown" as |Section|> | ||
<Section.subsection @name="Button"> | ||
<FreestyleUsage> | ||
<:example> | ||
<Dropdown | ||
class={{this.class}} | ||
@alignment={{this.alignment}} | ||
@closeOnSelect={{this.closeOnSelect}} | ||
@hasIcon={{this.hasIcon}} | ||
@isOpen={{this.isOpen}} | ||
@offset={{this.offset}} | ||
@side={{this.side}} | ||
@onShow={{fn this.toast.info "onShow was fired"}} | ||
@onHide={{fn this.toast.info "onHide was fired"}} | ||
> | ||
<:control> | ||
Dropdown | ||
</:control> | ||
<:menu as |Menu|> | ||
<Menu.Header> | ||
Header | ||
</Menu.Header> | ||
<Menu.Item | ||
@disabled={{true}} | ||
@onSelect={{fn this.log "Item 1 clicked"}} | ||
> | ||
Item 1 | ||
</Menu.Item> | ||
<Menu.Item @onSelect={{fn this.log "Item 2 clicked"}}>Item 2</Menu.Item> | ||
<Menu.Item @onSelect={{fn this.log "Item 3 clicked"}}>Item 3</Menu.Item> | ||
<Menu.Divider /> | ||
<Menu.Header> | ||
Header 2 | ||
</Menu.Header> | ||
<Menu.Item @onSelect={{fn this.log "Item 4 clicked"}}>Item 4</Menu.Item> | ||
<Menu.Item @onSelect={{fn this.log "Item 5 clicked"}}>Item 5</Menu.Item> | ||
<Menu.Item @onSelect={{fn this.log "Item 6 clicked"}}>Item 6</Menu.Item> | ||
</:menu> | ||
</Dropdown> | ||
</:example> | ||
<:api as |Args|> | ||
<Args.String | ||
@name="class" | ||
@description="The class to apply to the dropdown button. Note that this is not an argument but rather a class applied directly to the button" | ||
@value={{this.class}} | ||
@onInput={{fn this.update "class"}} | ||
@options={{this.classOptions}} | ||
/> | ||
<Args.String | ||
@name="alignment" | ||
@defaultValue="start" | ||
@description="How to align the dropdown" | ||
@value={{this.alignment}} | ||
@options={{array "" "start" "end"}} | ||
@onInput={{fn this.update "alignment"}} | ||
/> | ||
<Args.Bool | ||
@name="closeOnSelect" | ||
@defaultValue={{true}} | ||
@description="Whether to close the dropdown when an item is selected" | ||
@value={{this.closeOnSelect}} | ||
@onInput={{fn this.update "closeOnSelect"}} | ||
/> | ||
<Args.Bool | ||
@name="hasIcon" | ||
@defaultValue="true" | ||
@description="Whether to show the dropdown icon" | ||
@value={{this.hasIcon}} | ||
@onInput={{fn this.update "hasIcon"}} | ||
/> | ||
<Args.Bool | ||
@name="isShown" | ||
@defaultValue="undefined" | ||
@description="Whether to open the dropdown" | ||
@value={{this.isShown}} | ||
@onInput={{fn this.update "isShown"}} | ||
/> | ||
<Args.Number | ||
@name="offset" | ||
@description="How far to offset the dropdown from the button (in pixels)" | ||
@value={{this.offset}} | ||
@onInput={{fn this.update "offset"}} | ||
/> | ||
<Args.String | ||
@name="side" | ||
@defaultValue="bottom" | ||
@description="Which side of the control to show the dropdown" | ||
@value={{this.side}} | ||
@options={{array "" "top" "end" "bottom" "start"}} | ||
@onInput={{fn this.update "side"}} | ||
/> | ||
<Args.Action | ||
@name="onShow" | ||
@description="Fired when the dropdown is shown" | ||
> | ||
<CodeBlock @lang="typescript" @code="() => Promise<void>" /> | ||
</Args.Action> | ||
<Args.Action | ||
@name="onHide" | ||
@description="Fired when the dropdown is hidden" | ||
> | ||
<CodeBlock @lang="typescript" @code="() => Promise<void>" /> | ||
</Args.Action> | ||
</:api> | ||
</FreestyleUsage> | ||
</Section.subsection> | ||
</FreestyleSection> | ||
</template> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{{page-title "Dropdown"}} | ||
|
||
<div class="container mx-auto"> | ||
<F::Components::Dropdown /> | ||
</div> |
62 changes: 62 additions & 0 deletions
62
apps/test-app/tests/integration/components/dropdown-test.gts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { fn } from '@ember/helper'; | ||
import { click, render } from '@ember/test-helpers'; | ||
import Dropdown from '@nrg-ui/core/components/dropdown'; | ||
import { module, test } from 'qunit'; | ||
import { setupRenderingTest } from 'test-app/tests/helpers'; | ||
|
||
module('Integration | Component | dropdown', function (hooks) { | ||
setupRenderingTest(hooks); | ||
|
||
test('it renders', async function (assert) { | ||
const clickHandler = (val: string) => { | ||
assert.step(val); | ||
}; | ||
|
||
await render(<template> | ||
<Dropdown> | ||
<:control> | ||
This is the button | ||
</:control> | ||
<:menu as |Menu|> | ||
<Menu.Item @onSelect={{fn clickHandler "item1"}}> | ||
Item 1 | ||
</Menu.Item> | ||
<Menu.Item @onSelect={{fn clickHandler "item2"}}> | ||
Item 2 | ||
</Menu.Item> | ||
<Menu.Item @disabled={{true}} @onSelect={{fn clickHandler "item3"}}> | ||
Item 3 (disabled) | ||
</Menu.Item> | ||
<Menu.Divider /> | ||
<Menu.Header> | ||
Header | ||
</Menu.Header> | ||
</:menu> | ||
</Dropdown> | ||
</template>); | ||
|
||
await click('.btn.dropdown'); | ||
await click('.dropdown-menu > li:nth-child(1)'); | ||
|
||
await click('.btn.dropdown'); | ||
await click('.dropdown-menu > li:nth-child(2)'); | ||
|
||
await click('.btn.dropdown'); | ||
await click('.dropdown-menu > li:nth-child(3)'); | ||
|
||
assert.verifySteps(['item1', 'item2']); | ||
|
||
assert.dom('[data-test-dropdown-item]').exists({ count: 3 }); | ||
assert | ||
.dom( | ||
'[data-test-dropdown-item]:nth-child(3) + li > [data-test-dropdown-divider]', | ||
) | ||
.exists(); | ||
assert | ||
.dom( | ||
'li:has(> [data-test-dropdown-divider]) + li > [data-test-dropdown-header]', | ||
) | ||
.hasText('Header') | ||
.exists(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.