Skip to content

Commit

Permalink
feat(segment-view): adds support for new ion-segment-view component (
Browse files Browse the repository at this point in the history
…#29969)

Issue number: resolves internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Segments can only be changed by clicking a segment button, or dragging
the indicator

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

The segment/segment buttons can now be linked to segment content within
a segment view component. This content is scrollable/swipeable. Changing
the content will update the segment/indicator and vice-versa.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

**Limitations:**
- Segment buttons **cannot** be disabled when connected ton
`ion-segment-content` instances
- The `ion-segment` **cannot** be without a value when linked with an
`ion-segment-view`. If no value is provided, the value will default to
the value of the first `ion-segment-content`


[Preview](https://ionic-framework-jlt8by2io-ionic1.vercel.app/src/components/segment-view/test/basic)
[Preview (disabled
state)](https://ionic-framework-jlt8by2io-ionic1.vercel.app/src/components/segment-view/test/disabled)

---------

Co-authored-by: Brandy Carney <brandyscarney@gmail.com>
  • Loading branch information
tanner-reits and brandyscarney committed Nov 4, 2024
1 parent 3628ea8 commit 89508fb
Show file tree
Hide file tree
Showing 26 changed files with 1,096 additions and 23 deletions.
7 changes: 7 additions & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,7 @@ ion-segment,css-prop,--background,ios
ion-segment,css-prop,--background,md

ion-segment-button,shadow
ion-segment-button,prop,contentId,string | undefined,undefined,false,true
ion-segment-button,prop,disabled,boolean,false,false,false
ion-segment-button,prop,layout,"icon-bottom" | "icon-end" | "icon-hide" | "icon-start" | "icon-top" | "label-hide" | undefined,'icon-top',false,false
ion-segment-button,prop,mode,"ios" | "md",undefined,false,false
Expand Down Expand Up @@ -1607,6 +1608,12 @@ 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-segment-view,prop,disabled,boolean,false,false,false
ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true

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
Expand Down
68 changes: 68 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { NavigationHookCallback } from "./components/route/route-interface";
import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
import { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
import { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
import { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface";
import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
import { SelectModalOption } from "./components/select-modal/select-modal-interface";
import { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
Expand Down Expand Up @@ -70,6 +71,7 @@ export { NavigationHookCallback } from "./components/route/route-interface";
export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
export { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
export { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
export { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface";
export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
export { SelectModalOption } from "./components/select-modal/select-modal-interface";
export { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
Expand Down Expand Up @@ -2696,6 +2698,10 @@ export namespace Components {
"value"?: SegmentValue;
}
interface IonSegmentButton {
/**
* The `id` of the segment content.
*/
"contentId"?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
Expand All @@ -2718,6 +2724,19 @@ export namespace Components {
*/
"value": SegmentValue;
}
interface IonSegmentContent {
}
interface IonSegmentView {
/**
* If `true`, the segment view cannot be interacted with.
*/
"disabled": boolean;
/**
* @param id : The id of the segment content to display.
* @param smoothScroll : Whether to animate the scroll transition.
*/
"setContent": (id: string, smoothScroll?: boolean) => Promise<void>;
}
interface IonSelect {
/**
* The text to display on the cancel button.
Expand Down Expand Up @@ -3424,6 +3443,10 @@ export interface IonSegmentCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSegmentElement;
}
export interface IonSegmentViewCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSegmentViewElement;
}
export interface IonSelectCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSelectElement;
Expand Down Expand Up @@ -4420,6 +4443,29 @@ declare global {
prototype: HTMLIonSegmentButtonElement;
new (): HTMLIonSegmentButtonElement;
};
interface HTMLIonSegmentContentElement extends Components.IonSegmentContent, HTMLStencilElement {
}
var HTMLIonSegmentContentElement: {
prototype: HTMLIonSegmentContentElement;
new (): HTMLIonSegmentContentElement;
};
interface HTMLIonSegmentViewElementEventMap {
"ionSegmentViewScroll": SegmentViewScrollEvent;
}
interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement {
addEventListener<K extends keyof HTMLIonSegmentViewElementEventMap>(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent<HTMLIonSegmentViewElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLIonSegmentViewElementEventMap>(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent<HTMLIonSegmentViewElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLIonSegmentViewElement: {
prototype: HTMLIonSegmentViewElement;
new (): HTMLIonSegmentViewElement;
};
interface HTMLIonSelectElementEventMap {
"ionChange": SelectChangeEventDetail;
"ionCancel": void;
Expand Down Expand Up @@ -4735,6 +4781,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-modal": HTMLIonSelectModalElement;
"ion-select-option": HTMLIonSelectOptionElement;
Expand Down Expand Up @@ -7465,6 +7513,10 @@ declare namespace LocalJSX {
"value"?: SegmentValue;
}
interface IonSegmentButton {
/**
* The `id` of the segment content.
*/
"contentId"?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
Expand All @@ -7486,6 +7538,18 @@ declare namespace LocalJSX {
*/
"value"?: SegmentValue;
}
interface IonSegmentContent {
}
interface IonSegmentView {
/**
* If `true`, the segment view cannot be interacted with.
*/
"disabled"?: boolean;
/**
* Emitted when the segment view is scrolled.
*/
"onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<SegmentViewScrollEvent>) => void;
}
interface IonSelect {
/**
* The text to display on the cancel button.
Expand Down Expand Up @@ -8182,6 +8246,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-modal": IonSelectModal;
"ion-select-option": IonSelectOption;
Expand Down Expand Up @@ -8282,6 +8348,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-modal": LocalJSX.IonSelectModal & JSXBase.HTMLAttributes<HTMLIonSelectModalElement>;
"ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;
Expand Down
37 changes: 30 additions & 7 deletions core/src/components/segment-button/segment-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {

@State() checked = false;

/**
* The `id` of the segment content.
*/
@Prop({ reflect: true }) contentId?: string;

/**
* If `true`, the user cannot interact with the segment button.
*/
Expand Down Expand Up @@ -67,6 +72,30 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
addEventListener(segmentEl, 'ionSelect', this.updateState);
addEventListener(segmentEl, 'ionStyle', this.updateStyle);
}

// Return if there is no contentId defined
if (!this.contentId) return;

// Attempt to find the Segment Content by its contentId
const segmentContent = document.getElementById(this.contentId) as HTMLIonSegmentContentElement | null;

// If no associated Segment Content exists, log an error and return
if (!segmentContent) {
console.error(`Segment Button: Unable to find Segment Content with id="${this.contentId}".`);
return;
}

// Ensure the found element is a valid ION-SEGMENT-CONTENT
if (segmentContent.tagName !== 'ION-SEGMENT-CONTENT') {
console.error(`Segment Button: Element with id="${this.contentId}" is not an <ion-segment-content> element.`);
return;
}

// Prevent buttons from being disabled when associated with segment content
if (this.disabled) {
console.warn(`Segment Button: Segment buttons cannot be disabled when associated with an <ion-segment-content>.`);
this.disabled = false;
}
}

disconnectedCallback() {
Expand Down Expand Up @@ -161,13 +190,7 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
<div
part="indicator"
class={{
'segment-button-indicator': true,
'segment-button-indicator-animated': true,
}}
>
<div part="indicator" class="segment-button-indicator segment-button-indicator-animated">
<div part="indicator-background" class="segment-button-indicator-background"></div>
</div>
</Host>
Expand Down
11 changes: 11 additions & 0 deletions core/src/components/segment-content/segment-content.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Segment Content
// --------------------------------------------------

:host {
scroll-snap-align: center;
scroll-snap-stop: always;

flex-shrink: 0;

width: 100%;
}
17 changes: 17 additions & 0 deletions core/src/components/segment-content/segment-content.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
}
4 changes: 4 additions & 0 deletions core/src/components/segment-view/segment-view-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface SegmentViewScrollEvent {
scrollRatio: number;
isManualScroll: boolean;
}
9 changes: 9 additions & 0 deletions core/src/components/segment-view/segment-view.ios.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import "./segment-view";
@import "../segment-button/segment-button.ios.vars";

// iOS Segment View
// --------------------------------------------------

:host(.segment-view-disabled) {
opacity: $segment-button-ios-opacity-disabled;
}
9 changes: 9 additions & 0 deletions core/src/components/segment-view/segment-view.md.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import "./segment-view";
@import "../segment-button/segment-button.md.vars";

// Material Design Segment View
// --------------------------------------------------

:host(.segment-view-disabled) {
opacity: $segment-button-md-opacity-disabled;
}
31 changes: 31 additions & 0 deletions core/src/components/segment-view/segment-view.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Segment View
// --------------------------------------------------

:host {
display: flex;

height: 100%;

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;
}

:host(.segment-view-disabled) {
touch-action: none;
overflow-x: hidden;
}

:host(.segment-view-scroll-disabled) {
pointer-events: none;
}
Loading

0 comments on commit 89508fb

Please sign in to comment.