mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
feat(segment): add segment view and content components
This commit is contained in:
@ -1606,6 +1606,10 @@ ion-segment-button,part,indicator
|
||||
ion-segment-button,part,indicator-background
|
||||
ion-segment-button,part,native
|
||||
|
||||
ion-segment-content,shadow
|
||||
|
||||
ion-segment-view,shadow
|
||||
|
||||
ion-select,shadow
|
||||
ion-select,prop,cancelText,string,'Cancel',false,false
|
||||
ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
|
26
core/src/components.d.ts
vendored
26
core/src/components.d.ts
vendored
@ -2712,6 +2712,10 @@ export namespace Components {
|
||||
*/
|
||||
"value": SegmentValue;
|
||||
}
|
||||
interface IonSegmentContent {
|
||||
}
|
||||
interface IonSegmentView {
|
||||
}
|
||||
interface IonSelect {
|
||||
/**
|
||||
* The text to display on the cancel button.
|
||||
@ -4409,6 +4413,18 @@ declare global {
|
||||
prototype: HTMLIonSegmentButtonElement;
|
||||
new (): HTMLIonSegmentButtonElement;
|
||||
};
|
||||
interface HTMLIonSegmentContentElement extends Components.IonSegmentContent, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonSegmentContentElement: {
|
||||
prototype: HTMLIonSegmentContentElement;
|
||||
new (): HTMLIonSegmentContentElement;
|
||||
};
|
||||
interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonSegmentViewElement: {
|
||||
prototype: HTMLIonSegmentViewElement;
|
||||
new (): HTMLIonSegmentViewElement;
|
||||
};
|
||||
interface HTMLIonSelectElementEventMap {
|
||||
"ionChange": SelectChangeEventDetail;
|
||||
"ionCancel": void;
|
||||
@ -4718,6 +4734,8 @@ declare global {
|
||||
"ion-searchbar": HTMLIonSearchbarElement;
|
||||
"ion-segment": HTMLIonSegmentElement;
|
||||
"ion-segment-button": HTMLIonSegmentButtonElement;
|
||||
"ion-segment-content": HTMLIonSegmentContentElement;
|
||||
"ion-segment-view": HTMLIonSegmentViewElement;
|
||||
"ion-select": HTMLIonSelectElement;
|
||||
"ion-select-option": HTMLIonSelectOptionElement;
|
||||
"ion-select-popover": HTMLIonSelectPopoverElement;
|
||||
@ -7468,6 +7486,10 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"value"?: SegmentValue;
|
||||
}
|
||||
interface IonSegmentContent {
|
||||
}
|
||||
interface IonSegmentView {
|
||||
}
|
||||
interface IonSelect {
|
||||
/**
|
||||
* The text to display on the cancel button.
|
||||
@ -8159,6 +8181,8 @@ declare namespace LocalJSX {
|
||||
"ion-searchbar": IonSearchbar;
|
||||
"ion-segment": IonSegment;
|
||||
"ion-segment-button": IonSegmentButton;
|
||||
"ion-segment-content": IonSegmentContent;
|
||||
"ion-segment-view": IonSegmentView;
|
||||
"ion-select": IonSelect;
|
||||
"ion-select-option": IonSelectOption;
|
||||
"ion-select-popover": IonSelectPopover;
|
||||
@ -8258,6 +8282,8 @@ declare module "@stencil/core" {
|
||||
"ion-searchbar": LocalJSX.IonSearchbar & JSXBase.HTMLAttributes<HTMLIonSearchbarElement>;
|
||||
"ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes<HTMLIonSegmentElement>;
|
||||
"ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes<HTMLIonSegmentButtonElement>;
|
||||
"ion-segment-content": LocalJSX.IonSegmentContent & JSXBase.HTMLAttributes<HTMLIonSegmentContentElement>;
|
||||
"ion-segment-view": LocalJSX.IonSegmentView & JSXBase.HTMLAttributes<HTMLIonSegmentViewElement>;
|
||||
"ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes<HTMLIonSelectElement>;
|
||||
"ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;
|
||||
"ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes<HTMLIonSelectPopoverElement>;
|
||||
|
10
core/src/components/segment-content/segment-content.scss
Normal file
10
core/src/components/segment-content/segment-content.scss
Normal file
@ -0,0 +1,10 @@
|
||||
// Segment Content
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
scroll-snap-align: center;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 100%;
|
||||
}
|
17
core/src/components/segment-content/segment-content.tsx
Normal file
17
core/src/components/segment-content/segment-content.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Host, h } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-segment-content',
|
||||
styleUrl: 'segment-content.scss',
|
||||
shadow: true,
|
||||
})
|
||||
export class SegmentContent implements ComponentInterface {
|
||||
render() {
|
||||
return (
|
||||
<Host>
|
||||
<slot></slot>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
20
core/src/components/segment-view/segment-view.scss
Normal file
20
core/src/components/segment-view/segment-view.scss
Normal file
@ -0,0 +1,20 @@
|
||||
// Segment View
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
|
||||
overflow-x: scroll;
|
||||
scroll-snap-type: x mandatory;
|
||||
|
||||
/* Hide scrollbar in Firefox */
|
||||
scrollbar-width: none;
|
||||
|
||||
/* Hide scrollbar in IE and Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar in webkit */
|
||||
:host::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
90
core/src/components/segment-view/segment-view.tsx
Normal file
90
core/src/components/segment-view/segment-view.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Element, Host, Listen, h } from '@stencil/core';
|
||||
import { addEventListener, removeEventListener } from '@utils/helpers';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-segment-view',
|
||||
styleUrl: 'segment-view.scss',
|
||||
shadow: true,
|
||||
})
|
||||
export class SegmentView implements ComponentInterface {
|
||||
private segmentEl: HTMLIonSegmentElement | null = null;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@Listen('scroll')
|
||||
segmentViewScroll(ev: any) {
|
||||
const { segmentEl } = this;
|
||||
|
||||
const atSnappingPoint = ev.target.scrollLeft % ev.target.offsetWidth === 0;
|
||||
|
||||
if (atSnappingPoint) {
|
||||
const index = Math.round(ev.target.scrollLeft / ev.target.offsetWidth);
|
||||
const segmentButton = this.getSegmentButtonAtIndex(index);
|
||||
|
||||
if (segmentEl) {
|
||||
segmentEl.value = segmentButton.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const segmentEl = (this.segmentEl = document.querySelector(`ion-segment[view=${this.el.id}]`));
|
||||
if (segmentEl) {
|
||||
addEventListener(segmentEl, 'ionChange', this.updateSection);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
const segmentEl = this.segmentEl;
|
||||
if (segmentEl) {
|
||||
removeEventListener(segmentEl, 'ionChange', this.updateSection);
|
||||
this.segmentEl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private updateSection = () => {
|
||||
const { segmentEl } = this;
|
||||
|
||||
if (segmentEl) {
|
||||
const value = segmentEl.value;
|
||||
const index = this.getSegmentButtonIndexWithValue(value);
|
||||
this.setSection(index);
|
||||
}
|
||||
};
|
||||
|
||||
private setSection = (index: number) => {
|
||||
const sectionWidth = this.el.offsetWidth;
|
||||
this.el.scrollTo({
|
||||
top: 0,
|
||||
left: index * sectionWidth,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
private getSegmentButtons(): HTMLIonSegmentButtonElement[] {
|
||||
const { segmentEl } = this;
|
||||
|
||||
if (!segmentEl) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(segmentEl.querySelectorAll('ion-segment-button'));
|
||||
}
|
||||
|
||||
private getSegmentButtonAtIndex(index: number) {
|
||||
return this.getSegmentButtons()[index];
|
||||
}
|
||||
|
||||
private getSegmentButtonIndexWithValue(value: any) {
|
||||
return this.getSegmentButtons().findIndex((b) => b.value === value);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Host>
|
||||
<slot></slot>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
55
core/src/components/segment-view/test/basic/index.html
Normal file
55
core/src/components/segment-view/test/basic/index.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Segment View - Basic</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="listenForEvent()">
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Segment View - Basic</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-segment view="myView" value="Paid">
|
||||
<ion-segment-button value="Paid">
|
||||
<ion-label>Paid</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="Free">
|
||||
<ion-label>Free</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="Top">
|
||||
<ion-label>Top</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-segment-view id="myView">
|
||||
<ion-segment-content>
|
||||
Paid
|
||||
</ion-segment-content>
|
||||
<ion-segment-content>
|
||||
Free
|
||||
</ion-segment-content>
|
||||
<ion-segment-content>
|
||||
Top
|
||||
</ion-segment-content>
|
||||
</ion-segment-view>
|
||||
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
@ -69,6 +69,8 @@ export const DIRECTIVES = [
|
||||
d.IonSearchbar,
|
||||
d.IonSegment,
|
||||
d.IonSegmentButton,
|
||||
d.IonSegmentContent,
|
||||
d.IonSegmentView,
|
||||
d.IonSelect,
|
||||
d.IonSelectOption,
|
||||
d.IonSkeletonText,
|
||||
|
@ -2005,6 +2005,48 @@ export class IonSegmentButton {
|
||||
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment-content',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: [],
|
||||
})
|
||||
export class IonSegmentContent {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonSegmentContent extends Components.IonSegmentContent {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment-view',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: [],
|
||||
})
|
||||
export class IonSegmentView {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonSegmentView extends Components.IonSegmentView {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
methods: ['open']
|
||||
|
@ -65,6 +65,8 @@ import { defineCustomElement as defineIonReorderGroup } from '@ionic/core/compon
|
||||
import { defineCustomElement as defineIonRippleEffect } from '@ionic/core/components/ion-ripple-effect.js';
|
||||
import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-row.js';
|
||||
import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js';
|
||||
import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js';
|
||||
import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js';
|
||||
import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
|
||||
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
|
||||
import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js';
|
||||
@ -1838,6 +1840,52 @@ export class IonSegmentButton {
|
||||
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonSegmentContent
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment-content',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: [],
|
||||
standalone: true
|
||||
})
|
||||
export class IonSegmentContent {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonSegmentContent extends Components.IonSegmentContent {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonSegmentView
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment-view',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: [],
|
||||
standalone: true
|
||||
})
|
||||
export class IonSegmentView {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonSegmentView extends Components.IonSegmentView {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonSelectOption,
|
||||
inputs: ['disabled', 'value']
|
||||
|
@ -61,6 +61,8 @@ import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-
|
||||
import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js';
|
||||
import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js';
|
||||
import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js';
|
||||
import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js';
|
||||
import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js';
|
||||
import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js';
|
||||
import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
|
||||
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
|
||||
@ -130,6 +132,8 @@ export const IonRow = /*@__PURE__*/createReactComponent<JSX.IonRow, HTMLIonRowEl
|
||||
export const IonSearchbar = /*@__PURE__*/createReactComponent<JSX.IonSearchbar, HTMLIonSearchbarElement>('ion-searchbar', undefined, undefined, defineIonSearchbar);
|
||||
export const IonSegment = /*@__PURE__*/createReactComponent<JSX.IonSegment, HTMLIonSegmentElement>('ion-segment', undefined, undefined, defineIonSegment);
|
||||
export const IonSegmentButton = /*@__PURE__*/createReactComponent<JSX.IonSegmentButton, HTMLIonSegmentButtonElement>('ion-segment-button', undefined, undefined, defineIonSegmentButton);
|
||||
export const IonSegmentContent = /*@__PURE__*/createReactComponent<JSX.IonSegmentContent, HTMLIonSegmentContentElement>('ion-segment-content', undefined, undefined, defineIonSegmentContent);
|
||||
export const IonSegmentView = /*@__PURE__*/createReactComponent<JSX.IonSegmentView, HTMLIonSegmentViewElement>('ion-segment-view', undefined, undefined, defineIonSegmentView);
|
||||
export const IonSelect = /*@__PURE__*/createReactComponent<JSX.IonSelect, HTMLIonSelectElement>('ion-select', undefined, undefined, defineIonSelect);
|
||||
export const IonSelectOption = /*@__PURE__*/createReactComponent<JSX.IonSelectOption, HTMLIonSelectOptionElement>('ion-select-option', undefined, undefined, defineIonSelectOption);
|
||||
export const IonSkeletonText = /*@__PURE__*/createReactComponent<JSX.IonSkeletonText, HTMLIonSkeletonTextElement>('ion-skeleton-text', undefined, undefined, defineIonSkeletonText);
|
||||
|
@ -67,6 +67,8 @@ import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-
|
||||
import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js';
|
||||
import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js';
|
||||
import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js';
|
||||
import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js';
|
||||
import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js';
|
||||
import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js';
|
||||
import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
|
||||
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
|
||||
@ -752,6 +754,12 @@ export const IonSegmentButton = /*@__PURE__*/ defineContainer<JSX.IonSegmentButt
|
||||
'value', 'ion-change');
|
||||
|
||||
|
||||
export const IonSegmentContent = /*@__PURE__*/ defineContainer<JSX.IonSegmentContent>('ion-segment-content', defineIonSegmentContent);
|
||||
|
||||
|
||||
export const IonSegmentView = /*@__PURE__*/ defineContainer<JSX.IonSegmentView>('ion-segment-view', defineIonSegmentView);
|
||||
|
||||
|
||||
export const IonSelect = /*@__PURE__*/ defineContainer<JSX.IonSelect, JSX.IonSelect["value"]>('ion-select', defineIonSelect, [
|
||||
'cancelText',
|
||||
'color',
|
||||
|
Reference in New Issue
Block a user