mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 01:03:03 +08:00
feat(accordion): add accordion and accordion-group components (#22865)
resolves #17094
This commit is contained in:
14
.github/COMPONENT-GUIDE.md
vendored
14
.github/COMPONENT-GUIDE.md
vendored
@ -12,6 +12,7 @@
|
||||
- [Accessibility](#accessibility)
|
||||
* [Checkbox](#checkbox)
|
||||
* [Switch](#switch)
|
||||
* [Accordion](#accordion)
|
||||
- [Rendering Anchor or Button](#rendering-anchor-or-button)
|
||||
* [Example Components](#example-components-1)
|
||||
* [Component Structure](#component-structure-1)
|
||||
@ -623,6 +624,19 @@ You are currently on a switch. To select or deselect this checkbox, press Contro
|
||||
|
||||
There is a WebKit bug open for this: https://bugs.webkit.org/show_bug.cgi?id=196354
|
||||
|
||||
### Accordion
|
||||
|
||||
#### Example Components
|
||||
|
||||
- [ion-accordion](https://github.com/ionic-team/ionic/tree/master/core/src/components/accordion)
|
||||
- [ion-accordion-group](https://github.com/ionic-team/ionic/tree/master/core/src/components/accordion-group)
|
||||
|
||||
#### NVDA
|
||||
|
||||
In order to use the arrow keys to navigate the accordions, users must be in "Focus Mode". Typically, NVDA automatically switches between Browse and Focus modes when inside of a form, but not every accordion needs a form.
|
||||
|
||||
You can either wrap your `ion-accordion-group` in a form, or manually toggle Focus Mode using NVDA's keyboard shortcut.
|
||||
|
||||
|
||||
## Rendering Anchor or Button
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
import type * as d from './proxies';
|
||||
|
||||
export const DIRECTIVES = [
|
||||
d.IonAccordion,
|
||||
d.IonAccordionGroup,
|
||||
d.IonApp,
|
||||
d.IonAvatar,
|
||||
d.IonBackButton,
|
||||
|
@ -4,6 +4,30 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from "@angular/core";
|
||||
import { ProxyCmp, proxyOutputs } from "./proxies-utils";
|
||||
import { Components } from "@ionic/core";
|
||||
export declare interface IonAccordion extends Components.IonAccordion {
|
||||
}
|
||||
@ProxyCmp({ inputs: ["disabled", "mode", "readonly", "toggleIcon", "toggleIconSlot", "value"] })
|
||||
@Component({ selector: "ion-accordion", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["disabled", "mode", "readonly", "toggleIcon", "toggleIconSlot", "value"] })
|
||||
export class IonAccordion {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
export declare interface IonAccordionGroup extends Components.IonAccordionGroup {
|
||||
}
|
||||
@ProxyCmp({ inputs: ["disabled", "expand", "mode", "multiple", "readonly", "value"] })
|
||||
@Component({ selector: "ion-accordion-group", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["disabled", "expand", "mode", "multiple", "readonly", "value"] })
|
||||
export class IonAccordionGroup {
|
||||
ionChange!: EventEmitter<CustomEvent>;
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ["ionChange"]);
|
||||
}
|
||||
}
|
||||
export declare interface IonApp extends Components.IonApp {
|
||||
}
|
||||
@Component({ selector: "ion-app", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>" })
|
||||
|
@ -13,7 +13,7 @@ import { IonRouterOutlet } from './directives/navigation/ion-router-outlet';
|
||||
import { IonTabs } from './directives/navigation/ion-tabs';
|
||||
import { NavDelegate } from './directives/navigation/nav-delegate';
|
||||
import { RouterLinkDelegate } from './directives/navigation/router-link-delegate';
|
||||
import { IonApp, IonAvatar, IonBackButton, IonBackdrop, IonBadge, IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCheckbox, IonChip, IonCol, IonContent, IonDatetime, IonFab, IonFabButton, IonFabList, IonFooter, IonGrid, IonHeader, IonIcon, IonImg, IonInfiniteScroll, IonInfiniteScrollContent, IonInput, IonItem, IonItemDivider, IonItemGroup, IonItemOption, IonItemOptions, IonItemSliding, IonLabel, IonList, IonListHeader, IonMenu, IonMenuButton, IonMenuToggle, IonNav, IonNavLink, IonNote, IonProgressBar, IonRadio, IonRadioGroup, IonRange, IonRefresher, IonRefresherContent, IonReorder, IonReorderGroup, IonRippleEffect, IonRow, IonSearchbar, IonSegment, IonSegmentButton, IonSelect, IonSelectOption, IonSkeletonText, IonSlide, IonSlides, IonSpinner, IonSplitPane, IonTabBar, IonTabButton, IonText, IonTextarea, IonThumbnail, IonTitle, IonToggle, IonToolbar } from './directives/proxies';
|
||||
import { IonAccordion, IonAccordionGroup, IonApp, IonAvatar, IonBackButton, IonBackdrop, IonBadge, IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCheckbox, IonChip, IonCol, IonContent, IonDatetime, IonFab, IonFabButton, IonFabList, IonFooter, IonGrid, IonHeader, IonIcon, IonImg, IonInfiniteScroll, IonInfiniteScrollContent, IonInput, IonItem, IonItemDivider, IonItemGroup, IonItemOption, IonItemOptions, IonItemSliding, IonLabel, IonList, IonListHeader, IonMenu, IonMenuButton, IonMenuToggle, IonNav, IonNavLink, IonNote, IonProgressBar, IonRadio, IonRadioGroup, IonRange, IonRefresher, IonRefresherContent, IonReorder, IonReorderGroup, IonRippleEffect, IonRow, IonSearchbar, IonSegment, IonSegmentButton, IonSelect, IonSelectOption, IonSkeletonText, IonSlide, IonSlides, IonSpinner, IonSplitPane, IonTabBar, IonTabButton, IonText, IonTextarea, IonThumbnail, IonTitle, IonToggle, IonToolbar } from './directives/proxies';
|
||||
import { VirtualFooter } from './directives/virtual-scroll/virtual-footer';
|
||||
import { VirtualHeader } from './directives/virtual-scroll/virtual-header';
|
||||
import { VirtualItem } from './directives/virtual-scroll/virtual-item';
|
||||
@ -25,6 +25,8 @@ import { PopoverController } from './providers/popover-controller';
|
||||
|
||||
const DECLARATIONS = [
|
||||
// proxies
|
||||
IonAccordion,
|
||||
IonAccordionGroup,
|
||||
IonApp,
|
||||
IonAvatar,
|
||||
IonBackButton,
|
||||
|
20
core/api.txt
20
core/api.txt
@ -1,4 +1,24 @@
|
||||
|
||||
ion-accordion,shadow
|
||||
ion-accordion,prop,disabled,boolean,false,false,false
|
||||
ion-accordion,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-accordion,prop,readonly,boolean,false,false,false
|
||||
ion-accordion,prop,toggleIcon,string,'chevron-down',false,false
|
||||
ion-accordion,prop,toggleIconSlot,"end" | "start",'end',false,false
|
||||
ion-accordion,prop,value,string,`ion-accordion-${accordionIds++}`,false,false
|
||||
ion-accordion,part,content
|
||||
ion-accordion,part,expanded
|
||||
ion-accordion,part,header
|
||||
|
||||
ion-accordion-group,shadow
|
||||
ion-accordion-group,prop,disabled,boolean,false,false,false
|
||||
ion-accordion-group,prop,expand,"compact" | "inset",'compact',false,false
|
||||
ion-accordion-group,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-accordion-group,prop,multiple,boolean | undefined,undefined,false,false
|
||||
ion-accordion-group,prop,readonly,boolean,false,false,false
|
||||
ion-accordion-group,prop,value,null | string | string[] | undefined,undefined,false,false
|
||||
ion-accordion-group,event,ionChange,AccordionGroupChangeEventDetail<any>,true
|
||||
|
||||
ion-action-sheet,scoped
|
||||
ion-action-sheet,prop,animated,boolean,true,false,false
|
||||
ion-action-sheet,prop,backdropDismiss,boolean,true,false,false
|
||||
|
40
core/package-lock.json
generated
40
core/package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/puppeteer": "^4.1.1",
|
||||
"@jest/core": "^26.6.3",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
@ -43,6 +44,21 @@
|
||||
"typescript": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@axe-core/puppeteer": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.1.1.tgz",
|
||||
"integrity": "sha512-Ao9N7HL//s26hdasx3Ba18tlJgxpoO+1SmIN6eSx5vC50dqYhiRU0xp6wBKWqzo10u1jpzl/s4RFsOAuolFMBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axe-core": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"puppeteer": ">=1.10.0 < 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
||||
@ -2029,6 +2045,15 @@
|
||||
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axe-core": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.3.tgz",
|
||||
"integrity": "sha512-vwPpH4Aj4122EW38mxO/fxhGKtwWTMLDIJfZ1He0Edbtjcfna/R3YB67yVhezUMzqc3Jr3+Ii50KRntlENL4xQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-istanbul": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz",
|
||||
@ -13778,6 +13803,15 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@axe-core/puppeteer": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.1.1.tgz",
|
||||
"integrity": "sha512-Ao9N7HL//s26hdasx3Ba18tlJgxpoO+1SmIN6eSx5vC50dqYhiRU0xp6wBKWqzo10u1jpzl/s4RFsOAuolFMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"axe-core": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
||||
@ -15457,6 +15491,12 @@
|
||||
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
|
||||
"dev": true
|
||||
},
|
||||
"axe-core": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.3.tgz",
|
||||
"integrity": "sha512-vwPpH4Aj4122EW38mxO/fxhGKtwWTMLDIJfZ1He0Edbtjcfna/R3YB67yVhezUMzqc3Jr3+Ii50KRntlENL4xQ==",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-istanbul": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz",
|
||||
|
@ -36,6 +36,7 @@
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/puppeteer": "^4.1.1",
|
||||
"@jest/core": "^26.6.3",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
|
130
core/src/components.d.ts
vendored
130
core/src/components.d.ts
vendored
@ -5,11 +5,65 @@
|
||||
* It contains typing information for all components that exist in this project.
|
||||
*/
|
||||
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
|
||||
import { ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimeOptions, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, MenuChangeEventDetail, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerButton, PickerColumn, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, ViewController } from "./interface";
|
||||
import { AccordionGroupChangeEventDetail, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimeOptions, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, MenuChangeEventDetail, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerButton, PickerColumn, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, ViewController } from "./interface";
|
||||
import { IonicSafeString } from "./utils/sanitization";
|
||||
import { NavigationHookCallback } from "./components/route/route-interface";
|
||||
import { SelectCompareFn } from "./components/select/select-interface";
|
||||
export namespace Components {
|
||||
interface IonAccordion {
|
||||
/**
|
||||
* If `true`, the accordion cannot be interacted with.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, the accordion cannot be interacted with, but does not alter the opacity.
|
||||
*/
|
||||
"readonly": boolean;
|
||||
/**
|
||||
* The toggle icon to use. This icon will be rotated when the accordion is expanded or collapsed.
|
||||
*/
|
||||
"toggleIcon": string;
|
||||
/**
|
||||
* The slot inside of `ion-item` to place the toggle icon. Defaults to `'end'`.
|
||||
*/
|
||||
"toggleIconSlot": 'start' | 'end';
|
||||
/**
|
||||
* The value of the accordion. Defaults to an autogenerated value.
|
||||
*/
|
||||
"value": string;
|
||||
}
|
||||
interface IonAccordionGroup {
|
||||
/**
|
||||
* If `true`, the accordion group cannot be interacted with.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* Describes the expansion behavior for each accordion. Possible values are `"compact"` and `"inset"`. Defaults to `"compact"`.
|
||||
*/
|
||||
"expand": 'compact' | 'inset';
|
||||
"getAccordions": () => Promise<HTMLIonAccordionElement[]>;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, the accordion group can have multiple accordion components expanded at the same time.
|
||||
*/
|
||||
"multiple"?: boolean;
|
||||
/**
|
||||
* If `true`, the accordion group cannot be interacted with, but does not alter the opacity.
|
||||
*/
|
||||
"readonly": boolean;
|
||||
"requestAccordionToggle": (accordionValue: string | undefined, accordionExpand: boolean) => Promise<void>;
|
||||
/**
|
||||
* The value of the accordion group.
|
||||
*/
|
||||
"value"?: string | string[] | null;
|
||||
}
|
||||
interface IonActionSheet {
|
||||
/**
|
||||
* If `true`, the action sheet will animate.
|
||||
@ -2708,6 +2762,18 @@ export namespace Components {
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLIonAccordionElement extends Components.IonAccordion, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonAccordionElement: {
|
||||
prototype: HTMLIonAccordionElement;
|
||||
new (): HTMLIonAccordionElement;
|
||||
};
|
||||
interface HTMLIonAccordionGroupElement extends Components.IonAccordionGroup, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonAccordionGroupElement: {
|
||||
prototype: HTMLIonAccordionGroupElement;
|
||||
new (): HTMLIonAccordionGroupElement;
|
||||
};
|
||||
interface HTMLIonActionSheetElement extends Components.IonActionSheet, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonActionSheetElement: {
|
||||
@ -3231,6 +3297,8 @@ declare global {
|
||||
new (): HTMLIonVirtualScrollElement;
|
||||
};
|
||||
interface HTMLElementTagNameMap {
|
||||
"ion-accordion": HTMLIonAccordionElement;
|
||||
"ion-accordion-group": HTMLIonAccordionGroupElement;
|
||||
"ion-action-sheet": HTMLIonActionSheetElement;
|
||||
"ion-alert": HTMLIonAlertElement;
|
||||
"ion-app": HTMLIonAppElement;
|
||||
@ -3321,6 +3389,62 @@ declare global {
|
||||
}
|
||||
}
|
||||
declare namespace LocalJSX {
|
||||
interface IonAccordion {
|
||||
/**
|
||||
* If `true`, the accordion cannot be interacted with.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, the accordion cannot be interacted with, but does not alter the opacity.
|
||||
*/
|
||||
"readonly"?: boolean;
|
||||
/**
|
||||
* The toggle icon to use. This icon will be rotated when the accordion is expanded or collapsed.
|
||||
*/
|
||||
"toggleIcon"?: string;
|
||||
/**
|
||||
* The slot inside of `ion-item` to place the toggle icon. Defaults to `'end'`.
|
||||
*/
|
||||
"toggleIconSlot"?: 'start' | 'end';
|
||||
/**
|
||||
* The value of the accordion. Defaults to an autogenerated value.
|
||||
*/
|
||||
"value"?: string;
|
||||
}
|
||||
interface IonAccordionGroup {
|
||||
/**
|
||||
* If `true`, the accordion group cannot be interacted with.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* Describes the expansion behavior for each accordion. Possible values are `"compact"` and `"inset"`. Defaults to `"compact"`.
|
||||
*/
|
||||
"expand"?: 'compact' | 'inset';
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
/**
|
||||
* If `true`, the accordion group can have multiple accordion components expanded at the same time.
|
||||
*/
|
||||
"multiple"?: boolean;
|
||||
/**
|
||||
* Emitted when the value property has changed.
|
||||
*/
|
||||
"onIonChange"?: (event: CustomEvent<AccordionGroupChangeEventDetail>) => void;
|
||||
/**
|
||||
* If `true`, the accordion group cannot be interacted with, but does not alter the opacity.
|
||||
*/
|
||||
"readonly"?: boolean;
|
||||
/**
|
||||
* The value of the accordion group.
|
||||
*/
|
||||
"value"?: string | string[] | null;
|
||||
}
|
||||
interface IonActionSheet {
|
||||
/**
|
||||
* If `true`, the action sheet will animate.
|
||||
@ -6044,6 +6168,8 @@ declare namespace LocalJSX {
|
||||
"renderItem"?: (item: any, index: number) => any;
|
||||
}
|
||||
interface IntrinsicElements {
|
||||
"ion-accordion": IonAccordion;
|
||||
"ion-accordion-group": IonAccordionGroup;
|
||||
"ion-action-sheet": IonActionSheet;
|
||||
"ion-alert": IonAlert;
|
||||
"ion-app": IonApp;
|
||||
@ -6137,6 +6263,8 @@ export { LocalJSX as JSX };
|
||||
declare module "@stencil/core" {
|
||||
export namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
"ion-accordion": LocalJSX.IonAccordion & JSXBase.HTMLAttributes<HTMLIonAccordionElement>;
|
||||
"ion-accordion-group": LocalJSX.IonAccordionGroup & JSXBase.HTMLAttributes<HTMLIonAccordionGroupElement>;
|
||||
"ion-action-sheet": LocalJSX.IonActionSheet & JSXBase.HTMLAttributes<HTMLIonActionSheetElement>;
|
||||
"ion-alert": LocalJSX.IonAlert & JSXBase.HTMLAttributes<HTMLIonAlertElement>;
|
||||
"ion-app": LocalJSX.IonApp & JSXBase.HTMLAttributes<HTMLIonAppElement>;
|
||||
|
@ -0,0 +1,8 @@
|
||||
export interface AccordionGroupChangeEventDetail<T = any> {
|
||||
value: T;
|
||||
}
|
||||
|
||||
export interface AccordionGroupChangeEvent extends CustomEvent {
|
||||
detail: AccordionGroupChangeEventDetail;
|
||||
target: HTMLIonAccordionGroupElement;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
@import "./accordion-group";
|
||||
|
||||
// iOS Accordion Group
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion.accordion-expanding),
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion.accordion-expanded) {
|
||||
border-bottom: none;
|
||||
}
|
27
core/src/components/accordion-group/accordion-group.md.scss
Normal file
27
core/src/components/accordion-group/accordion-group.md.scss
Normal file
@ -0,0 +1,27 @@
|
||||
@import "./accordion-group";
|
||||
@import "../accordion/accordion.md.vars";
|
||||
|
||||
// Material Design Accordion Group
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion) {
|
||||
box-shadow: $accordion-md-box-shadow;
|
||||
}
|
||||
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion.accordion-expanding),
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion.accordion-expanded) {
|
||||
@include margin($accordion-md-expanded-margin, 0, $accordion-md-expanded-margin, 0);
|
||||
@include border-radius($accordion-md-border-radius, $accordion-md-border-radius, $accordion-md-border-radius, $accordion-md-border-radius);
|
||||
}
|
||||
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion.accordion-previous) {
|
||||
@include border-radius(null, null, $accordion-md-border-radius, $accordion-md-border-radius);
|
||||
}
|
||||
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion.accordion-next) {
|
||||
@include border-radius($accordion-md-border-radius, $accordion-md-border-radius, null, null);
|
||||
}
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion):first-of-type,
|
||||
:host(.accordion-group-expand-inset) ::slotted(ion-accordion):first-of-type {
|
||||
@include margin(0, 0, 0, 0);
|
||||
}
|
13
core/src/components/accordion-group/accordion-group.scss
Normal file
13
core/src/components/accordion-group/accordion-group.scss
Normal file
@ -0,0 +1,13 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
@import "../accordion/accordion.vars";
|
||||
|
||||
// Accordion Group
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host(.accordion-group-expand-inset) {
|
||||
@include margin($accordion-inset-margin, $accordion-inset-margin, $accordion-inset-margin, $accordion-inset-margin);
|
||||
}
|
214
core/src/components/accordion-group/accordion-group.tsx
Normal file
214
core/src/components/accordion-group/accordion-group.tsx
Normal file
@ -0,0 +1,214 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Listen, Method, Prop, Watch, h } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AccordionGroupChangeEventDetail } from '../../interface';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-accordion-group',
|
||||
styleUrls: {
|
||||
ios: 'accordion-group.ios.scss',
|
||||
md: 'accordion-group.md.scss'
|
||||
},
|
||||
shadow: true
|
||||
})
|
||||
export class AccordionGroup implements ComponentInterface {
|
||||
@Element() el!: HTMLIonAccordionGroupElement;
|
||||
|
||||
/**
|
||||
* If `true`, the accordion group can have multiple
|
||||
* accordion components expanded at the same time.
|
||||
*/
|
||||
@Prop() multiple?: boolean;
|
||||
|
||||
/**
|
||||
* The value of the accordion group.
|
||||
*/
|
||||
@Prop({ mutable: true }) value?: string | string[] | null;
|
||||
|
||||
/**
|
||||
* If `true`, the accordion group cannot be interacted with.
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* If `true`, the accordion group cannot be interacted with,
|
||||
* but does not alter the opacity.
|
||||
*/
|
||||
@Prop() readonly = false;
|
||||
|
||||
/**
|
||||
* Describes the expansion behavior for each accordion.
|
||||
* Possible values are `"compact"` and `"inset"`.
|
||||
* Defaults to `"compact"`.
|
||||
*/
|
||||
@Prop() expand: 'compact' | 'inset' = 'compact';
|
||||
|
||||
/**
|
||||
* Emitted when the value property has changed.
|
||||
*/
|
||||
@Event() ionChange!: EventEmitter<AccordionGroupChangeEventDetail>;
|
||||
|
||||
@Watch('value')
|
||||
valueChanged() {
|
||||
const { value, multiple } = this;
|
||||
|
||||
/**
|
||||
* If accordion group does not
|
||||
* let multiple accordions be open
|
||||
* at once, but user passes an array
|
||||
* just grab the first value.
|
||||
*/
|
||||
if (!multiple && Array.isArray(value)) {
|
||||
this.value = value[0];
|
||||
} else {
|
||||
this.ionChange.emit({ value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('disabled')
|
||||
async disabledChanged() {
|
||||
const { disabled } = this;
|
||||
const accordions = await this.getAccordions();
|
||||
for (const accordion of accordions) {
|
||||
accordion.disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('readonly')
|
||||
async readonlyChanged() {
|
||||
const { readonly } = this;
|
||||
const accordions = await this.getAccordions();
|
||||
for (const accordion of accordions) {
|
||||
accordion.readonly = readonly;
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('keydown')
|
||||
async onKeydown(ev: KeyboardEvent) {
|
||||
const activeElement = document.activeElement;
|
||||
if (!activeElement) { return; }
|
||||
|
||||
const accordionEl = (activeElement.tagName === 'ION-ACCORDION') ? activeElement : activeElement.closest('ion-accordion');
|
||||
if (!accordionEl) { return; }
|
||||
|
||||
const closestGroup = accordionEl.closest('ion-accordion-group');
|
||||
if (closestGroup !== this.el) { return; }
|
||||
|
||||
// If the active accordion is not in the current array of accordions, do not do anything
|
||||
const accordions = await this.getAccordions();
|
||||
const startingIndex = accordions.findIndex(a => a === accordionEl);
|
||||
if (startingIndex === -1) { return; }
|
||||
|
||||
let accordion: HTMLIonAccordionElement | undefined;
|
||||
if (ev.key === 'ArrowDown') {
|
||||
accordion = this.findNextAccordion(accordions, startingIndex);
|
||||
} else if (ev.key === 'ArrowUp') {
|
||||
accordion = this.findPreviousAccordion(accordions, startingIndex);
|
||||
} else if (ev.key === 'Home') {
|
||||
accordion = accordions[0];
|
||||
} else if (ev.key === 'End') {
|
||||
accordion = accordions[accordions.length - 1];
|
||||
}
|
||||
|
||||
if (accordion !== undefined && accordion !== activeElement) {
|
||||
accordion.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidLoad() {
|
||||
if (this.disabled) {
|
||||
this.disabledChanged();
|
||||
}
|
||||
if (this.readonly) {
|
||||
this.readonlyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@Method()
|
||||
async requestAccordionToggle(accordionValue: string | undefined, accordionExpand: boolean) {
|
||||
const { multiple, value, readonly, disabled } = this;
|
||||
if (readonly || disabled) { return; }
|
||||
|
||||
if (accordionExpand) {
|
||||
/**
|
||||
* If group accepts multiple values
|
||||
* check to see if value is already in
|
||||
* in values array. If not, add it
|
||||
* to the array.
|
||||
*/
|
||||
if (multiple) {
|
||||
const groupValue = (value || []) as string[];
|
||||
const valueExists = groupValue.find(v => v === accordionValue);
|
||||
if (valueExists === undefined && accordionValue !== undefined) {
|
||||
this.value = [...groupValue, accordionValue];
|
||||
}
|
||||
} else {
|
||||
this.value = accordionValue;
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* If collapsing accordion, either filter the value
|
||||
* out of the values array or unset the value.
|
||||
*/
|
||||
if (multiple) {
|
||||
const groupValue = (value || []) as string[];
|
||||
this.value = groupValue.filter(v => v !== accordionValue);
|
||||
} else {
|
||||
this.value = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private findNextAccordion(accordions: HTMLIonAccordionElement[], startingIndex: number) {
|
||||
const nextAccordion = accordions[startingIndex + 1];
|
||||
// tslint:disable-next-line:strict-type-predicates
|
||||
if (nextAccordion === undefined) {
|
||||
return accordions[0];
|
||||
}
|
||||
|
||||
return nextAccordion;
|
||||
}
|
||||
|
||||
private findPreviousAccordion(accordions: HTMLIonAccordionElement[], startingIndex: number) {
|
||||
const prevAccordion = accordions[startingIndex - 1];
|
||||
// tslint:disable-next-line:strict-type-predicates
|
||||
if (prevAccordion === undefined) {
|
||||
return accordions[accordions.length - 1];
|
||||
}
|
||||
|
||||
return prevAccordion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@Method()
|
||||
async getAccordions() {
|
||||
return Array.from(this.el.querySelectorAll('ion-accordion'));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { disabled, readonly, expand } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={{
|
||||
[mode]: true,
|
||||
'accordion-group-disabled': disabled,
|
||||
'accordion-group-readonly': readonly,
|
||||
[`accordion-group-expand-${expand}`]: true
|
||||
}}
|
||||
role="presentation"
|
||||
>
|
||||
<slot></slot>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
31
core/src/components/accordion-group/readme.md
Normal file
31
core/src/components/accordion-group/readme.md
Normal file
@ -0,0 +1,31 @@
|
||||
# ion-accordion-group
|
||||
|
||||
Accordion group is a container for accordion instances. It manages the state of the accordions and provides keyboard navigation.
|
||||
|
||||
For more information as well as usage, see the [Accordion Documentation](../accordion)
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ----------- |
|
||||
| `disabled` | `disabled` | If `true`, the accordion group cannot be interacted with. | `boolean` | `false` |
|
||||
| `expand` | `expand` | Describes the expansion behavior for each accordion. Possible values are `"compact"` and `"inset"`. Defaults to `"compact"`. | `"compact" \| "inset"` | `'compact'` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `multiple` | `multiple` | If `true`, the accordion group can have multiple accordion components expanded at the same time. | `boolean \| undefined` | `undefined` |
|
||||
| `readonly` | `readonly` | If `true`, the accordion group cannot be interacted with, but does not alter the opacity. | `boolean` | `false` |
|
||||
| `value` | `value` | The value of the accordion group. | `null \| string \| string[] \| undefined` | `undefined` |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Description | Type |
|
||||
| ----------- | -------------------------------------------- | --------------------------------------------------- |
|
||||
| `ionChange` | Emitted when the value property has changed. | `CustomEvent<AccordionGroupChangeEventDetail<any>>` |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
*Built with [StencilJS](https://stenciljs.com/)*
|
9
core/src/components/accordion/accordion.ios.scss
Normal file
9
core/src/components/accordion/accordion.ios.scss
Normal file
@ -0,0 +1,9 @@
|
||||
@import "./accordion.scss";
|
||||
@import "../item/item.ios.vars";
|
||||
|
||||
// iOS Accordion
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.accordion-next) ::slotted(ion-item[slot="header"]) {
|
||||
--border-width: #{$item-ios-border-bottom-width 0px $item-ios-border-bottom-width 0px};
|
||||
}
|
4
core/src/components/accordion/accordion.md.scss
Normal file
4
core/src/components/accordion/accordion.md.scss
Normal file
@ -0,0 +1,4 @@
|
||||
@import "./accordion.scss";
|
||||
|
||||
// Material Design Accordion
|
||||
// --------------------------------------------------
|
13
core/src/components/accordion/accordion.md.vars.scss
Normal file
13
core/src/components/accordion/accordion.md.vars.scss
Normal file
@ -0,0 +1,13 @@
|
||||
@import "../../themes/ionic.globals.md";
|
||||
|
||||
// Accordion
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Border radius applied to the accordion
|
||||
$accordion-md-border-radius: 6px !default;
|
||||
|
||||
/// @prop - Box shadow of the accordion
|
||||
$accordion-md-box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12) !default;
|
||||
|
||||
/// @prop - Margin of the expanded accordion
|
||||
$accordion-md-expanded-margin: 16px !default;
|
81
core/src/components/accordion/accordion.scss
Normal file
81
core/src/components/accordion/accordion.scss
Normal file
@ -0,0 +1,81 @@
|
||||
@import "./accordion.vars.scss";
|
||||
|
||||
// Accordion
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background-color: $accordion-background-color;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
/**
|
||||
* This is required to force WebKit
|
||||
* to create a new stacking context
|
||||
* otherwise the border radius is
|
||||
* temporarily lost when hovering over
|
||||
* the ion-item or expanding/collapsing
|
||||
* the accordion.
|
||||
*/
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
:host(.accordion-expanding) ::slotted(ion-item[slot="header"]),
|
||||
:host(.accordion-expanded) ::slotted(ion-item[slot="header"]) {
|
||||
--border-width: 0px;
|
||||
}
|
||||
|
||||
:host(.accordion-animated) {
|
||||
transition: all $accordion-transition-duration $accordion-transition-easing;
|
||||
}
|
||||
|
||||
:host(.accordion-animated) #content {
|
||||
transition: max-height $accordion-transition-duration $accordion-transition-easing;
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow: hidden;
|
||||
|
||||
will-change: max-height;
|
||||
}
|
||||
|
||||
:host(.accordion-collapsing) #content {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
max-height: 0 !important;
|
||||
}
|
||||
|
||||
:host(.accordion-collapsed) #content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host(.accordion-expanding) #content {
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
:host(.accordion-disabled) #header,
|
||||
:host(.accordion-readonly) #header {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* We do not set the opacity on the
|
||||
* host otherwise you would see the
|
||||
* box-shadow behind it.
|
||||
*/
|
||||
:host(.accordion-disabled) #header,
|
||||
:host(.accordion-disabled) #content {
|
||||
opacity: $accordion-disabled-opacity;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:host,
|
||||
#content {
|
||||
/* stylelint-disable declaration-no-important */
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
427
core/src/components/accordion/accordion.tsx
Normal file
427
core/src/components/accordion/accordion.tsx
Normal file
@ -0,0 +1,427 @@
|
||||
import { Component, ComponentInterface, Element, Host, Prop, State, h } from '@stencil/core';
|
||||
|
||||
import { config } from '../../global/config';
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { addEventListener, getElementRoot, raf, removeEventListener, transitionEndAsync } from '../../utils/helpers';
|
||||
|
||||
const enum AccordionState {
|
||||
Collapsed = 1 << 0,
|
||||
Collapsing = 1 << 1,
|
||||
Expanded = 1 << 2,
|
||||
Expanding = 1 << 3
|
||||
}
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
*
|
||||
* @slot header - Content is placed at the top and is used to
|
||||
* expand or collapse the accordion item.
|
||||
* @slot content - Content is placed below the header and is
|
||||
* shown or hidden based on expanded state.
|
||||
*
|
||||
* @part header - The wrapper element for the header slot.
|
||||
* @part content - The wrapper element for the content slot.
|
||||
* @part expanded - The expanded element. Can be used in combination
|
||||
* with the `header` and `content` parts (i.e. `::part(header expanded)`).
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-accordion',
|
||||
styleUrls: {
|
||||
ios: 'accordion.ios.scss',
|
||||
md: 'accordion.md.scss'
|
||||
},
|
||||
shadow: {
|
||||
delegatesFocus: true
|
||||
}
|
||||
})
|
||||
export class Accordion implements ComponentInterface {
|
||||
private accordionGroupEl?: HTMLIonAccordionGroupElement | null;
|
||||
private updateListener = () => this.updateState(false);
|
||||
private contentEl: HTMLDivElement | undefined;
|
||||
private contentElWrapper: HTMLDivElement | undefined;
|
||||
private headerEl: HTMLDivElement | undefined;
|
||||
|
||||
private currentRaf: number | undefined;
|
||||
|
||||
@Element() el?: HTMLElement;
|
||||
|
||||
@State() state: AccordionState = AccordionState.Collapsed;
|
||||
@State() isNext = false;
|
||||
@State() isPrevious = false;
|
||||
|
||||
/**
|
||||
* The value of the accordion. Defaults to an autogenerated
|
||||
* value.
|
||||
*/
|
||||
@Prop() value = `ion-accordion-${accordionIds++}`;
|
||||
|
||||
/**
|
||||
* If `true`, the accordion cannot be interacted with.
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* If `true`, the accordion cannot be interacted with,
|
||||
* but does not alter the opacity.
|
||||
*/
|
||||
@Prop() readonly = false;
|
||||
|
||||
/**
|
||||
* The toggle icon to use. This icon will be
|
||||
* rotated when the accordion is expanded
|
||||
* or collapsed.
|
||||
*/
|
||||
@Prop() toggleIcon = 'chevron-down';
|
||||
|
||||
/**
|
||||
* The slot inside of `ion-item` to
|
||||
* place the toggle icon. Defaults to `'end'`.
|
||||
*/
|
||||
@Prop() toggleIconSlot: 'start' | 'end' = 'end';
|
||||
|
||||
connectedCallback() {
|
||||
const accordionGroupEl = this.accordionGroupEl = this.el && this.el.closest('ion-accordion-group');
|
||||
if (accordionGroupEl) {
|
||||
this.updateState(true);
|
||||
addEventListener(accordionGroupEl, 'ionChange', this.updateListener);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
const accordionGroupEl = this.accordionGroupEl;
|
||||
if (accordionGroupEl) {
|
||||
removeEventListener(accordionGroupEl, 'ionChange', this.updateListener);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
this.setItemDefaults();
|
||||
this.slotToggleIcon();
|
||||
|
||||
/**
|
||||
* We need to wait a tick because we
|
||||
* just set ionItem.button = true and
|
||||
* the button has not have been rendered yet.
|
||||
*/
|
||||
raf(() => {
|
||||
/**
|
||||
* Set aria label on button inside of ion-item
|
||||
* once the inner content has been rendered.
|
||||
*/
|
||||
const expanded = this.state === AccordionState.Expanded || this.state === AccordionState.Expanding;
|
||||
this.setAria(expanded);
|
||||
});
|
||||
}
|
||||
|
||||
private setItemDefaults = () => {
|
||||
const ionItem = this.getSlottedHeaderIonItem();
|
||||
if (!ionItem) { return; }
|
||||
|
||||
/**
|
||||
* For a11y purposes, we make
|
||||
* the ion-item a button so users
|
||||
* can tab to it and use keyboard
|
||||
* navigation to get around.
|
||||
*/
|
||||
ionItem.button = true;
|
||||
ionItem.detail = false;
|
||||
|
||||
/**
|
||||
* By default, the lines in an
|
||||
* item should be full here, but
|
||||
* only do that if a user has
|
||||
* not explicitly overridden them
|
||||
*/
|
||||
if (ionItem.lines === undefined) {
|
||||
ionItem.lines = 'full';
|
||||
}
|
||||
}
|
||||
|
||||
private getSlottedHeaderIonItem = () => {
|
||||
const { headerEl } = this;
|
||||
if (!headerEl) { return; }
|
||||
|
||||
/**
|
||||
* Get the first ion-item
|
||||
* slotted in the header slot
|
||||
*/
|
||||
const slot = headerEl.querySelector('slot');
|
||||
if (!slot) { return; }
|
||||
|
||||
// This is not defined in unit tests
|
||||
const ionItem = slot.assignedElements && (slot.assignedElements().find(el => el.tagName === 'ION-ITEM') as HTMLIonItemElement | undefined);
|
||||
|
||||
return ionItem;
|
||||
}
|
||||
|
||||
private setAria = (expanded = false) => {
|
||||
const ionItem = this.getSlottedHeaderIonItem();
|
||||
if (!ionItem) { return; }
|
||||
|
||||
/**
|
||||
* Get the native <button> element inside of
|
||||
* ion-item because that is what will be focused
|
||||
*/
|
||||
const root = getElementRoot(ionItem);
|
||||
const button = root.querySelector('button');
|
||||
if (!button) { return; }
|
||||
|
||||
button.setAttribute('aria-expanded', `${expanded}`);
|
||||
}
|
||||
|
||||
private slotToggleIcon = () => {
|
||||
const ionItem = this.getSlottedHeaderIonItem();
|
||||
if (!ionItem) { return; }
|
||||
|
||||
const { toggleIconSlot, toggleIcon } = this;
|
||||
|
||||
/**
|
||||
* Check if there already is a toggle icon.
|
||||
* If so, do not add another one.
|
||||
*/
|
||||
const existingToggleIcon = ionItem.querySelector('.ion-accordion-toggle-icon');
|
||||
if (existingToggleIcon) { return; }
|
||||
|
||||
const iconEl = document.createElement('ion-icon');
|
||||
iconEl.slot = toggleIconSlot;
|
||||
iconEl.lazy = false;
|
||||
iconEl.classList.add('ion-accordion-toggle-icon');
|
||||
iconEl.icon = toggleIcon;
|
||||
iconEl.ariaHidden = 'true';
|
||||
|
||||
ionItem.appendChild(iconEl);
|
||||
}
|
||||
|
||||
private expandAccordion = (initialUpdate = false) => {
|
||||
if (initialUpdate) {
|
||||
this.state = AccordionState.Expanded;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state === AccordionState.Expanded) { return; }
|
||||
|
||||
const { contentEl, contentElWrapper } = this;
|
||||
if (contentEl === undefined || contentElWrapper === undefined) { return; }
|
||||
|
||||
if (this.currentRaf !== undefined) {
|
||||
cancelAnimationFrame(this.currentRaf);
|
||||
}
|
||||
|
||||
if (this.shouldAnimate()) {
|
||||
this.state = AccordionState.Expanding;
|
||||
|
||||
this.currentRaf = raf(async () => {
|
||||
const contentHeight = contentElWrapper.offsetHeight;
|
||||
const waitForTransition = transitionEndAsync(contentEl, 2000);
|
||||
contentEl.style.setProperty('max-height', `${contentHeight}px`);
|
||||
|
||||
/**
|
||||
* Force a repaint. We can't use an raf
|
||||
* here as it could cause the collapse animation
|
||||
* to get out of sync with the other
|
||||
* accordion's expand animation.
|
||||
*/
|
||||
// tslint:disable-next-line
|
||||
void contentEl.offsetHeight;
|
||||
|
||||
await waitForTransition;
|
||||
|
||||
this.state = AccordionState.Expanded;
|
||||
contentEl.style.removeProperty('max-height');
|
||||
});
|
||||
} else {
|
||||
this.state = AccordionState.Expanded;
|
||||
}
|
||||
}
|
||||
|
||||
private collapseAccordion = (initialUpdate = false) => {
|
||||
if (initialUpdate) {
|
||||
this.state = AccordionState.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state === AccordionState.Collapsed) { return; }
|
||||
|
||||
const { contentEl } = this;
|
||||
if (contentEl === undefined) { return; }
|
||||
|
||||
if (this.currentRaf !== undefined) {
|
||||
cancelAnimationFrame(this.currentRaf);
|
||||
}
|
||||
|
||||
if (this.shouldAnimate()) {
|
||||
this.currentRaf = raf(async () => {
|
||||
const contentHeight = contentEl.offsetHeight;
|
||||
contentEl.style.setProperty('max-height', `${contentHeight}px`);
|
||||
|
||||
/**
|
||||
* Force a repaint. We can't use an raf
|
||||
* here as it could cause the collapse animation
|
||||
* to get out of sync with the other
|
||||
* accordion's expand animation.
|
||||
*/
|
||||
// tslint:disable-next-line
|
||||
void contentEl.offsetHeight;
|
||||
|
||||
const waitForTransition = transitionEndAsync(contentEl, 2000);
|
||||
this.state = AccordionState.Collapsing;
|
||||
|
||||
await waitForTransition;
|
||||
|
||||
this.state = AccordionState.Collapsed;
|
||||
contentEl.style.removeProperty('max-height');
|
||||
});
|
||||
} else {
|
||||
this.state = AccordionState.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine if
|
||||
* something should animate.
|
||||
* If prefers-reduced-motion is set
|
||||
* then we should not animate, regardless
|
||||
* of what is set in the config.
|
||||
*/
|
||||
private shouldAnimate = () => {
|
||||
if (typeof (window as any) === 'undefined') { return false; }
|
||||
|
||||
const prefersReducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
if (prefersReducedMotion) { return false; }
|
||||
|
||||
const animated = config.get('animated', true);
|
||||
if (!animated) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private updateState = async (initialUpdate = false) => {
|
||||
const accordionGroup = this.accordionGroupEl;
|
||||
const accordionValue = this.value;
|
||||
|
||||
if (!accordionGroup) { return; }
|
||||
|
||||
const value = accordionGroup.value;
|
||||
|
||||
const shouldExpand = (Array.isArray(value)) ? value.includes(accordionValue) : value === accordionValue;
|
||||
|
||||
if (shouldExpand) {
|
||||
this.expandAccordion(initialUpdate);
|
||||
this.isNext = this.isPrevious = false;
|
||||
} else {
|
||||
this.collapseAccordion(initialUpdate);
|
||||
|
||||
/**
|
||||
* When using popout or inset,
|
||||
* the collapsed accordion items
|
||||
* may need additional border radius
|
||||
* applied. Check to see if the
|
||||
* next or previous accordion is selected.
|
||||
*/
|
||||
const nextAccordion = this.getNextSibling();
|
||||
const nextAccordionValue = nextAccordion && nextAccordion.value;
|
||||
|
||||
if (nextAccordionValue !== undefined) {
|
||||
this.isPrevious = (Array.isArray(value)) ? value.includes(nextAccordionValue) : value === nextAccordionValue;
|
||||
}
|
||||
|
||||
const previousAccordion = this.getPreviousSibling();
|
||||
const previousAccordionValue = previousAccordion && previousAccordion.value;
|
||||
|
||||
if (previousAccordionValue !== undefined) {
|
||||
this.isNext = (Array.isArray(value)) ? value.includes(previousAccordionValue) : value === previousAccordionValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getNextSibling = () => {
|
||||
if (!this.el) { return; }
|
||||
|
||||
const nextSibling = this.el.nextElementSibling;
|
||||
|
||||
if (nextSibling?.tagName !== 'ION-ACCORDION') { return; }
|
||||
|
||||
return nextSibling as HTMLIonAccordionElement;
|
||||
}
|
||||
|
||||
private getPreviousSibling = () => {
|
||||
if (!this.el) { return; }
|
||||
|
||||
const previousSibling = this.el.previousElementSibling;
|
||||
|
||||
if (previousSibling?.tagName !== 'ION-ACCORDION') { return; }
|
||||
|
||||
return previousSibling as HTMLIonAccordionElement;
|
||||
}
|
||||
|
||||
private toggleExpanded() {
|
||||
const { accordionGroupEl, value, state } = this;
|
||||
if (accordionGroupEl) {
|
||||
/**
|
||||
* Because the accordion group may or may
|
||||
* not allow multiple accordions open, we
|
||||
* need to request the toggling of this
|
||||
* accordion and the accordion group will
|
||||
* make the decision on whether or not
|
||||
* to allow it.
|
||||
*/
|
||||
const expand = state === AccordionState.Collapsed || state === AccordionState.Collapsing;
|
||||
accordionGroupEl.requestAccordionToggle(value, expand);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { disabled, readonly } = this;
|
||||
const mode = getIonMode(this);
|
||||
const expanded = this.state === AccordionState.Expanded || this.state === AccordionState.Expanding;
|
||||
const headerPart = expanded ? 'header expanded' : 'header';
|
||||
const contentPart = expanded ? 'content expanded' : 'content';
|
||||
|
||||
this.setAria(expanded);
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={{
|
||||
[mode]: true,
|
||||
'accordion-expanding': this.state === AccordionState.Expanding,
|
||||
'accordion-expanded': this.state === AccordionState.Expanded,
|
||||
'accordion-collapsing': this.state === AccordionState.Collapsing,
|
||||
'accordion-collapsed': this.state === AccordionState.Collapsed,
|
||||
|
||||
'accordion-next': this.isNext,
|
||||
'accordion-previous': this.isPrevious,
|
||||
|
||||
'accordion-disabled': disabled,
|
||||
'accordion-readonly': readonly,
|
||||
|
||||
'accordion-animated': config.getBoolean('animated', true)
|
||||
}}
|
||||
|
||||
>
|
||||
<div
|
||||
onClick={() => this.toggleExpanded()}
|
||||
id="header"
|
||||
part={headerPart}
|
||||
aria-controls="content"
|
||||
ref={headerEl => this.headerEl = headerEl}
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="content"
|
||||
part={contentPart}
|
||||
role="region"
|
||||
aria-labelledby="header"
|
||||
ref={contentEl => this.contentEl = contentEl}
|
||||
>
|
||||
<div id="content-wrapper" ref={contentElWrapper => this.contentElWrapper = contentElWrapper}>
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let accordionIds = 0;
|
19
core/src/components/accordion/accordion.vars.scss
Normal file
19
core/src/components/accordion/accordion.vars.scss
Normal file
@ -0,0 +1,19 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Accordion
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Background color of the accordion
|
||||
$accordion-background-color: var(--ion-background-color, #ffffff) !default;
|
||||
|
||||
/// @prop - Duration of the accordion transition
|
||||
$accordion-transition-duration: 300ms !default;
|
||||
|
||||
/// @prop - Timing function of the accordion transition
|
||||
$accordion-transition-easing: cubic-bezier(0.25, 0.8, 0.5, 1) !default;
|
||||
|
||||
/// @prop - Opacity of the disabled accordion
|
||||
$accordion-disabled-opacity: 0.4 !default;
|
||||
|
||||
/// @prop - Margin of the inset accordion
|
||||
$accordion-inset-margin: 16px !default;
|
1295
core/src/components/accordion/readme.md
Normal file
1295
core/src/components/accordion/readme.md
Normal file
File diff suppressed because it is too large
Load Diff
114
core/src/components/accordion/test/a11y/index.html
Normal file
114
core/src/components/accordion/test/a11y/index.html
Normal file
@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Accordion - a11y</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-content>
|
||||
<h1>Accordion Group - a11y</h1>
|
||||
|
||||
<ion-accordion-group expand="inset">
|
||||
<ion-accordion value="personal-information">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Personal Information</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Name</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Email</ion-label>
|
||||
<ion-input type="email"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Phone</ion-label>
|
||||
<ion-input type="tel"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Extension</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Country</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>City/Province</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
|
||||
<ion-accordion value="billing-address">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Billing Address</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Address 1</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Address 2</ion-label>
|
||||
<ion-input type="email"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>City</ion-label>
|
||||
<ion-input type="tel"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>State</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Zip Code</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
|
||||
<ion-accordion value="shipping-address">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shipping Address</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Address 1</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Address 2</ion-label>
|
||||
<ion-input type="email"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>City</ion-label>
|
||||
<ion-input type="tel"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>State</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Zip Code</ion-label>
|
||||
<ion-input type="text"></ion-input>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
21
core/src/components/accordion/test/a11y/screen-readers.md
Normal file
21
core/src/components/accordion/test/a11y/screen-readers.md
Normal file
@ -0,0 +1,21 @@
|
||||
"native" refers to this sample: https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html
|
||||
|
||||
### Selecting Accordion
|
||||
|
||||
| native | Ionic
|
||||
-- | -- | --
|
||||
VoiceOver macOS - Chrome | Personal information, collapsed, button | Personal information, collapsed, button
|
||||
VoiceOver macOS - Safari | Personal information, collapsed, button | Personal information, collapsed, button
|
||||
VoiceOver iOS | Personal information, collapsed | Personal information, button, main, landmark, collapsed
|
||||
Android TalkBack | Collapsed, personal information, button | Collapsed, personal information, button
|
||||
Windows NVDA | Personal information, button, unavailable, collapsed | Clickable Personal Information button collapsed
|
||||
|
||||
### Toggling Accordion
|
||||
|
||||
| native | Ionic
|
||||
-- | -- | --
|
||||
VoiceOver macOS - Chrome | Personal information, dimmed expanded, button | Personal information, expanded, button
|
||||
VoiceOver macOS - Safari | Personal information, dimmed expanded, button | Personal information, expanded, button
|
||||
VoiceOver iOS | Personal information, dimmed, expanded | Personal information, main, landmark, expanded
|
||||
Android TalkBack | Expanded | Expanded
|
||||
Windows NVDA | Unavailable, expanded | Expanded
|
259
core/src/components/accordion/test/accordion.spec.ts
Normal file
259
core/src/components/accordion/test/accordion.spec.ts
Normal file
@ -0,0 +1,259 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
import { AccordionGroup } from '../../accordion-group/accordion-group.tsx';
|
||||
import { Accordion } from '../accordion.tsx';
|
||||
import { Item } from '../../item/item.tsx';
|
||||
|
||||
it('should properly set readonly on child accordions', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
expect(accordions.length).toEqual(1);
|
||||
accordions.forEach(accordion => {
|
||||
expect(accordion.readonly).toEqual(false);
|
||||
});
|
||||
|
||||
accordionGroup.readonly = true;
|
||||
await page.waitForChanges();
|
||||
|
||||
accordions.forEach(accordion => {
|
||||
expect(accordion.readonly).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly set disabled on child accordions', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
expect(accordions.length).toEqual(1);
|
||||
accordions.forEach(accordion => {
|
||||
expect(accordion.disabled).toEqual(false);
|
||||
});
|
||||
|
||||
accordionGroup.disabled = true;
|
||||
await page.waitForChanges();
|
||||
|
||||
accordions.forEach(accordion => {
|
||||
expect(accordion.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should open correct accordions', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="second">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="third">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
accordions.forEach(accordion => {
|
||||
expect(accordion.classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
accordionGroup.value = 'second';
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
expect(accordions[1].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
expect(accordions[2].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
|
||||
it('should not open more than one accordion when multiple="false"', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="second">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="third">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
accordions.forEach(accordion => {
|
||||
expect(accordion.classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
accordionGroup.value = ['first', 'second'];
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
expect(accordions[1].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
expect(accordions[2].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should open more than one accordion when multiple="true"', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group multiple="true">
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="second">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="third">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
accordions.forEach(accordion => {
|
||||
expect(accordion.classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
accordionGroup.value = ['first', 'second'];
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
expect(accordions[1].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
expect(accordions[2].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should render with accordion open', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group value="first">
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="second">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="third">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
expect(accordions[1].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
expect(accordions[2].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should accept a string when multiple="true"', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group multiple="true" value="first">
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="second">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="third">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordions = accordionGroup.querySelectorAll('ion-accordion');
|
||||
|
||||
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
|
||||
expect(accordions[1].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
expect(accordions[2].classList.contains('accordion-collapsed')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should set default values if not provided', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group>
|
||||
<ion-accordion>
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group');
|
||||
const accordion = accordionGroup.querySelector('ion-accordion');
|
||||
|
||||
/**
|
||||
* ID is determined via an auto incrementing counter
|
||||
* so do not hard code ion-accordion-0 as it might
|
||||
* change depending on how many accordions
|
||||
* are used in these tests.
|
||||
*/
|
||||
expect(accordion.value).toContain('ion-accordion-');
|
||||
|
||||
accordionGroup.value = accordion.value;
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(accordion.classList.contains('accordion-collapsed')).toEqual(false);
|
||||
});
|
739
core/src/components/accordion/test/basic/index.html
Normal file
739
core/src/components/accordion/test/basic/index.html
Normal file
@ -0,0 +1,739 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Accordion - Basic</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
|
||||
color: #6f7378;
|
||||
|
||||
margin-top: 10px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-title>Accordion - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content fullscreen="true" color="light">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar color="light">
|
||||
<ion-title size="large">Accordion - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<div class="grid ion-padding">
|
||||
<div class="grid-item">
|
||||
<h2>Inset, Color - iOS</h2>
|
||||
<ion-accordion-group mode="ios" expand="inset">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item color="primary" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item color="warning" slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item color="danger" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item color="success" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Inset - iOS</h2>
|
||||
<ion-accordion-group mode="ios" expand="inset">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Inset, Color - MD</h2>
|
||||
<ion-accordion-group mode="md" expand="inset">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item color="primary" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item color="warning" slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item color="danger" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item color="success" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Inset - MD</h2>
|
||||
<ion-accordion-group mode="md" expand="inset">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid ion-padding">
|
||||
<div class="grid-item">
|
||||
<h2>Compact, Color - iOS</h2>
|
||||
<ion-accordion-group mode="ios">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item color="primary" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item color="warning" slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item color="danger" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item color="success" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Compact - iOS</h2>
|
||||
<ion-accordion-group mode="ios">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Compact, Color - MD</h2>
|
||||
<ion-accordion-group mode="md">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item color="primary" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item color="warning" slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item color="danger" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item color="success" slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Compact - MD</h2>
|
||||
<ion-accordion-group mode="md">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid ion-padding">
|
||||
<div class="grid-item">
|
||||
<h2>Multiple</h2>
|
||||
<ion-accordion-group expand="inset" mode="md" multiple="true">
|
||||
<ion-accordion value="attractions">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="film-outline" md="film"></ion-icon>
|
||||
<ion-label> Attractions</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Movie Theaters</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Amusement Parks</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Mini Golf</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="dining">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" name="pizza"></ion-icon>
|
||||
<ion-label>Dining</ion-label>
|
||||
</ion-item>
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Breakfast & Brunch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>New American</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Sushi Bars</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="games">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="game-controller-outline" md="game-controller"></ion-icon>
|
||||
<ion-label>Games</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Xbox</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Playstation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Switch</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="exercise">
|
||||
<ion-item slot="header" button detail="false">
|
||||
<ion-icon slot="start" ios="bicycle-outline" md="bicycle"></ion-icon>
|
||||
<ion-label>Exercise</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list lines="none" slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Jog</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Swim</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Nap</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const multipleAccordion = document.querySelector('ion-accordion-group[multiple="true"]');
|
||||
multipleAccordion.value = ['dining', 'exercise'];
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
64
core/src/components/accordion/test/e2e.ts
Normal file
64
core/src/components/accordion/test/e2e.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { AxePuppeteer } from '@axe-core/puppeteer';
|
||||
|
||||
const getActiveElementText = async (page) => {
|
||||
const activeElement = await page.evaluateHandle(() => document.activeElement);
|
||||
return await page.evaluate(el => el && el.innerText, activeElement);
|
||||
}
|
||||
|
||||
test('accordion: a11y', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/accordion/test/a11y?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('accordion: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/accordion/test/basic?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('accordion:rtl: a11y', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/accordion/test/a11y?ionic:_testing=true&rtl=true'
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('accordion: keyboard navigation', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/accordion/test/a11y?ionic:_testing=true'
|
||||
});
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
expect(await getActiveElementText(page)).toEqual('Personal Information');
|
||||
|
||||
await page.keyboard.press('ArrowDown');
|
||||
expect(await getActiveElementText(page)).toEqual('Billing Address');
|
||||
|
||||
await page.keyboard.press('ArrowDown');
|
||||
expect(await getActiveElementText(page)).toEqual('Shipping Address');
|
||||
|
||||
await page.keyboard.press('ArrowDown');
|
||||
expect(await getActiveElementText(page)).toEqual('Personal Information');
|
||||
|
||||
await page.keyboard.press('ArrowUp');
|
||||
expect(await getActiveElementText(page)).toEqual('Shipping Address');
|
||||
});
|
||||
|
||||
test('accordion: axe', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/accordion/test/standalone?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const results = await new AxePuppeteer(page).analyze();
|
||||
expect(results.violations.length).toEqual(0);
|
||||
});
|
133
core/src/components/accordion/test/standalone/index.html
Normal file
133
core/src/components/accordion/test/standalone/index.html
Normal file
@ -0,0 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Accordion - Basic</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1 class="ion-padding">Accordion</h1>
|
||||
|
||||
<div class="grid ion-padding">
|
||||
<div class="grid-item">
|
||||
<h2>Default Accordion</h2>
|
||||
|
||||
<ion-accordion-group>
|
||||
<ion-accordion>
|
||||
<button class="custom-accordion-button" slot="header">
|
||||
Attractions
|
||||
</button>
|
||||
<div class="custom-accordion-content" slot="content">
|
||||
Some content
|
||||
</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<button class="custom-accordion-button" slot="header">
|
||||
Second one
|
||||
</button>
|
||||
<div class="custom-accordion-content" slot="content">
|
||||
Some content
|
||||
</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Readonly Accordion (1st)</h2>
|
||||
|
||||
<ion-accordion-group value="expanded">
|
||||
<ion-accordion readonly value="expanded">
|
||||
<button class="custom-accordion-button" slot="header">
|
||||
Readonly - expanded
|
||||
</button>
|
||||
<div class="custom-accordion-content" slot="content">
|
||||
<input id="checkme-1" type="checkbox"></input>
|
||||
<label for="checkme-1">Check me!</label>
|
||||
</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="collapsed">
|
||||
<button class="custom-accordion-button" slot="header">
|
||||
Collapsed
|
||||
</button>
|
||||
<div class="custom-accordion-content" slot="content">
|
||||
<input id="checkme-2" type="checkbox"></input>
|
||||
<label for="checkme-2">Check me!</label>
|
||||
</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Custom Accordion Colors</h2>
|
||||
|
||||
<ion-accordion-group expand="inset" class="custom-colors">
|
||||
<ion-accordion>
|
||||
<button class="custom-accordion-button" slot="header">
|
||||
Danger
|
||||
</button>
|
||||
<div class="custom-accordion-content" slot="content">
|
||||
Some content
|
||||
</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion>
|
||||
<button class="custom-accordion-button" slot="header">
|
||||
Primary
|
||||
</button>
|
||||
<div class="custom-accordion-content" slot="content">
|
||||
Some content
|
||||
</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
|
||||
.custom-accordion-button {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
text-align: left;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.custom-accordion-content {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
ion-accordion {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
ion-accordion:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.custom-colors ion-accordion:first-child {
|
||||
background: rgb(245, 116, 116);
|
||||
border-bottom-color: red;
|
||||
}
|
||||
|
||||
.custom-colors ion-accordion:last-child {
|
||||
background: rgb(160, 160, 250);
|
||||
}
|
||||
</style>
|
||||
</html>
|
221
core/src/components/accordion/usage/angular.md
Normal file
221
core/src/components/accordion/usage/angular.md
Normal file
@ -0,0 +1,221 @@
|
||||
```html
|
||||
<!-- Basic -->
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Custom Icon -->
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Open Accordion -->
|
||||
<ion-accordion-group value="colors">
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Multiple Accordions -->
|
||||
<ion-accordion-group [multiple]="true" [value]="['colors', 'numbers']">
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
```
|
226
core/src/components/accordion/usage/javascript.md
Normal file
226
core/src/components/accordion/usage/javascript.md
Normal file
@ -0,0 +1,226 @@
|
||||
```html
|
||||
<!-- Basic -->
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Custom Icon -->
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Open Accordion -->
|
||||
<ion-accordion-group value="colors">
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Multiple Accordions -->
|
||||
<ion-accordion-group multiple="true">
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<script>
|
||||
let accordionGroup = document.querySelector('ion-accordion-group');
|
||||
accordionGroup.value = ['colors', 'numbers'];
|
||||
</script>
|
||||
```
|
228
core/src/components/accordion/usage/react.md
Normal file
228
core/src/components/accordion/usage/react.md
Normal file
@ -0,0 +1,228 @@
|
||||
```tsx
|
||||
import React from 'react';
|
||||
|
||||
import { IonContent, IonAccordionGroup, IonAccordion, IonItem, IonLabel } from '@ionic/react';
|
||||
import { arrowDownCircle } from 'ionicons/icons';
|
||||
|
||||
export const AccordionExample: React.FC = () => (
|
||||
{/*-- Basic --*/}
|
||||
<IonAccordionGroup>
|
||||
<IonAccordion value="colors">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Colors</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Red</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Green</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Blue</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="shapes">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Shapes</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Circle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Triangle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Square</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="numbers">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Numbers</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>1</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>2</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>3</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
</IonAccordionGroup>
|
||||
|
||||
{/*-- Custom Icon --*/}
|
||||
<IonAccordionGroup>
|
||||
<IonAccordion value="colors" toggleIcon={arrowDownCircle}>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Colors</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Red</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Green</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Blue</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="shapes" toggleIcon={arrowDownCircle}>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Shapes</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Circle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Triangle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Square</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="numbers" toggleIcon={arrowDownCircle}>
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Numbers</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>1</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>2</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>3</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
</IonAccordionGroup>
|
||||
|
||||
{/*-- Open Accordion --*/}
|
||||
<IonAccordionGroup value="colors">
|
||||
<IonAccordion value="colors">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Colors</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Red</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Green</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Blue</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="shapes">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Shapes</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Circle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Triangle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Square</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="numbers">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Numbers</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>1</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>2</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>3</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
</IonAccordionGroup>
|
||||
|
||||
{/*-- Multiple Accordions --*/}
|
||||
<IonAccordionGroup multiple={true} value={['colors', 'numbers']}>
|
||||
<IonAccordion value="colors">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Colors</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Red</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Green</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Blue</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="shapes">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Shapes</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>Circle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Triangle</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>Square</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
<IonAccordion value="numbers">
|
||||
<IonItem slot="header">
|
||||
<IonLabel>Numbers</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
<ion-list slot="content">
|
||||
<IonItem>
|
||||
<IonLabel>1</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>2</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem>
|
||||
<IonLabel>3</IonLabel>
|
||||
</IonItem>
|
||||
</ion-list>
|
||||
</IonAccordion>
|
||||
</IonAccordionGroup>
|
||||
);
|
||||
```
|
233
core/src/components/accordion/usage/stencil.md
Normal file
233
core/src/components/accordion/usage/stencil.md
Normal file
@ -0,0 +1,233 @@
|
||||
```tsx
|
||||
import { Component, h } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'accordion-example',
|
||||
styleUrl: 'accordion-example.css'
|
||||
})
|
||||
export const AccordionExample {
|
||||
render() {
|
||||
return [
|
||||
// Basic
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
// Custom Icon
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers" toggle-icon="arrow-down-circle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
// Open Accordion
|
||||
<ion-accordion-group value="colors">
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
// Multiple Accordions
|
||||
<ion-accordion-group multiple={true} value={['colors', 'numbers']}>
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
];
|
||||
}
|
||||
);
|
||||
```
|
236
core/src/components/accordion/usage/vue.md
Normal file
236
core/src/components/accordion/usage/vue.md
Normal file
@ -0,0 +1,236 @@
|
||||
```html
|
||||
<template>
|
||||
<!-- Basic -->
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Custom Icon -->
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="colors" :toggle-icon="arrowDownCircle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes" :toggle-icon="arrowDownCircle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers" :toggle-icon="arrowDownCircle">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Open Accordion -->
|
||||
<ion-accordion-group value="colors">
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
|
||||
<!-- Multiple Accordions -->
|
||||
<ion-accordion-group :multiple="true" :value="['colors', 'numbers']">
|
||||
<ion-accordion value="colors">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Colors</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Red</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Green</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Blue</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="shapes">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Shapes</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>Circle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Triangle</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Square</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="numbers">
|
||||
<ion-item slot="header">
|
||||
<ion-label>Numbers</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-list slot="content">
|
||||
<ion-item>
|
||||
<ion-label>1</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>2</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>3</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonAccordion, IonAccordionGroup, IonItem, IonLabel } from '@ionic/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { arrowDownCircle } from 'ionicons/icons';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonAccordion, IonAccordionGroup, IonItem, IonLabel },
|
||||
setup() {
|
||||
return { arrowDownCircle }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
@ -3,7 +3,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface';
|
||||
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
|
||||
import { clamp, componentOnReady, getElementRoot, raf } from '../../utils/helpers';
|
||||
import { clamp, componentOnReady, getElementRoot, raf, transitionEndAsync } from '../../utils/helpers';
|
||||
import { hapticImpact } from '../../utils/native/haptic';
|
||||
|
||||
import {
|
||||
@ -14,7 +14,6 @@ import {
|
||||
handleScrollWhileRefreshing,
|
||||
setSpinnerOpacity,
|
||||
shouldUseNativeRefresher,
|
||||
transitionEndAsync,
|
||||
translateElement
|
||||
} from './refresher.utils';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { writeTask } from '@stencil/core';
|
||||
|
||||
import { createAnimation } from '../../utils/animation/animation';
|
||||
import { clamp, componentOnReady } from '../../utils/helpers';
|
||||
import { clamp, componentOnReady, transitionEndAsync } from '../../utils/helpers';
|
||||
import { isPlatform } from '../../utils/platform';
|
||||
|
||||
// MD Native Refresher
|
||||
@ -198,46 +198,3 @@ export const shouldUseNativeRefresher = async (referenceEl: HTMLIonRefresherElem
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export const transitionEndAsync = (el: HTMLElement | null, expectedDuration = 0) => {
|
||||
return new Promise(resolve => {
|
||||
transitionEnd(el, expectedDuration, resolve);
|
||||
});
|
||||
};
|
||||
|
||||
const transitionEnd = (el: HTMLElement | null, expectedDuration = 0, callback: (ev?: TransitionEvent) => void) => {
|
||||
let unRegTrans: (() => void) | undefined;
|
||||
let animationTimeout: any;
|
||||
const opts: any = { passive: true };
|
||||
const ANIMATION_FALLBACK_TIMEOUT = 500;
|
||||
|
||||
const unregister = () => {
|
||||
if (unRegTrans) {
|
||||
unRegTrans();
|
||||
}
|
||||
};
|
||||
|
||||
const onTransitionEnd = (ev?: Event) => {
|
||||
if (ev === undefined || el === ev.target) {
|
||||
unregister();
|
||||
callback(ev as TransitionEvent);
|
||||
}
|
||||
};
|
||||
|
||||
if (el) {
|
||||
el.addEventListener('webkitTransitionEnd', onTransitionEnd, opts);
|
||||
el.addEventListener('transitionend', onTransitionEnd, opts);
|
||||
animationTimeout = setTimeout(onTransitionEnd, expectedDuration + ANIMATION_FALLBACK_TIMEOUT);
|
||||
|
||||
unRegTrans = () => {
|
||||
if (animationTimeout) {
|
||||
clearTimeout(animationTimeout);
|
||||
animationTimeout = undefined;
|
||||
}
|
||||
el.removeEventListener('webkitTransitionEnd', onTransitionEnd, opts);
|
||||
el.removeEventListener('transitionend', onTransitionEnd, opts);
|
||||
};
|
||||
}
|
||||
|
||||
return unregister;
|
||||
};
|
||||
|
@ -201,3 +201,41 @@ ion-card-header.ion-color .ion-inherit-color {
|
||||
.md .menu-content-push {
|
||||
box-shadow: $menu-md-box-shadow;
|
||||
}
|
||||
|
||||
// Accordion Styles
|
||||
ion-accordion-group.accordion-group-expand-inset ion-accordion:first-of-type {
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
ion-accordion-group.accordion-group-expand-inset ion-accordion:last-of-type {
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
ion-accordion-group ion-accordion:last-of-type ion-item {
|
||||
--border-width: 0px;
|
||||
}
|
||||
|
||||
ion-accordion.accordion-animated .ion-accordion-toggle-icon {
|
||||
transition: 300ms transform cubic-bezier(0.25, 0.8, 0.5, 1);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
ion-accordion .ion-accordion-toggle-icon {
|
||||
/* stylelint-disable declaration-no-important */
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
ion-accordion.accordion-expanding .ion-accordion-toggle-icon,
|
||||
ion-accordion.accordion-expanded .ion-accordion-toggle-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
ion-accordion-group.accordion-group-expand-inset.md ion-accordion.accordion-previous ion-item[slot="header"] {
|
||||
--border-width: 0px;
|
||||
--inner-border-width: 0px;
|
||||
}
|
||||
|
||||
ion-accordion-group.accordion-group-expand-inset.md ion-accordion.accordion-expanding:first-of-type,
|
||||
ion-accordion-group.accordion-group-expand-inset.md ion-accordion.accordion-expanded:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
1
core/src/interface.d.ts
vendored
1
core/src/interface.d.ts
vendored
@ -2,6 +2,7 @@
|
||||
import { Components as IoniconsComponents, JSX as IoniconsJSX } from 'ionicons';
|
||||
export * from './components';
|
||||
export * from './index';
|
||||
export * from './components/accordion-group/accordion-group-interface';
|
||||
export * from './components/alert/alert-interface';
|
||||
export * from './components/action-sheet/action-sheet-interface';
|
||||
export * from './components/content/content-interface';
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
const ION_FOCUSED = 'ion-focused';
|
||||
const ION_FOCUSABLE = 'ion-focusable';
|
||||
const FOCUS_KEYS = ['Tab', 'ArrowDown', 'Space', 'Escape', ' ', 'Shift', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp'];
|
||||
const FOCUS_KEYS = ['Tab', 'ArrowDown', 'Space', 'Escape', ' ', 'Shift', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Home', 'End'];
|
||||
|
||||
export const startFocusVisible = () => {
|
||||
|
||||
|
@ -5,6 +5,64 @@ import { Side } from '../interface';
|
||||
declare const __zone_symbol__requestAnimationFrame: any;
|
||||
declare const requestAnimationFrame: any;
|
||||
|
||||
export const transitionEndAsync = (el: HTMLElement | null, expectedDuration = 0) => {
|
||||
return new Promise(resolve => {
|
||||
transitionEnd(el, expectedDuration, resolve);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows developer to wait for a transition
|
||||
* to finish and fallback to a timer if the
|
||||
* transition is cancelled or otherwise
|
||||
* never finishes. Also see transitionEndAsync
|
||||
* which is an await-able version of this.
|
||||
*/
|
||||
const transitionEnd = (el: HTMLElement | null, expectedDuration = 0, callback: (ev?: TransitionEvent) => void) => {
|
||||
let unRegTrans: (() => void) | undefined;
|
||||
let animationTimeout: any;
|
||||
const opts: any = { passive: true };
|
||||
const ANIMATION_FALLBACK_TIMEOUT = 500;
|
||||
|
||||
const unregister = () => {
|
||||
if (unRegTrans) {
|
||||
unRegTrans();
|
||||
}
|
||||
};
|
||||
|
||||
const onTransitionEnd = (ev?: Event) => {
|
||||
if (ev === undefined || el === ev.target) {
|
||||
unregister();
|
||||
callback(ev as TransitionEvent);
|
||||
}
|
||||
};
|
||||
|
||||
if (el) {
|
||||
el.addEventListener('webkitTransitionEnd', onTransitionEnd, opts);
|
||||
el.addEventListener('transitionend', onTransitionEnd, opts);
|
||||
animationTimeout = setTimeout(onTransitionEnd, expectedDuration + ANIMATION_FALLBACK_TIMEOUT);
|
||||
|
||||
unRegTrans = () => {
|
||||
if (animationTimeout) {
|
||||
clearTimeout(animationTimeout);
|
||||
animationTimeout = undefined;
|
||||
}
|
||||
el.removeEventListener('webkitTransitionEnd', onTransitionEnd, opts);
|
||||
el.removeEventListener('transitionend', onTransitionEnd, opts);
|
||||
};
|
||||
}
|
||||
|
||||
return unregister;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to wait for
|
||||
* componentOnReady on Stencil
|
||||
* components if not using a
|
||||
* custom elements build or
|
||||
* quickly resolve if using
|
||||
* a custom elements build.
|
||||
*/
|
||||
export const componentOnReady = (el: any, callback: any) => {
|
||||
if (el.componentOnReady) {
|
||||
el.componentOnReady().then(callback);
|
||||
|
@ -52,6 +52,7 @@ export const config: Config = {
|
||||
{ components: ['ion-toast'] },
|
||||
{ components: ['ion-toggle'] },
|
||||
{ components: ['ion-virtual-scroll'] },
|
||||
{ components: ['ion-accordion-group', 'ion-accordion'] },
|
||||
],
|
||||
plugins: [
|
||||
sass({
|
||||
|
@ -8,6 +8,25 @@ import type { JSX } from '@ionic/core';
|
||||
|
||||
|
||||
|
||||
export const IonAccordion = /*@__PURE__*/ defineContainer<JSX.IonAccordion>('ion-accordion', [
|
||||
'value',
|
||||
'disabled',
|
||||
'readonly',
|
||||
'toggleIcon',
|
||||
'toggleIconSlot'
|
||||
]);
|
||||
|
||||
|
||||
export const IonAccordionGroup = /*@__PURE__*/ defineContainer<JSX.IonAccordionGroup>('ion-accordion-group', [
|
||||
'multiple',
|
||||
'value',
|
||||
'disabled',
|
||||
'readonly',
|
||||
'expand',
|
||||
'ionChange'
|
||||
]);
|
||||
|
||||
|
||||
export const IonAvatar = /*@__PURE__*/ defineContainer<JSX.IonAvatar>('ion-avatar');
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user