Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.

Commit 5d49b08

Browse files
feat(#680): toggle list with popover (new standard UX)
Signed-off-by: peterpeterparker <[email protected]>
1 parent 096ea72 commit 5d49b08

File tree

5 files changed

+121
-35
lines changed

5 files changed

+121
-35
lines changed

studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, Element, Event, EventEmitter, h, Listen, Method, State, Prop} from '@stencil/core';
1+
import {Component, Element, Event, EventEmitter, h, Listen, Method, Prop, State} from '@stencil/core';
22
import {modalController, OverlayEventDetail, popoverController} from '@ionic/core';
33

44
import {Subject, Subscription} from 'rxjs';
@@ -14,6 +14,7 @@ import {RevealSlotUtils} from '../../../../../utils/editor/reveal-slot.utils';
1414
import {SlotType} from '../../../../../utils/editor/slot-type';
1515
import {SlotUtils} from '../../../../../utils/editor/slot.utils';
1616
import {AlignUtils, TextAlign} from '../../../../../utils/editor/align.utils';
17+
import {ListUtils} from '../../../../../utils/editor/list.utils';
1718

1819
import {EditAction} from '../../../../../utils/editor/edit-action';
1920
import {MoreAction} from '../../../../../utils/editor/more-action';
@@ -55,7 +56,7 @@ export class AppActionsElement {
5556
private align: TextAlign | undefined;
5657

5758
@State()
58-
private list: SlotType | undefined;
59+
private list: SlotType.OL | SlotType.UL | undefined;
5960

6061
@Event() private blockSlide: EventEmitter<boolean>;
6162

@@ -243,18 +244,6 @@ export class AppActionsElement {
243244
return element && element.nodeName && element.nodeName.toLowerCase() === SlotType.DRAG_RESIZE_ROTATE;
244245
}
245246

246-
private isElementList(element: HTMLElement): SlotType {
247-
if (!SlotUtils.isNodeList(element)) {
248-
return undefined;
249-
}
250-
251-
if (SlotUtils.isNodeRevealList(element)) {
252-
return element && element.getAttribute('list-tag') === SlotType.UL ? SlotType.UL : SlotType.OL;
253-
} else {
254-
return element && element.nodeName && element.nodeName.toLowerCase() === SlotType.OL ? SlotType.OL : SlotType.UL;
255-
}
256-
}
257-
258247
private isElementImage(element: HTMLElement): boolean {
259248
return element && element.nodeName && element.nodeName.toLowerCase() === SlotType.IMG;
260249
}
@@ -570,7 +559,7 @@ export class AppActionsElement {
570559
await modal.present();
571560
}
572561

573-
private async openSingleAction($event: UIEvent, component: 'app-reveal' | 'app-align') {
562+
private async openSingleAction($event: UIEvent, component: 'app-reveal' | 'app-align' | 'app-list') {
574563
if (this.slide) {
575564
return;
576565
}
@@ -590,6 +579,8 @@ export class AppActionsElement {
590579
await this.toggleReveal(detail.data.reveal);
591580
} else if (detail.data && component === 'app-align') {
592581
await this.updateAlignAttribute(detail.data.align);
582+
} else if (detail.data && component === 'app-list') {
583+
await this.toggleList(detail.data.list);
593584
}
594585
});
595586

@@ -749,7 +740,7 @@ export class AppActionsElement {
749740

750741
this.align = await AlignUtils.getAlignment(element);
751742

752-
this.list = this.isElementList(element);
743+
this.list = await ListUtils.isElementList(element);
753744

754745
if (element) {
755746
element.addEventListener('paste', this.cleanOnPaste, false);
@@ -879,23 +870,19 @@ export class AppActionsElement {
879870
});
880871
}
881872

882-
private toggleList(): Promise<void> {
873+
private toggleList(destinationListType: SlotType.OL | SlotType.UL): Promise<void> {
883874
return new Promise<void>(async (resolve) => {
884875
if (!this.selectedElement || !this.list) {
885876
resolve();
886877
return;
887878
}
888879

889-
const destinationListType: SlotType = this.list === SlotType.UL ? SlotType.OL : SlotType.UL;
890-
891880
if (SlotUtils.isNodeRevealList(this.selectedElement)) {
892881
await this.updateRevealListAttribute(destinationListType);
893882
} else {
894883
await this.toggleSlotType(destinationListType);
895884
}
896885

897-
this.list = destinationListType;
898-
899886
resolve();
900887
});
901888
}
@@ -989,7 +976,7 @@ export class AppActionsElement {
989976
} else if (detail.data.action === MoreAction.REVEAL) {
990977
await this.openSingleAction($event, 'app-reveal');
991978
} else if (detail.data.action === MoreAction.LIST) {
992-
await this.toggleList();
979+
await this.openSingleAction($event, 'app-list');
993980
}
994981
}
995982
});
@@ -1157,26 +1144,30 @@ export class AppActionsElement {
11571144
color="primary"
11581145
mode="md"
11591146
class={classAlign}>
1160-
<ion-icon src={`/assets/icons/align-${this.align}.svg`}></ion-icon>
1147+
{this.align !== undefined ? (
1148+
<ion-icon src={`/assets/icons/align-${this.align}.svg`}></ion-icon>
1149+
) : (
1150+
<ion-icon src={`/assets/icons/align-left.svg`}></ion-icon>
1151+
)}
11611152
<ion-label>Alignment</ion-label>
11621153
</ion-tab-button>
11631154
);
11641155
}
11651156

