Skip to content

Commit 0324a78

Browse files
committed
feat(segment): add segment view and content components
1 parent fa74077 commit 0324a78

File tree

12 files changed

+326
-0
lines changed

12 files changed

+326
-0
lines changed

core/api.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,10 @@ ion-segment-button,part,indicator
16061606
ion-segment-button,part,indicator-background
16071607
ion-segment-button,part,native
16081608

1609+
ion-segment-content,shadow
1610+
1611+
ion-segment-view,shadow
1612+
16091613
ion-select,shadow
16101614
ion-select,prop,cancelText,string,'Cancel',false,false
16111615
ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true

core/src/components.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,6 +2712,10 @@ export namespace Components {
27122712
*/
27132713
"value": SegmentValue;
27142714
}
2715+
interface IonSegmentContent {
2716+
}
2717+
interface IonSegmentView {
2718+
}
27152719
interface IonSelect {
27162720
/**
27172721
* The text to display on the cancel button.
@@ -4409,6 +4413,18 @@ declare global {
44094413
prototype: HTMLIonSegmentButtonElement;
44104414
new (): HTMLIonSegmentButtonElement;
44114415
};
4416+
interface HTMLIonSegmentContentElement extends Components.IonSegmentContent, HTMLStencilElement {
4417+
}
4418+
var HTMLIonSegmentContentElement: {
4419+
prototype: HTMLIonSegmentContentElement;
4420+
new (): HTMLIonSegmentContentElement;
4421+
};
4422+
interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement {
4423+
}
4424+
var HTMLIonSegmentViewElement: {
4425+
prototype: HTMLIonSegmentViewElement;
4426+
new (): HTMLIonSegmentViewElement;
4427+
};
44124428
interface HTMLIonSelectElementEventMap {
44134429
"ionChange": SelectChangeEventDetail;
44144430
"ionCancel": void;
@@ -4718,6 +4734,8 @@ declare global {
47184734
"ion-searchbar": HTMLIonSearchbarElement;
47194735
"ion-segment": HTMLIonSegmentElement;
47204736
"ion-segment-button": HTMLIonSegmentButtonElement;
4737+
"ion-segment-content": HTMLIonSegmentContentElement;
4738+
"ion-segment-view": HTMLIonSegmentViewElement;
47214739
"ion-select": HTMLIonSelectElement;
47224740
"ion-select-option": HTMLIonSelectOptionElement;
47234741
"ion-select-popover": HTMLIonSelectPopoverElement;
@@ -7468,6 +7486,10 @@ declare namespace LocalJSX {
74687486
*/
74697487
"value"?: SegmentValue;
74707488
}
7489+
interface IonSegmentContent {
7490+
}
7491+
interface IonSegmentView {
7492+
}
74717493
interface IonSelect {
74727494
/**
74737495
* The text to display on the cancel button.
@@ -8159,6 +8181,8 @@ declare namespace LocalJSX {
81598181
"ion-searchbar": IonSearchbar;
81608182
"ion-segment": IonSegment;
81618183
"ion-segment-button": IonSegmentButton;
8184+
"ion-segment-content": IonSegmentContent;
8185+
"ion-segment-view": IonSegmentView;
81628186
"ion-select": IonSelect;
81638187
"ion-select-option": IonSelectOption;
81648188
"ion-select-popover": IonSelectPopover;
@@ -8258,6 +8282,8 @@ declare module "@stencil/core" {
82588282
"ion-searchbar": LocalJSX.IonSearchbar & JSXBase.HTMLAttributes<HTMLIonSearchbarElement>;
82598283
"ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes<HTMLIonSegmentElement>;
82608284
"ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes<HTMLIonSegmentButtonElement>;
8285+
"ion-segment-content": LocalJSX.IonSegmentContent & JSXBase.HTMLAttributes<HTMLIonSegmentContentElement>;
8286+
"ion-segment-view": LocalJSX.IonSegmentView & JSXBase.HTMLAttributes<HTMLIonSegmentViewElement>;
82618287
"ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes<HTMLIonSelectElement>;
82628288
"ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;
82638289
"ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes<HTMLIonSelectPopoverElement>;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Segment Content
2+
// --------------------------------------------------
3+
4+
:host {
5+
scroll-snap-align: center;
6+
7+
flex-shrink: 0;
8+
9+
width: 100%;
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { ComponentInterface } from '@stencil/core';
2+
import { Component, Host, h } from '@stencil/core';
3+
4+
@Component({
5+
tag: 'ion-segment-content',
6+
styleUrl: 'segment-content.scss',
7+
shadow: true,
8+
})
9+
export class SegmentContent implements ComponentInterface {
10+
render() {
11+
return (
12+
<Host>
13+
<slot></slot>
14+
</Host>
15+
);
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Segment View
2+
// --------------------------------------------------
3+
4+
:host {
5+
display: flex;
6+
7+
overflow-x: scroll;
8+
scroll-snap-type: x mandatory;
9+
10+
/* Hide scrollbar in Firefox */
11+
scrollbar-width: none;
12+
13+
/* Hide scrollbar in IE and Edge */
14+
-ms-overflow-style: none;
15+
}
16+
17+
/* Hide scrollbar in webkit */
18+
:host::-webkit-scrollbar {
19+
display: none;
20+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type { ComponentInterface } from '@stencil/core';
2+
import { Component, Element, Host, Listen, h } from '@stencil/core';
3+
import { addEventListener, removeEventListener } from '@utils/helpers';
4+
5+
@Component({
6+
tag: 'ion-segment-view',
7+
styleUrl: 'segment-view.scss',
8+
shadow: true,
9+
})
10+
export class SegmentView implements ComponentInterface {
11+
private segmentEl: HTMLIonSegmentElement | null = null;
12+
13+
@Element() el!: HTMLElement;
14+
15+
@Listen('scroll')
16+
segmentViewScroll(ev: any) {
17+
const { segmentEl } = this;
18+
19+
const atSnappingPoint = ev.target.scrollLeft % ev.target.offsetWidth === 0;
20+
21+
if (atSnappingPoint) {
22+
const index = Math.round(ev.target.scrollLeft / ev.target.offsetWidth);
23+
const segmentButton = this.getSegmentButtonAtIndex(index);
24+
25+
if (segmentEl) {
26+
segmentEl.value = segmentButton.value;
27+
}
28+
}
29+
}
30+
31+
connectedCallback() {
32+
const segmentEl = (this.segmentEl = document.querySelector(`ion-segment[view=${this.el.id}]`));
33+
if (segmentEl) {
34+
addEventListener(segmentEl, 'ionChange', this.updateSection);
35+
}
36+
}
37+
38+
disconnectedCallback() {
39+
const segmentEl = this.segmentEl;
40+
if (segmentEl) {
41+
removeEventListener(segmentEl, 'ionChange', this.updateSection);
42+
this.segmentEl = null;
43+
}
44+
}
45+
46+
private updateSection = () => {
47+
const { segmentEl } = this;
48+
49+
if (segmentEl) {
50+
const value = segmentEl.value;
51+
const index = this.getSegmentButtonIndexWithValue(value);
52+
this.setSection(index);
53+
}
54+
};
55+
56+
private setSection = (index: number) => {
57+
const sectionWidth = this.el.offsetWidth;
58+
this.el.scrollTo({
59+
top: 0,
60+
left: index * sectionWidth,
61+
behavior: 'smooth',
62+
});
63+
};
64+
65+
private getSegmentButtons(): HTMLIonSegmentButtonElement[] {
66+
const { segmentEl } = this;
67+
68+
if (!segmentEl) {
69+
return [];
70+
}
71+
72+
return Array.from(segmentEl.querySelectorAll('ion-segment-button'));
73+
}
74+
75+
private getSegmentButtonAtIndex(index: number) {
76+
return this.getSegmentButtons()[index];
77+
}
78+
79+
private getSegmentButtonIndexWithValue(value: any) {
80+
return this.getSegmentButtons().findIndex((b) => b.value === value);
81+
}
82+
83+
render() {
84+
return (
85+
<Host>
86+
<slot></slot>
87+
</Host>
88+
);
89+
}
90+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Segment View - Basic</title>
6+
<meta
7+
name="viewport"
8+
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
9+
/>
10+
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
11+
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
12+
<script src="../../../../../scripts/testing/scripts.js"></script>
13+
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
14+
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
15+
</head>
16+
17+
<body onload="listenForEvent()">
18+
<ion-app>
19+
<ion-header>
20+
<ion-toolbar>
21+
<ion-title>Segment View - Basic</ion-title>
22+
</ion-toolbar>
23+
24+
<ion-toolbar>
25+
<ion-segment view="myView" value="Paid">
26+
<ion-segment-button value="Paid">
27+
<ion-label>Paid</ion-label>
28+
</ion-segment-button>
29+
<ion-segment-button value="Free">
30+
<ion-label>Free</ion-label>
31+
</ion-segment-button>
32+
<ion-segment-button value="Top">
33+
<ion-label>Top</ion-label>
34+
</ion-segment-button>
35+
</ion-segment>
36+
</ion-toolbar>
37+
</ion-header>
38+
39+
<ion-content>
40+
<ion-segment-view id="myView">
41+
<ion-segment-content>
42+
Paid
43+
</ion-segment-content>
44+
<ion-segment-content>
45+
Free
46+
</ion-segment-content>
47+
<ion-segment-content>
48+
Top
49+
</ion-segment-content>
50+
</ion-segment-view>
51+
52+
</ion-content>
53+
</ion-app>
54+
</body>
55+
</html>

packages/angular/src/directives/proxies-list.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export const DIRECTIVES = [
6969
d.IonSearchbar,
7070
d.IonSegment,
7171
d.IonSegmentButton,
72+
d.IonSegmentContent,
73+
d.IonSegmentView,
7274
d.IonSelect,
7375
d.IonSelectOption,
7476
d.IonSkeletonText,

packages/angular/src/directives/proxies.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,48 @@ export class IonSegmentButton {
20052005
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
20062006

20072007

2008+
@ProxyCmp({
2009+
})
2010+
@Component({
2011+
selector: 'ion-segment-content',
2012+
changeDetection: ChangeDetectionStrategy.OnPush,
2013+
template: '<ng-content></ng-content>',
2014+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2015+
inputs: [],
2016+
})
2017+
export class IonSegmentContent {
2018+
protected el: HTMLElement;
2019+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2020+
c.detach();
2021+
this.el = r.nativeElement;
2022+
}
2023+
}
2024+
2025+
2026+
export declare interface IonSegmentContent extends Components.IonSegmentContent {}
2027+
2028+
2029+
@ProxyCmp({
2030+
})
2031+
@Component({
2032+
selector: 'ion-segment-view',
2033+
changeDetection: ChangeDetectionStrategy.OnPush,
2034+
template: '<ng-content></ng-content>',
2035+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2036+
inputs: [],
2037+
})
2038+
export class IonSegmentView {
2039+
protected el: HTMLElement;
2040+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
2041+
c.detach();
2042+
this.el = r.nativeElement;
2043+
}
2044+
}
2045+
2046+
2047+
export declare interface IonSegmentView extends Components.IonSegmentView {}
2048+
2049+
20082050
@ProxyCmp({
20092051
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
20102052
methods: ['open']

packages/angular/standalone/src/directives/proxies.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ import { defineCustomElement as defineIonReorderGroup } from '@ionic/core/compon
6565
import { defineCustomElement as defineIonRippleEffect } from '@ionic/core/components/ion-ripple-effect.js';
6666
import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-row.js';
6767
import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js';
68+
import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js';
69+
import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js';
6870
import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
6971
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
7072
import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js';
@@ -1838,6 +1840,52 @@ export class IonSegmentButton {
18381840
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
18391841

18401842

1843+
@ProxyCmp({
1844+
defineCustomElementFn: defineIonSegmentContent
1845+
})
1846+
@Component({
1847+
selector: 'ion-segment-content',
1848+
changeDetection: ChangeDetectionStrategy.OnPush,
1849+
template: '<ng-content></ng-content>',
1850+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
1851+
inputs: [],
1852+
standalone: true
1853+
})
1854+
export class IonSegmentContent {
1855+
protected el: HTMLElement;
1856+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
1857+
c.detach();
1858+
this.el = r.nativeElement;
1859+
}
1860+
}
1861+
1862+
1863+
export declare interface IonSegmentContent extends Components.IonSegmentContent {}
1864+
1865+
1866+
@ProxyCmp({
1867+
defineCustomElementFn: defineIonSegmentView
1868+
})
1869+
@Component({
1870+
selector: 'ion-segment-view',
1871+
changeDetection: ChangeDetectionStrategy.OnPush,
1872+
template: '<ng-content></ng-content>',
1873+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
1874+
inputs: [],
1875+
standalone: true
1876+
})
1877+
export class IonSegmentView {
1878+
protected el: HTMLElement;
1879+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
1880+
c.detach();
1881+
this.el = r.nativeElement;
1882+
}
1883+
}
1884+
1885+
1886+
export declare interface IonSegmentView extends Components.IonSegmentView {}
1887+
1888+
18411889
@ProxyCmp({
18421890
defineCustomElementFn: defineIonSelectOption,
18431891
inputs: ['disabled', 'value']

0 commit comments

Comments
 (0)