Added static members filtering

- added filter builder dropdown (alpha)
- refactored related CSS
This commit is contained in:
Peter Zimon 2021-07-23 16:05:22 +02:00
parent c019ef5cb0
commit 5255489779
15 changed files with 397 additions and 95 deletions

View File

@ -174,7 +174,7 @@
</div>
<span class="action-menu">
<GhDropdownButton @dropdownName="subscription-menu-{{sub.id}}" @classNames="gh-btn gh-btn-outline gh-btn-icon gh-btn-subscription-action only-has-icon" @title="Actions">
<GhDropdownButton @dropdownName="subscription-menu-{{sub.id}}" @classNames="gh-btn gh-btn-outline gh-btn-icon gh-btn-subscription-action icon-only" @title="Actions">
<span>
{{svg-jar "dotdotdot"}}
<span class="hidden">Subscription menu</span>
@ -227,7 +227,7 @@
<div class="period">yearly</div>
</div>
<span class="action-menu">
<GhDropdownButton @dropdownName="subscription-menu-complimentary" @classNames="gh-btn gh-btn-outline gh-btn-icon gh-btn-subscription-action only-has-icon" @title="Actions">
<GhDropdownButton @dropdownName="subscription-menu-complimentary" @classNames="gh-btn gh-btn-outline gh-btn-icon gh-btn-subscription-action icon-only" @title="Actions">
<span>
{{svg-jar "dotdotdot"}}
<span class="hidden">Subscription menu</span>

View File