11661157
private renderList() {
1167-
const classListOL: string | undefined = this.list === SlotType.OL ? 'wider-devices' : 'wider-devices hidden';
1168-
const classListUL: string | undefined = this.list === SlotType.UL ? 'wider-devices' : 'wider-devices hidden';
1158+
const classList: string | undefined = this.list === undefined ? 'hidden wider-devices' : 'wider-devices';
11691159

1170-
return [
1171-
<ion-tab-button onClick={() => this.toggleList()} aria-label="Toggle to an unordered list" color="primary" mode="md" class={classListUL}>
1172-
<ion-icon src="/assets/icons/ionicons/list.svg"></ion-icon>
1173-
<ion-label>Unordered list</ion-label>
1174-
</ion-tab-button>,
1175-
<ion-tab-button onClick={() => this.toggleList()} aria-label="Toggle to an ordered list" color="primary" mode="md" class={classListOL}>
1176-
<ion-icon src="/assets/icons/list-ol.svg"></ion-icon>
1177-
<ion-label>Ordered list</ion-label>
1178-
</ion-tab-button>,
1179-
];
1160+
return (
1161+
<ion-tab-button
1162+
onClick={($event: UIEvent) => this.openSingleAction($event, 'app-list')}
1163+
aria-label="Edit ordered or unordered list"
1164+
color="primary"
1165+
mode="md"
1166+
class={classList}>
1167+
<ion-icon src={this.list === SlotType.OL ? '/assets/icons/list-ol.svg' : '/assets/icons/ionicons/list.svg'}></ion-icon>
1168+
<ion-label>List</ion-label>
1169+
</ion-tab-button>
1170+
);
11801171
}
11811172

