mirror of https://github.com/hashicorp/consul
ui: PagedCollection component (#12404)
* ui: PagedCollection component * ui: Use PagedCollection (#12436) * ui: Integrate PagedCollection into DisclosureMenu * Integrate PageCollection into DC, Nspace and Partition menuspull/12453/head
parent
79a07c7a3d
commit
121bd2e0ab
|
@ -2,18 +2,26 @@
|
||||||
{{#if (can "choose nspaces")}}
|
{{#if (can "choose nspaces")}}
|
||||||
{{#let
|
{{#let
|
||||||
(or @nspace 'default')
|
(or @nspace 'default')
|
||||||
as |nspace|}}
|
(is-href 'dc.nspaces' @dc.Name)
|
||||||
|
as |nspace isManaging|}}
|
||||||
<li
|
<li
|
||||||
class="nspaces"
|
class="nspaces"
|
||||||
data-test-nspace-menu
|
data-test-nspace-menu
|
||||||
>
|
>
|
||||||
<DisclosureMenu
|
<DisclosureMenu
|
||||||
aria-label="Namespace"
|
aria-label="Namespace"
|
||||||
|
@items={{append
|
||||||
|
(hash
|
||||||
|
Name="Manage Namespaces"
|
||||||
|
href=(href-to 'dc.nspaces' @dc.Name)
|
||||||
|
)
|
||||||
|
(reject-by 'DeletedAt' @nspaces)
|
||||||
|
}}
|
||||||
as |disclosure|>
|
as |disclosure|>
|
||||||
<disclosure.Action
|
<disclosure.Action
|
||||||
{{on 'click' disclosure.toggle}}
|
{{on 'click' disclosure.toggle}}
|
||||||
>
|
>
|
||||||
{{nspace}}
|
{{if isManaging 'Manage Namespaces' nspace}}
|
||||||
</disclosure.Action>
|
</disclosure.Action>
|
||||||
<disclosure.Menu as |panel|>
|
<disclosure.Menu as |panel|>
|
||||||
{{#if (gt @nspaces.length 0)}}
|
{{#if (gt @nspaces.length 0)}}
|
||||||
|
@ -40,34 +48,39 @@
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<panel.Menu as |menu|>
|
<panel.Menu as |menu|>
|
||||||
{{#each (reject-by 'DeletedAt' @nspaces) as |item|}}
|
{{#each menu.items as |item|}}
|
||||||
|
|
||||||
<menu.Item
|
<menu.Item
|
||||||
aria-current={{if (eq nspace item.Name) 'true'}}
|
data-test-main-nav-nspaces={{not-eq item.href undefined}}
|
||||||
|
aria-current={{if
|
||||||
|
(or
|
||||||
|
(and isManaging item.href)
|
||||||
|
(and (not isManaging) (eq nspace item.Name))
|
||||||
|
)
|
||||||
|
'true'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<menu.Action
|
<menu.Action
|
||||||
{{on 'click' disclosure.close}}
|
{{on 'click' disclosure.close}}
|
||||||
@href={{href-to '.' params=(hash
|
@href={{if item.href
|
||||||
partition=(if (gt @partition.length 0) @partition undefined)
|
item.href
|
||||||
nspace=item.Name
|
(if isManaging
|
||||||
)}}
|
(href-to 'dc.services.index' params=(hash
|
||||||
|
partition=(if (gt @partition.length 0) @partition undefined)
|
||||||
|
nspace=item.Name
|
||||||
|
dc=@dc.Name
|
||||||
|
))
|
||||||
|
(href-to '.' params=(hash
|
||||||
|
partition=(if (gt @partition.length 0) @partition undefined)
|
||||||
|
nspace=item.Name
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{{item.Name}}
|
{{item.Name}}
|
||||||
</menu.Action>
|
</menu.Action>
|
||||||
</menu.Item>
|
</menu.Item>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if (can 'manage nspaces')}}
|
|
||||||
<menu.Separator />
|
|
||||||
<menu.Item
|
|
||||||
data-test-main-nav-nspaces
|
|
||||||
>
|
|
||||||
<menu.Action
|
|
||||||
{{on 'click' disclosure.close}}
|
|
||||||
@href={{href-to 'dc.nspaces' @dc.Name}}
|
|
||||||
>
|
|
||||||
Manage Namespaces
|
|
||||||
</menu.Action>
|
|
||||||
</menu.Item>
|
|
||||||
{{/if}}
|
|
||||||
</panel.Menu>
|
</panel.Menu>
|
||||||
</disclosure.Menu>
|
</disclosure.Menu>
|
||||||
</DisclosureMenu>
|
</DisclosureMenu>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{{#let
|
{{#let
|
||||||
(or @partition 'default')
|
(or @partition 'default')
|
||||||
as |partition|}}
|
(is-href 'dc.partitions' @dc.Name)
|
||||||
|
as |partition isManaging|}}
|
||||||
{{#if (can "choose partitions" dc=@dc)}}
|
{{#if (can "choose partitions" dc=@dc)}}
|
||||||
<li
|
<li
|
||||||
class="partitions"
|
class="partitions"
|
||||||
|
@ -8,11 +9,18 @@ as |partition|}}
|
||||||
>
|
>
|
||||||
<DisclosureMenu
|
<DisclosureMenu
|
||||||
aria-label="Admin Partition"
|
aria-label="Admin Partition"
|
||||||
|
@items={{append
|
||||||
|
(hash
|
||||||
|
Name="Manage Partitions"
|
||||||
|
href=(href-to 'dc.partitions' @dc.Name)
|
||||||
|
)
|
||||||
|
(reject-by 'DeletedAt' @partitions)
|
||||||
|
}}
|
||||||
as |disclosure|>
|
as |disclosure|>
|
||||||
<disclosure.Action
|
<disclosure.Action
|
||||||
{{on 'click' disclosure.toggle}}
|
{{on 'click' disclosure.toggle}}
|
||||||
>
|
>
|
||||||
{{partition}}
|
{{if isManaging 'Manage Partition' partition}}
|
||||||
</disclosure.Action>
|
</disclosure.Action>
|
||||||
<disclosure.Menu as |panel|>
|
<disclosure.Menu as |panel|>
|
||||||
<DataSource
|
<DataSource
|
||||||
|
@ -25,34 +33,37 @@ as |partition|}}
|
||||||
@onchange={{fn (optional @onchange)}}
|
@onchange={{fn (optional @onchange)}}
|
||||||
/>
|
/>
|
||||||
<panel.Menu as |menu|>
|
<panel.Menu as |menu|>
|
||||||
{{#each (reject-by 'DeletedAt' @partitions) as |item|}}
|
{{#each menu.items as |item|}}
|
||||||
<menu.Item
|
<menu.Item
|
||||||
class={{if (eq partition item.Name) 'is-active'}}
|
aria-current={{if
|
||||||
|
(or
|
||||||
|
(and isManaging item.href)
|
||||||
|
(and (not isManaging) (eq partition item.Name))
|
||||||
|
)
|
||||||
|
'true'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<menu.Action
|
<menu.Action
|
||||||
{{on 'click' disclosure.close}}
|
{{on 'click' disclosure.close}}
|
||||||
@href={{href-to '.' params=(hash
|
@href={{if item.href
|
||||||
partition=item.Name
|
item.href
|
||||||
nspace=undefined
|
(if isManaging
|
||||||
)}}
|
(href-to 'dc.services.index' params=(hash
|
||||||
|
partition=item.Name
|
||||||
|
nspace=undefined
|
||||||
|
dc=@dc.Name
|
||||||
|
))
|
||||||
|
(href-to '.' params=(hash
|
||||||
|
partition=item.Name
|
||||||
|
nspace=undefined
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{{item.Name}}
|
{{item.Name}}
|
||||||
</menu.Action>
|
</menu.Action>
|
||||||
</menu.Item>
|
</menu.Item>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if (can 'manage partitions')}}
|
|
||||||
<menu.Separator />
|
|
||||||
<menu.Item
|
|
||||||
data-test-main-nav-partitions
|
|
||||||
>
|
|
||||||
<menu.Action
|
|
||||||
{{on 'click' disclosure.close}}
|
|
||||||
@href={{href-to 'dc.partitions.index' @dc.Name}}
|
|
||||||
>
|
|
||||||
Manage Partitions
|
|
||||||
</menu.Action>
|
|
||||||
</menu.Item>
|
|
||||||
{{/if}}
|
|
||||||
</panel.Menu>
|
</panel.Menu>
|
||||||
</disclosure.Menu>
|
</disclosure.Menu>
|
||||||
</DisclosureMenu>
|
</DisclosureMenu>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
>
|
>
|
||||||
<DisclosureMenu
|
<DisclosureMenu
|
||||||
aria-label="Datacenter"
|
aria-label="Datacenter"
|
||||||
|
@items={{sort-by 'Name' @dcs}}
|
||||||
as |disclosure|>
|
as |disclosure|>
|
||||||
<disclosure.Action
|
<disclosure.Action
|
||||||
{{on 'click' disclosure.toggle}}
|
{{on 'click' disclosure.toggle}}
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
@onchange={{action (mut @dcs) value="data"}}
|
@onchange={{action (mut @dcs) value="data"}}
|
||||||
/>
|
/>
|
||||||
<panel.Menu as |menu|>
|
<panel.Menu as |menu|>
|
||||||
{{#each (sort-by 'Name' @dcs) as |item|}}
|
{{#each menu.items as |item|}}
|
||||||
<menu.Item
|
<menu.Item
|
||||||
aria-current={{if (eq @dc.Name item.Name) 'true'}}
|
aria-current={{if (eq @dc.Name item.Name) 'true'}}
|
||||||
class={{class-map
|
class={{class-map
|
||||||
|
|
|
@ -38,14 +38,12 @@ common usecase of having a floating menu.
|
||||||
<DisclosureMenu as |disclosure|>
|
<DisclosureMenu as |disclosure|>
|
||||||
<disclosure.Action
|
<disclosure.Action
|
||||||
{{on 'click' disclosure.toggle}}
|
{{on 'click' disclosure.toggle}}
|
||||||
{{css-prop 'height' returns=(set this 'height')}}
|
|
||||||
>
|
>
|
||||||
{{if disclosure.expanded 'Close' 'Open'}}
|
{{if disclosure.expanded 'Close' 'Open'}}
|
||||||
</disclosure.Action>
|
</disclosure.Action>
|
||||||
<disclosure.Menu
|
<disclosure.Menu
|
||||||
style={{style-map
|
style={{style-map
|
||||||
(array 'position' 'absolute')
|
(array 'position' 'absolute')
|
||||||
(array 'top' this.height)
|
|
||||||
(array 'background-color' 'rgb(var(--tone-gray-000))')
|
(array 'background-color' 'rgb(var(--tone-gray-000))')
|
||||||
}}
|
}}
|
||||||
as |panel|>
|
as |panel|>
|
||||||
|
@ -62,11 +60,53 @@ common usecase of having a floating menu.
|
||||||
</figure>
|
</figure>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`DisclosureMenu` also supports virtually scrolling its menu items for when you have 1000s of items to display in the menu whilst avoiding DOM stuttering. The set up is a tinsy bit more involved. but eesnetially you provide the data items for the menu using the `@items` argument, and then you can loop through these in the menu to make/use your menu items. The `menu.items` property is automatically paged for you depending on the scroll position of the menu panel. Importantly, right now, you should provide a height value for each menu.item using the `--paged-row-height` CSS property, you can do this inline or within your CSS (preferred). If you don't do this the component is unable to calculate the size of the scroll track/thumb. In the future (when needed) we will provide a callback for each item so you can specify a function to calculate the size of each individual item to give us a little more flexibility on what we can do with this component.
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<DataSource
|
||||||
|
@src={{uri
|
||||||
|
'/${partition}/${nspace}/${dc}/nodes'
|
||||||
|
(hash
|
||||||
|
nspace=''
|
||||||
|
partition=''
|
||||||
|
dc='dc-1'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
as |source|>
|
||||||
|
<DisclosureMenu
|
||||||
|
@items={{source.data}}
|
||||||
|
as |disclosure|>
|
||||||
|
<disclosure.Action
|
||||||
|
{{on 'click' disclosure.toggle}}
|
||||||
|
>
|
||||||
|
{{if disclosure.expanded 'Close' 'Open'}}
|
||||||
|
</disclosure.Action>
|
||||||
|
<disclosure.Menu
|
||||||
|
style={{style-map
|
||||||
|
(array 'position' 'absolute')
|
||||||
|
(array 'max-height' '360' 'px')
|
||||||
|
(array 'width' '560' 'px')
|
||||||
|
(array '--paged-row-height' '42px')
|
||||||
|
}}
|
||||||
|
as |panel|>
|
||||||
|
<panel.Menu as |menu|>
|
||||||
|
{{#each menu.items as |item|}}
|
||||||
|
<menu.Item>
|
||||||
|
<menu.Action>{{item.Node}}</menu.Action>
|
||||||
|
</menu.Item>
|
||||||
|
{{/each}}
|
||||||
|
</panel.Menu>
|
||||||
|
</disclosure.Menu>
|
||||||
|
</DisclosureMenu>
|
||||||
|
</DataSource>
|
||||||
|
```
|
||||||
|
|
||||||
## Arguments
|
## Arguments
|
||||||
|
|
||||||
| Argument | Type | Default | Description |
|
| Argument | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `expanded` | `Boolean` | false | The _initial_ state of the disclosure. Please note: this is the _initial_ state only, please use the `disclosure.open` and `disclosure.close` for controling the state. |
|
| `expanded` | `Boolean` | false | The _initial_ state of the disclosure. Please note: this is the _initial_ state only, please use the `disclosure.open` and `disclosure.close` for controling the state. |
|
||||||
|
| `items` | `object[]` | | When using a paginated menu you should add all possible items to this arguments and then uses the disclosure.Panel.Menu.items property for `each`ing through, see above example |
|
||||||
|
|
||||||
## Exported API
|
## Exported API
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,11 @@
|
||||||
as |disclosure|>
|
as |disclosure|>
|
||||||
{{yield (hash
|
{{yield (hash
|
||||||
Action=(component 'disclosure-menu/action' disclosure=disclosure)
|
Action=(component 'disclosure-menu/action' disclosure=disclosure)
|
||||||
Menu=(component 'disclosure-menu/menu' disclosure=disclosure)
|
Menu=(component 'disclosure-menu/menu'
|
||||||
|
disclosure=disclosure
|
||||||
|
items=@items
|
||||||
|
rowHeight=@rowHeight
|
||||||
|
)
|
||||||
disclosure=disclosure
|
disclosure=disclosure
|
||||||
toggle=disclosure.toggle
|
toggle=disclosure.toggle
|
||||||
close=disclosure.close
|
close=disclosure.close
|
||||||
|
|
|
@ -3,4 +3,6 @@
|
||||||
}
|
}
|
||||||
.disclosure-menu [aria-expanded] ~ * {
|
.disclosure-menu [aria-expanded] ~ * {
|
||||||
@extend %menu-panel;
|
@extend %menu-panel;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
will-change: scrollPosition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,25 @@
|
||||||
<@disclosure.Details as |details|>
|
<@disclosure.Details as |details|>
|
||||||
<div
|
<PagedCollection
|
||||||
{{on-outside 'click' @disclosure.close}}
|
@items={{or @items (array)}}
|
||||||
...attributes
|
as |pager|>
|
||||||
>
|
<div
|
||||||
{{yield (hash
|
{{on-outside 'click' @disclosure.close}}
|
||||||
Menu=(component 'menu' disclosure=@disclosure)
|
{{did-insert pager.viewport}}
|
||||||
)}}
|
{{on-resize pager.resize}}
|
||||||
</div>
|
{{css-prop '--paged-row-height' returns=pager.rowHeight}}
|
||||||
|
{{css-prop 'max-height' returns=pager.maxHeight}}
|
||||||
|
class={{class-map
|
||||||
|
(array 'paged-collection-scroll' (contains pager.type (array 'virtual-scroll' 'native-scroll')))
|
||||||
|
}}
|
||||||
|
...attributes
|
||||||
|
>
|
||||||
|
{{yield (hash
|
||||||
|
Menu=(component 'menu'
|
||||||
|
disclosure=@disclosure
|
||||||
|
pager=pager
|
||||||
|
)
|
||||||
|
)}}
|
||||||
|
</div>
|
||||||
|
</PagedCollection>
|
||||||
</@disclosure.Details>
|
</@disclosure.Details>
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,21 @@
|
||||||
@extend %main-nav-vertical-hoisted;
|
@extend %main-nav-vertical-hoisted;
|
||||||
left: 100px;
|
left: 100px;
|
||||||
}
|
}
|
||||||
nav .dcs .menu-panel {
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
|
||||||
nav li.partitions,
|
nav li.partitions,
|
||||||
nav li.nspaces {
|
nav li.nspaces {
|
||||||
@extend %main-nav-vertical-popover-menu;
|
@extend %main-nav-vertical-popover-menu;
|
||||||
/* --panel-height: 300px;
|
}
|
||||||
--row-height: 43px; */
|
nav li.dcs [aria-expanded] ~ * {
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
nav li.dcs [aria-expanded] ~ * {
|
||||||
|
max-height: 560px;
|
||||||
|
--paged-row-height: 43px;
|
||||||
|
}
|
||||||
|
nav li.partitions [aria-expanded] ~ *,
|
||||||
|
nav li.nspaces [aria-expanded] ~ * {
|
||||||
|
max-height: 360px;
|
||||||
|
--paged-row-height: 43px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[role='banner'] a svg {
|
[role='banner'] a svg {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<ul
|
<ul
|
||||||
role="menu"
|
role="menu"
|
||||||
aria-labelledby={{@disclosure.button}}
|
style={{{style-map
|
||||||
id={{@disclosure.panel}}
|
(array 'height' (if (and @pager (not-eq @pager.type 'native-scroll')) @pager.totalHeight) 'px')
|
||||||
...attributes
|
(array '--paged-start' (if (and @pager (not-eq @pager.type 'native-scroll')) @pager.startHeight) 'px')
|
||||||
|
}}}
|
||||||
|
{{did-insert (optional @pager.pane)}}
|
||||||
{{aria-menu
|
{{aria-menu
|
||||||
onclose=(or @onclose @disclosure.close)
|
onclose=(or @onclose @disclosure.close)
|
||||||
openEvent=(or @event @disclosure.event)
|
openEvent=(or @event @disclosure.event)
|
||||||
|
@ -12,5 +14,6 @@
|
||||||
Action=(component 'menu/action' disclosure=@disclosure)
|
Action=(component 'menu/action' disclosure=@disclosure)
|
||||||
Item=(component 'menu/item')
|
Item=(component 'menu/item')
|
||||||
Separator=(component 'menu/separator')
|
Separator=(component 'menu/separator')
|
||||||
|
items=@pager.items
|
||||||
)}}
|
)}}
|
||||||
</ul>
|
</ul>
|
|
@ -0,0 +1,109 @@
|
||||||
|
# PagedCollection
|
||||||
|
|
||||||
|
A renderless component to act as a helper for different types of pagination.
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<figure>
|
||||||
|
<figcaption>
|
||||||
|
Provide a widget so we can try switching between two pagination methods
|
||||||
|
</figcaption>
|
||||||
|
<select
|
||||||
|
onchange={{action (mut this.type) value="target.value"}}
|
||||||
|
>
|
||||||
|
<option>virtual-scroll</option>
|
||||||
|
<option>index</option>
|
||||||
|
</select>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<figcaption>Get some data and page it</figcaption>
|
||||||
|
<DataSource
|
||||||
|
@src={{uri "/partition/default/dc-1/nodes"}}
|
||||||
|
as |source|>
|
||||||
|
<PagedCollection
|
||||||
|
@type={{or this.type "virtual-scroll"}}
|
||||||
|
@items={{or source.data (array)}}
|
||||||
|
@perPage={{8}}
|
||||||
|
@page={{or this.page 1}}
|
||||||
|
@rowHeight="43"
|
||||||
|
as |pager|>
|
||||||
|
<div
|
||||||
|
style={{{style-map
|
||||||
|
(array 'outline' '1px solid rgb(var(--tone-gray-300))')
|
||||||
|
(array 'max-height' '360' 'px')
|
||||||
|
(array '--paged-row-height' (if (not-eq this.type 'index') '43px'))
|
||||||
|
}}}
|
||||||
|
{{did-insert pager.viewport}}
|
||||||
|
{{on-resize pager.resize}}
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
style={{{style-map
|
||||||
|
(array 'height' pager.totalHeight 'px')
|
||||||
|
(array '--paged-start' pager.startHeight 'px')
|
||||||
|
}}}
|
||||||
|
>
|
||||||
|
{{#each pager.items as |item|}}
|
||||||
|
<li
|
||||||
|
style={{{style-map
|
||||||
|
(array 'height' '43' 'px')
|
||||||
|
(array 'outline' '1px solid rgb(var(--tone-gray-100))')
|
||||||
|
}}}
|
||||||
|
>
|
||||||
|
{{item.Node}}
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<pager.Pager>
|
||||||
|
<div
|
||||||
|
style={{{style-map
|
||||||
|
(array 'display' 'flex')
|
||||||
|
(array 'flex-direction' (if (not-eq pager.page pager.totalPages) 'row-reverse'))
|
||||||
|
(array 'justify-content' 'space-between')
|
||||||
|
}}}
|
||||||
|
>
|
||||||
|
{{#if (not-eq pager.page pager.totalPages)}}
|
||||||
|
<Action
|
||||||
|
{{on 'click' (set this 'page' (add pager.page 1))}}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Action>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (not-eq pager.page 1)}}
|
||||||
|
<Action
|
||||||
|
{{on 'click' (set this 'page' (sub pager.page 1))}}
|
||||||
|
>
|
||||||
|
Prev
|
||||||
|
</Action>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</pager.Pager>
|
||||||
|
</PagedCollection>
|
||||||
|
</DataSource>
|
||||||
|
</figure>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
| Argument | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `type` | `(native-scroll \| virtual-scroll \| index)` | `native-scroll` | The type of pagination |
|
||||||
|
| `items` | `array` | `undefined` | An array or items to be paginated |
|
||||||
|
| `rowHeight` | `(string \| number)` | `undefined` | When `@type=virtual-scroll` this informs the scroller of the size of each row in the scroll pane. For the moment this _must_ be the same for every row. |
|
||||||
|
| `page` | `number` | `undefined` | When `@type=index` this is the current page number to show |
|
||||||
|
| `perPage` | `number` | `undefined` | When `@type=index` this is the amount of rows to show per page |
|
||||||
|
|
||||||
|
## Exported API
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `items` | `array` | An array of the items to be shown on the current page |
|
||||||
|
| `page` | `number` | The current page number (a mirror of @page) |
|
||||||
|
| `resize` | `Function` | Function to be called on the resize of the viewport |
|
||||||
|
| `viewport` | `Function` | Function to be called on the `did-insert` of the viewport to be used for scrolling |
|
||||||
|
| `rowHeight` | `Function` | Function to be called to set the rowHeight of the virtual-scroller |
|
||||||
|
| `startHeight` | `number` | Size of the area before the panel to be virtually-scroller, usually you should use this to set `--paged-start` wrapping element of the scrollable items |
|
||||||
|
| `totalHeight` | `number` | Size of the of the entire panel in order to show the correctly sized scroll thumb |
|
||||||
|
| `totalPages` | `number` | Totol number of pages |
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{{yield (hash
|
||||||
|
items=this.items
|
||||||
|
page=@page
|
||||||
|
pane=(fn this.setPane)
|
||||||
|
resize=(fn this.resize)
|
||||||
|
viewport=(fn this.setViewport)
|
||||||
|
rowHeight=(fn this.setRowHeight)
|
||||||
|
maxHeight=(fn this.setMaxHeight)
|
||||||
|
startHeight=this.startHeight
|
||||||
|
totalHeight=this.totalHeight
|
||||||
|
totalPages=this.totalPages
|
||||||
|
Pager=(if (eq type "index") (component 'yield') '')
|
||||||
|
)}}
|
||||||
|
|
||||||
|
{{will-destroy this.disconnect}}
|
|
@ -0,0 +1,125 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
|
export default class PagedCollectionComponent extends Component {
|
||||||
|
|
||||||
|
@tracked $pane;
|
||||||
|
@tracked $viewport;
|
||||||
|
|
||||||
|
@tracked top = 0;
|
||||||
|
@tracked visibleItems = 0;
|
||||||
|
@tracked overflow = 10;
|
||||||
|
@tracked _rowHeight = 0;
|
||||||
|
|
||||||
|
@tracked _type = 'native-scroll';
|
||||||
|
|
||||||
|
get type() {
|
||||||
|
return this.args.type || this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
get items() {
|
||||||
|
return this.args.items.slice(this.cursor, this.cursor + this.perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
get perPage() {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'virtual-scroll':
|
||||||
|
return this.visibleItems + (this.overflow * 2);
|
||||||
|
case 'index':
|
||||||
|
return parseInt(this.args.perPage);
|
||||||
|
}
|
||||||
|
// 'native-scroll':
|
||||||
|
return this.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
get cursor() {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'virtual-scroll':
|
||||||
|
return this.itemsBefore;
|
||||||
|
case 'index':
|
||||||
|
return (parseInt(this.args.page) - 1) * this.perPage;
|
||||||
|
}
|
||||||
|
// 'native-scroll':
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get itemsBefore() {
|
||||||
|
if(typeof this.$viewport === 'undefined') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.max(0, Math.round(this.top / this.rowHeight) - this.overflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
get rowHeight() {
|
||||||
|
return parseFloat(this.args.rowHeight || this._rowHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
get startHeight() {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'virtual-scroll':
|
||||||
|
return Math.min(this.totalHeight, this.itemsBefore * this.rowHeight);
|
||||||
|
case 'index':
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// 'native-scroll':
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalHeight() {
|
||||||
|
return this.total * this.rowHeight;
|
||||||
|
}
|
||||||
|
get totalPages() {
|
||||||
|
return Math.ceil(this.total / this.perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
get total() {
|
||||||
|
return this.args.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
scroll(e) {
|
||||||
|
this.top = this.$viewport.scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
resize() {
|
||||||
|
if(this.$viewport.clientHeight > 0 && this.rowHeight > 0) {
|
||||||
|
this.visibleItems = Math.ceil(this.$viewport.clientHeight / this.rowHeight);
|
||||||
|
} else {
|
||||||
|
this.visibleItems = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setViewport($viewport) {
|
||||||
|
this.$viewport = $viewport === 'html' ? [...document.getElementsByTagName('html')][0] : $viewport;
|
||||||
|
this.$viewport.addEventListener('scroll', this.scroll);
|
||||||
|
if($viewport === 'html') {
|
||||||
|
this.$viewport.addEventListener('resize', this.resize);
|
||||||
|
}
|
||||||
|
this.scroll();
|
||||||
|
this.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setPane($pane) {
|
||||||
|
this.$pane = $pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setRowHeight(str) {
|
||||||
|
this._rowHeight = parseFloat(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setMaxHeight(str) {
|
||||||
|
const maxHeight = parseFloat(str);
|
||||||
|
if(!isNaN(maxHeight)) {
|
||||||
|
this._type = 'virtual-scroll';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
disconnect() {
|
||||||
|
this.$viewport.removeEventListener('scroll', this.scroll);
|
||||||
|
this.$viewport.removeEventListener('resize', this.resize);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
%paged-collection-scroll {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
will-change: scrollPosition;
|
||||||
|
}
|
||||||
|
.paged-collection-scroll {
|
||||||
|
@extend %paged-collection-scroll;
|
||||||
|
}
|
||||||
|
[style*="--paged-row-height"] {
|
||||||
|
@extend %paged-collection-scroll;
|
||||||
|
}
|
||||||
|
[style*="--paged-start"]::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: var(--paged-start);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{{yield}}
|
|
@ -27,6 +27,7 @@
|
||||||
@import 'consul-ui/components/oidc-select';
|
@import 'consul-ui/components/oidc-select';
|
||||||
@import 'consul-ui/components/radio-card';
|
@import 'consul-ui/components/radio-card';
|
||||||
@import 'consul-ui/components/panel';
|
@import 'consul-ui/components/panel';
|
||||||
|
@import 'consul-ui/components/paged-collection';
|
||||||
@import 'consul-ui/components/pill';
|
@import 'consul-ui/components/pill';
|
||||||
@import 'consul-ui/components/popover-menu';
|
@import 'consul-ui/components/popover-menu';
|
||||||
@import 'consul-ui/components/popover-select';
|
@import 'consul-ui/components/popover-select';
|
||||||
|
|
Loading…
Reference in New Issue