@ -1,76 +1,195 @@
<span class="dropdown dropdown-topmenu">
<span class="{{if @selectedLabel.slug "gh-contentfilter-selected"}}">
{{#if (feature "membersFiltering")}}
<span class="dropdown">
<GhDropdownButton
@dropdownName="members-label-menu"
@classNames="gh-contentfilter-menu-trigger"
@title="Member Labels"
data-test-button="labels-filter"
@dropdownName="members-actions-filter"
@classNames="gh-btn gh-btn-icon gh-btn-action-icon"
@title="Members Actions"
data-test-button="members-actions"
>
<span class="gh-btn-filter-maxwidth" title="{{@selectedLabel.name}}">
<span>{{@selectedLabel.name}}</span>
{{svg-jar "arrow-down-small"}}
</span>
<span>
{{svg-jar "filter"}}
Filter
</span>
</GhDropdownButton>
</span>
<GhDropdown
@name="members-label-menu"
@name="members-actions-filter"
@tagName="div"
@classNames="dropdown-menu dropdown-triangle-top-right dropdown-action members-label-list"
@classNames="gh-member-actions-menu gh-filter-builder gh-members-filter-builder dropdown-menu dropdown-triangle-top-right"
>
<ul class="dropdown-content">
{{#each @availableLabels as |label|}}
<li class="{{if (eq @selectedLabel.name label.name) "selected"}}">
<a>
<span class="dropdown-label" title="{{label.name}}" {{on "click" (fn @onLabelChange label)}} data-test-label-filter={{label.name}}>{{label.name}} </span>
{{#if label.slug}}
<span class="dropdown-action-icon" {{on "click" (fn @onLabelEdit label.slug)}}> {{svg-jar "pen"}} </span>
{{/if}}
</a>
</li>
{{/each}}
</ul>
<ul class="dropdown-footer">
<li>
<a {{on "click" (fn @onLabelAdd)}}>
<span>
{{svg-jar "add"}}
Add Label
</span>
</a>
</li>
</ul>
<h3>Filter list</h3>
<section class="gh-filters">
<div class="gh-filter-block">
<GhFormGroup @property="filter-1" @classNames="max-width">
<div class="gh-filter-inputgroup">
<span class="gh-select">
<OneWaySelect
@options={{this.availableFilterProperties}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@groupLabelPath="group"
/>
{{svg-jar "arrow-down-small"}}
</span>
<span class="gh-select">
<OneWaySelect
@options={{this.availableFilterRelations}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
/>
{{svg-jar "arrow-down-small"}}
</span>
<GhTextInput @id="filter-value-1" @name="filter-value-1" />
<button class="gh-delete-filter" title="Delete filter">{{svg-jar "close"}}<span class="hidden">Delete filter</span></button>
</div>
<GhErrorMessage @errors={{member.errors}} @property="filter-property-1" />
</GhFormGroup>
</div>
{{!-- <div class="gh-filter-block-divider">And</div> --}}
<div class="gh-filter-block">
<GhFormGroup @property="filter-2" @classNames="max-width">
<div class="gh-filter-inputgroup">
<span class="gh-select">
<OneWaySelect
@options={{this.availableFilterProperties}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@groupLabelPath="group"
/>
{{svg-jar "arrow-down-small"}}
</span>
<span class="gh-select">
<OneWaySelect
@options={{this.availableFilterRelations}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
/>
{{svg-jar "arrow-down-small"}}
</span>
<GhTextInput @id="filter-value-2" @name="filter-value-2" />
<button class="gh-delete-filter" title="Delete filter">{{svg-jar "close"}}<span class="hidden">Delete filter</span></button>
</div>
<GhErrorMessage @errors={{member.errors}} @property="filter-property-2" />
</GhFormGroup>
</div>
{{!-- <div class="gh-filter-block-divider">And</div> --}}
<div class="gh-filter-block">
<GhFormGroup @property="filter-3" @classNames="max-width">
<div class="gh-filter-inputgroup">
<span class="gh-select">
<OneWaySelect
@options={{this.availableFilterProperties}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
@groupLabelPath="group"
/>
{{svg-jar "arrow-down-small"}}
</span>
<span class="gh-select">
<OneWaySelect
@options={{this.availableFilterRelations}}
@optionValuePath="name"
@optionLabelPath="label"
@optionTargetPath="name"
/>
{{svg-jar "arrow-down-small"}}
</span>
<GhTextInput @id="filter-value-3" @name="filter-value-3" />
<button class="gh-delete-filter" title="Delete filter">{{svg-jar "close"}}<span class="hidden">Delete filter</span></button>
</div>
<GhErrorMessage @errors={{member.errors}} @property="filter-property-3" />
</GhFormGroup>
</div>
<button type="button" class="gh-btn gh-btn-text gh-btn-link green gh-btn-icon gh-add-filter">
<span>{{svg-jar "add"}} Add filter</span>
</button>
</section>
<div class="gh-filter-builder-footer">
<button class="gh-btn gh-btn-primary gh-btn-icon">
<span>Apply filter</span>
</button>
</div>
</GhDropdown>
</span>
{{else}}
<span class="dropdown dropdown-topmenu">
<span class="{{if @selectedLabel.slug "gh-contentfilter-selected"}}">
<GhDropdownButton
@dropdownName="members-label-menu"
@classNames="gh-contentfilter-menu-trigger"
@title="Member Labels"
data-test-button="labels-filter"
>
<span class="gh-btn-filter-maxwidth" title="{{@selectedLabel.name}}">
<span>{{@selectedLabel.name}}</span>
{{svg-jar "arrow-down-small"}}
</span>
</GhDropdownButton>
</span>
<GhDropdown
@name="members-label-menu"
@tagName="div"
@classNames="dropdown-menu dropdown-triangle-top-right dropdown-action members-label-list"
>
<ul class="dropdown-content">
{{#each @availableLabels as |label|}}
<li class="{{if (eq @selectedLabel.name label.name) "selected"}}">
<a>
<span class="dropdown-label" title="{{label.name}}" {{on "click" (fn @onLabelChange label)}} data-test-label-filter={{label.name}}>{{label.name}} </span>
{{#if label.slug}}
<span class="dropdown-action-icon" {{on "click" (fn @onLabelEdit label.slug)}}> {{svg-jar "pen"}} </span>
{{/if}}
</a>
</li>
{{/each}}
</ul>
<ul class="dropdown-footer">
<li>
<a {{on "click" (fn @onLabelAdd)}}>
<span>
{{svg-jar "add"}}
Add Label
</span>
</a>
</li>
</ul>
</GhDropdown>
</span>
<div class="gh-contentfilter-menu {{if @selectedPaidParam.value "gh-contentfilter-selected"}}" data-test-select="paidParam">
<PowerSelect
@selected={{@selectedPaidParam}}
@options={{@availablePaidParams}}
@searchEnabled={{false}}
@onChange={{@onPaidParamChange}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@searchPlaceholder="Search authors"
@matchTriggerWidth={{false}}
as |paidParam|
>
{{#if paidParam.name}}{{paidParam.name}}{{else}}<span class="red">Unknown paid status</span>{{/if}}
</PowerSelect>
</div>
<div class="gh-contentfilter-menu {{if @selectedPaidParam.value "gh-contentfilter-selected"}}" data-test-select="paidParam">
<PowerSelect
@selected={{@selectedPaidParam}}
@options={{@availablePaidParams}}
@searchEnabled={{false}}
@onChange={{@onPaidParamChange}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@searchPlaceholder="Search authors"
@matchTriggerWidth={{false}}
as |paidParam|
>
{{#if paidParam.name}}{{paidParam.name}}{{else}}<span class="red">Unknown paid status</span>{{/if}}
</PowerSelect>
</div>
<div class="gh-contenfilter-menu gh-contentfilter-sort" data-test-select="members-order">
<PowerSelect
@selected={{@selectedOrder}}
@options={{@availableOrders}}
@searchEnabled={{false}}
@onChange={{@onOrderChange}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@matchTriggerWidth={{false}}
as |order|
>
{{#if order.name}}{{order.name}}{{else}}<span class="red">Unknown</span>{{/if}}
</PowerSelect>
</div>
<div class="gh-contenfilter-menu gh-contentfilter-sort" data-test-select="members-order">
<PowerSelect
@selected={{@selectedOrder}}
@options={{@availableOrders}}
@searchEnabled={{false}}
@onChange={{@onOrderChange}}
@triggerComponent="gh-power-select/trigger"
@triggerClass="gh-contentfilter-menu-trigger"
@dropdownClass="gh-contentfilter-menu-dropdown"
@matchTriggerWidth={{false}}
as |order|
>
{{#if order.name}}{{order.name}}{{else}}<span class="red">Unknown</span>{{/if}}
</PowerSelect>
</div>
{{/if}}

View File

@ -1,6 +1,46 @@
import Component from '@glimmer/component';
import {inject as service} from '@ember/service';
const FILTER_PROPERTIES = [
// Basic
{label: 'Name', name: 'x', group: 'Basic'},
{label: 'Email', name: 'x', group: 'Basic'},
{label: 'Location', name: 'x', group: 'Basic'},
{label: 'Newsletter subscription status', name: 'x', group: 'Basic'},
{label: 'Label', name: 'x', group: 'Basic'},
// Member subscription
{label: 'Member status', name: 'x', group: 'Subscription'},
{label: 'Tier', name: 'x', group: 'Subscription'},
{label: 'Billing period', name: 'x', group: 'Subscription'},
// Emails
{label: 'Emails sent (all time)', name: 'x', group: 'Email'},
{label: 'Emails opened (all time)', name: 'x', group: 'Email'},
{label: 'Open rate (all time)', name: 'x', group: 'Email'},
{label: 'Emails sent (30 days)', name: 'x', group: 'Email'},
{label: 'Emails opened (30 days)', name: 'x', group: 'Email'},
{label: 'Open rate (30 days)', name: 'x', group: 'Email'},
{label: 'Emails sent (60 days)', name: 'x', group: 'Email'},
{label: 'Emails opened (60 days)', name: 'x', group: 'Email'},
{label: 'Open rate (60 days)', name: 'x', group: 'Email'},
{label: 'Stripe subscription status', name: 'x', group: 'Email'}
];
const FILTER_RELATIONS = [
{label: 'is', name: 'x'},
{label: 'is not', name: 'x'},
{label: 'contains', name: 'x'},
{label: 'exists', name: 'x'},
{label: 'does not exist', name: 'x'}
];
export default class GhMembersFilterComponent extends Component {
@service session
constructor(...args) {
super(...args);
this.availableFilterProperties = FILTER_PROPERTIES;
this.availableFilterRelations = FILTER_RELATIONS;
}
}

View File

@ -6,7 +6,7 @@
*/
@import "spirit/spirit-dark.css";
/* Patterns: Groups of Styles
/* Patterns
/* ---------------------------------------------------------- */
@import "patterns/global.css";
@import "patterns/icons.css";
@ -18,7 +18,7 @@
@import "patterns/boxes.css";
/* Components: Groups of Patterns
/* Components
/* ---------------------------------------------------------- */
@import "components/loading-indicator.css";
@import "components/modals.css";
@ -40,9 +40,10 @@
@import "components/browser-preview.css";
@import "components/stacks.css";
@import "components/browser-preview.css";
@import "components/filter-builder.css";
/* Layouts: Groups of Components
/* Layouts
/* ---------------------------------------------------------- */
@import "layouts/main.css";
@import "layouts/flow.css";

View File

@ -6,7 +6,7 @@
*/
@import "spirit/spirit.css";
/* Patterns: Groups of Styles
/* Patterns
/* ---------------------------------------------------------- */
@import "patterns/global.css";
@import "patterns/icons.css";
@ -18,7 +18,7 @@
@import "patterns/boxes.css";
/* Components: Groups of Patterns
/* Components
/* ---------------------------------------------------------- */
@import "components/loading-indicator.css";
@import "components/modals.css";
@ -40,9 +40,10 @@
@import "components/browser-preview.css";
@import "components/stacks.css";
@import "components/browser-preview.css";
@import "components/filter-builder.css";
/* Layouts: Groups of Components
/* Layouts
/* ---------------------------------------------------------- */
@import "layouts/main.css";
@import "layouts/flow.css";

View File

@ -0,0 +1,111 @@
.gh-filter-builder {
padding: 20px;
max-width: 780px;
min-width: 400px;
}
.gh-filter-builder h3 {
font-size: 1.9rem;
font-weight: 600;
letter-spacing: 0.2px;
}
.gh-filter-builder .gh-filters {
display: grid;
grid-template-columns: 1fr;
grid-gap: 12px;
background: var(--whitegrey-l1);
border-radius: 3px;
padding: 16px;
margin-top: 20px;
}
.gh-filter-builder .gh-filter-block {
display: flex;
align-items: center;
}
.gh-filter-builder .gh-filter-block .form-group {
margin: 0;
}
.gh-filter-builder .gh-filter-inputgroup {
display: grid;
grid-template-columns: 1fr 130px 1fr 18px;
grid-column-gap: 8px;
}
.gh-filter-builder .gh-input,
.gh-filter-builder .gh-select,
.gh-filter-builder select {
height: 33px;
font-size: 1.35rem;
}
.gh-filter-builder .gh-select svg {
width: 9px;
height: 9px;
margin-right: 0;
}
.gh-filter-builder .gh-delete-filter {
margin-left: 4px;
}
.gh-filter-builder .gh-delete-filter svg {
width: 10px;
height: 10px;
}
.gh-filter-builder .gh-delete-filter svg path {
stroke: var(--middarkgrey);
}
.gh-filter-builder .gh-delete-filter:hover svg path {
stroke: var(--red);
}
.gh-add-filter svg {
margin-right: 7px;
}
.gh-add-filter svg path {
stroke: none !important;
fill: var(--green-d1);
}
.gh-filter-builder .gh-filter-block-divider {
display: flex;
align-items: center;
font-size: 1.1rem;
font-weight: 500;
letter-spacing: .1px;
color: var(--midgrey);
text-transform: uppercase;
margin: 12px 0;
}
.gh-filter-builder .gh-filter-block-divider::before {
content: "";
display: block;
width: 16px;
height: 1px;
background: var(--whitegrey-d2);
margin: 0 4px 0 -16px;
}
.gh-filter-builder .gh-filter-block-divider::after {
content: "";
flex-grow: 1;
display: block;
height: 1px;
background: var(--whitegrey-d2);
margin: 0 -16px 0 4px;
}
.gh-filter-builder-footer {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 20px;
}

View File

@ -1417,13 +1417,13 @@
font-size: 1.35rem;
}
.view-actions .gh-btn:not(.gh-btn-primary):not(.gh-btn-blue):not(.gh-btn-green) {
.view-actions .gh-btn:not(.gh-btn-primary):not(.gh-btn-blue):not(.gh-btn-green):not(.gh-btn-link) {
border: none;
box-shadow: none;
background: color-mod(var(--whitegrey-l1) l(-3%));
}
.view-actions .gh-btn:not(.gh-btn-primary):not(.gh-btn-blue):not(.gh-btn-green):hover {
.view-actions .gh-btn:not(.gh-btn-primary):not(.gh-btn-blue):not(.gh-btn-green):not(.gh-btn-link):hover {
background: var(--whitegrey);
}
@ -1447,20 +1447,6 @@
fill: color-mod(var(--midlightgrey) l(+5%));
}
.gh-actions-cog {
margin-right: 10px
}
.gh-actions-cog svg {
height: 16px;
width: 16px;
margin-right: 0;
}
.gh-actions-cog svg path {
stroke: currentColor;
}
.gh-actions-menu {
top: calc(100% + 6px);
right: 10px;

View File

@ -1802,3 +1802,7 @@ p.gh-members-import-errordetail:first-of-type {
.gh-member-addcomp-warning {
margin-top: -16px;
}
.gh-members-filter-builder {
width: 720px;
}

View File

@ -361,6 +361,24 @@ svg.gh-btn-icon-right {
color: var(--red);
}
.gh-btn-action-icon {
margin-right: 10px
}
.gh-btn-action-icon svg {
height: 16px;
width: 16px;
margin: 0;
}
.gh-btn-action-icon:not(.icon-only) svg {
margin-right: 10px;
}
.gh-btn-action-icon svg path {
stroke: currentColor;
}
/*
/* Button Variations

View File

@ -15,7 +15,7 @@
<LinkTo class="gh-btn gh-btn-green" @route="launch"><span>Start setup guide</span></LinkTo>
<div class="gh-dashboard-dismiss">
<GhDropdownButton @dropdownName="launch-wizard-dismiss"
@classNames="gh-btn gh-btn-icon only-has-icon gh-dashboard-dismissbutton dark">
@classNames="gh-btn gh-btn-icon icon-only gh-dashboard-dismissbutton dark">
<span>
{{svg-jar "dotdotdot"}}
</span>

View File

@ -110,7 +110,7 @@
{{/if}}
</div>
<button type="button" class="settings-menu-toggle gh-btn gh-btn-editor gh-btn-icon only-has-icon gh-actions-cog" title="Settings" {{on "click" this.toggleSettingsMenu}} data-test-psm-trigger>
<button type="button" class="settings-menu-toggle gh-btn gh-btn-editor gh-btn-icon icon-only gh-btn-action-icon" title="Settings" {{on "click" this.toggleSettingsMenu}} data-test-psm-trigger>
{{#if this.showSettingsMenu}}
<span class="settings-menu-open">{{svg-jar "sidemenu-open"}}</span>
{{else}}

View File

@ -3,6 +3,7 @@
<h2 class="gh-canvas-title" data-test-screen-title>Members</h2>
<section class="view-actions">
<div class="view-actions-bottom-row">
{{#unless (feature "membersFiltering")}}
<GhMembersFilter
@selectedLabel={{this.selectedLabel}}
@availableLabels={{this.availableLabels}}
@ -16,6 +17,7 @@
@selectedOrder={{this.selectedOrder}}
@onOrderChange={{this.changeOrder}}
/>
{{/unless}}
<div class="relative gh-members-header-search">
{{svg-jar "search" class="gh-input-search-icon"}}
<GhTextInput
@ -27,10 +29,25 @@
</div>
<div class="view-actions-top-row">
{{#if (feature "membersFiltering")}}
<GhMembersFilter
@selectedLabel={{this.selectedLabel}}
@availableLabels={{this.availableLabels}}
@onLabelChange={{this.changeLabel}}
@onLabelAdd={{this.addLabel}}
@onLabelEdit={{this.editLabel}}
@selectedPaidParam={{this.selectedPaidParam}}
@availablePaidParams={{this.paidParams}}
@onPaidParamChange={{this.changePaidParam}}
@availableOrders={{this.availableOrders}}
@selectedOrder={{this.selectedOrder}}
@onOrderChange={{this.changeOrder}}
/>
{{/if}}
<span class="dropdown">
<GhDropdownButton
@dropdownName="members-actions-menu"
@classNames="gh-btn gh-btn-icon only-has-icon gh-actions-cog"
@classNames="gh-btn gh-btn-icon icon-only gh-btn-action-icon"
@title="Members Actions"
data-test-button="members-actions"
>

View File

@ -8,7 +8,7 @@
<span class="dropdown">
<GhDropdownButton
@dropdownName="staff-actions-menu"
@classNames="gh-btn gh-btn-icon only-has-icon gh-actions-cog"
@classNames="gh-btn gh-btn-icon icon-only gh-btn-action-icon"
@title="Staff Actions"
data-test-button="staff-actions"
>

View File

@ -20,7 +20,7 @@
<section class="view-actions">
{{#if this.userActionsAreVisible}}
<span class="dropdown">
<GhDropdownButton @dropdownName="user-actions-menu" @classNames="gh-btn gh-btn-white gh-btn-icon only-has-icon user-actions-cog" @title="User Actions" data-test-user-actions={{true}}>
<GhDropdownButton @dropdownName="user-actions-menu" @classNames="gh-btn gh-btn-white gh-btn-icon icon-only user-actions-cog" @title="User Actions" data-test-user-actions={{true}}>
<span>
{{svg-jar "settings"}}
<span class="hidden">User Settings</span>

View File

@ -0,0 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 13.5022H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 9.00439L14 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.6875 4.50224H16.3125" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 438 B