11821173
private renderMore() {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
app-list {
2+
@import "../../../../global/theme/editor/editor-popover";
3+
4+
ion-item {
5+
--background-activated: transparent;
6+
--background-focused: transparent;
7+
--background-hover: transparent;
8+
9+
cursor: pointer;
10+
11+
&:hover,
12+
&.active {
13+
--color: var(--ion-color-primary);
14+
}
15+
}
16+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {Component, Element, h, Prop, State} from '@stencil/core';
2+
3+
import {SlotType} from '../../../utils/editor/slot-type';
4+
import {ListUtils} from '../../../utils/editor/list.utils';
5+
6+
@Component({
7+
tag: 'app-list',
8+
styleUrl: 'app-list.scss',
9+
})
10+
export class AppList {
11+
@Element() el: HTMLElement;
12+
13+
@Prop()
14+
selectedElement: HTMLElement;
15+
16+
@State()
17+
private currentList: SlotType.OL | SlotType.UL | undefined;
18+
19+
async componentWillLoad() {
20+
this.currentList = await ListUtils.isElementList(this.selectedElement);
21+
}
22+
23+
private async closePopover(list: SlotType.OL | SlotType.UL) {
24+
await (this.el.closest('ion-popover') as HTMLIonPopoverElement).dismiss({
25+
list: list,
26+
});
27+
}
28+
29+
private async selectList(list: SlotType.OL | SlotType.UL) {
30+
await this.closePopover(list);
31+
}
32+
33+
render() {
34+
return (
35+
<ion-list>
36+
<ion-item onClick={() => this.selectList(SlotType.UL)} class={this.currentList == SlotType.UL ? 'active' : undefined}>
37+
<ion-icon slot="start" src="/assets/icons/ionicons/list.svg"></ion-icon>
38+
<ion-label>Unordered list</ion-label>
39+
</ion-item>
40+
41+
<ion-item onClick={() => this.selectList(SlotType.OL)} class={this.currentList == SlotType.OL ? 'active' : undefined}>
42+
<ion-icon slot="start" src="/assets/icons/list-ol.svg"></ion-icon>
43+
<ion-label>Ordered list</ion-label>
44+
</ion-item>
45+
</ion-list>
46+
);
47+
}
48+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {SlotType} from './slot-type';
2+
import {SlotUtils} from './slot.utils';
3+
4+
export class ListUtils {
5+
static async isElementList(element: HTMLElement): Promise<SlotType.OL | SlotType.UL | undefined> {
6+
if (!SlotUtils.isNodeList(element)) {
7+
return undefined;
8+
}
9+
10+
if (SlotUtils.isNodeRevealList(element)) {
11+
return element && element.getAttribute('list-tag') === SlotType.UL ? SlotType.UL : SlotType.OL;
12+
} else {
13+
return element && element.nodeName && element.nodeName.toLowerCase() === SlotType.OL ? SlotType.OL : SlotType.UL;
14+
}
15+
}
16+
}

studio/src/components.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ export namespace Components {
222222
interface AppLandingContent {}
223223
interface AppLandingDeck {}
224224
interface AppLandingFooter {}
225+
interface AppList {
226+
'selectedElement': HTMLElement;
227+
}
225228
interface AppLogo {}
226229
interface AppMenu {}
227230
interface AppMoreDeckActions {
@@ -665,6 +668,12 @@ declare global {
665668
new (): HTMLAppLandingFooterElement;
666669
};
667670

671+
interface HTMLAppListElement extends Components.AppList, HTMLStencilElement {}
672+
var HTMLAppListElement: {
673+
prototype: HTMLAppListElement;
674+
new (): HTMLAppListElement;
675+
};
676+
668677
interface HTMLAppLogoElement extends Components.AppLogo, HTMLStencilElement {}
669678
var HTMLAppLogoElement: {
670679
prototype: HTMLAppLogoElement;
@@ -967,6 +976,7 @@ declare global {
967976
'app-landing-content': HTMLAppLandingContentElement;
968977
'app-landing-deck': HTMLAppLandingDeckElement;
969978
'app-landing-footer': HTMLAppLandingFooterElement;
979+
'app-list': HTMLAppListElement;
970980
'app-logo': HTMLAppLogoElement;
971981
'app-menu': HTMLAppMenuElement;
972982
'app-more-deck-actions': HTMLAppMoreDeckActionsElement;
@@ -1233,6 +1243,9 @@ declare namespace LocalJSX {
12331243
interface AppLandingContent {}
12341244
interface AppLandingDeck {}
12351245
interface AppLandingFooter {}
1246+
interface AppList {
1247+
'selectedElement'?: HTMLElement;
1248+
}
12361249
interface AppLogo {}
12371250
interface AppMenu {}
12381251
interface AppMoreDeckActions {
@@ -1400,6 +1413,7 @@ declare namespace LocalJSX {
14001413
'app-landing-content': AppLandingContent;
14011414
'app-landing-deck': AppLandingDeck;
14021415
'app-landing-footer': AppLandingFooter;
1416+
'app-list': AppList;
14031417
'app-logo': AppLogo;
14041418
'app-menu': AppMenu;
14051419
'app-more-deck-actions': AppMoreDeckActions;
@@ -1506,6 +1520,7 @@ declare module "@stencil/core" {
15061520
'app-landing-content': LocalJSX.AppLandingContent & JSXBase.HTMLAttributes<HTMLAppLandingContentElement>;
15071521
'app-landing-deck': LocalJSX.AppLandingDeck & JSXBase.HTMLAttributes<HTMLAppLandingDeckElement>;
15081522
'app-landing-footer': LocalJSX.AppLandingFooter & JSXBase.HTMLAttributes<HTMLAppLandingFooterElement>;
1523+
'app-list': LocalJSX.AppList & JSXBase.HTMLAttributes<HTMLAppListElement>;
15091524
'app-logo': LocalJSX.AppLogo & JSXBase.HTMLAttributes<HTMLAppLogoElement>;
15101525
'app-menu': LocalJSX.AppMenu & JSXBase.HTMLAttributes<HTMLAppMenuElement>;
15111526
'app-more-deck-actions': LocalJSX.AppMoreDeckActions & JSXBase.HTMLAttributes<HTMLAppMoreDeckActionsElement>;

0 commit comments

Comments
 (0)