mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 11:17:19 +08:00
feat(segment): implement iOS 13 segment with animation (#19036)
Changes Closes #18663 * Converts Segment to shadow * Enables gesture to swipe between segment buttons * Adds indicator transition to slide the indicator between buttons * Updates global theme variables * Removes activated state, now handled by the gesture * Updates iOS to latest iOS 13 UI * Ensures customization is working for the buttons and indicator * Updates the e2e tests
This commit is contained in:

committed by
Liam DeBeasi

parent
8e11f79fcc
commit
dc66ce48e1
@ -443,7 +443,7 @@ export const SegmentExample: React.FC = () => (
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the segment. | `boolean` | `false` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `scrollable` | `scrollable` | If `true`, the segment buttons will overflow and the user can swipe to see them. | `boolean` | `false` |
|
||||
| `scrollable` | `scrollable` | If `true`, the segment buttons will overflow and the user can swipe to see them. In addition, this will disable the gesture to drag the indicator between the buttons in order to swipe to see hidden buttons. | `boolean` | `false` |
|
||||
| `value` | `value` | the value of the segment. | `null \| string \| undefined` | `undefined` |
|
||||
|
||||
|
||||
@ -454,6 +454,13 @@ export const SegmentExample: React.FC = () => (
|
||||
| `ionChange` | Emitted when the value property has changed. | `CustomEvent<SegmentChangeEventDetail>` |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
||||
| Name | Description |
|
||||
| -------------- | -------------------------------- |
|
||||
| `--background` | Background of the segment button |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
*Built with [StencilJS](https://stenciljs.com/)*
|
||||
|
@ -5,92 +5,78 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
--background: #{$segment-button-ios-background-color};
|
||||
--background-hover: #{$segment-button-ios-background-color-hover};
|
||||
--background-activated: #{$segment-button-ios-background-color-activated};
|
||||
--background-checked: #{$segment-button-ios-background-color-checked};
|
||||
--color: #{$segment-button-ios-text-color};
|
||||
--color-checked: #{$segment-button-ios-text-color-checked};
|
||||
--color-disabled: #{ion-color(primary, base, $segment-button-ios-opacity-disabled)};
|
||||
--color-checked-disabled: #{ion-color(primary, contrast, $segment-button-ios-opacity-disabled)};
|
||||
--border-color: #{$segment-button-ios-border-color};
|
||||
--indicator-color: transparent;
|
||||
--background: #{$segment-ios-background-color};
|
||||
|
||||
@include border-radius($segment-ios-border-radius);
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
:host(.segment-disabled) {
|
||||
opacity: $segment-ios-opacity-disabled;
|
||||
opacity: $segment-button-ios-opacity-disabled;
|
||||
}
|
||||
|
||||
|
||||
// Segment: Color
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.ion-color)::slotted(ion-segment-button) {
|
||||
--border-color: #{current-color(base)};
|
||||
|
||||
background: transparent;
|
||||
color: #{current-color(base)};
|
||||
:host(.ion-color) {
|
||||
background: #{current-color(base, 0.065)};
|
||||
}
|
||||
|
||||
:host(.ion-color)::slotted(.activated) {
|
||||
background: #{current-color(base, .16)};
|
||||
color: #{current-color(base)};
|
||||
:host(.ion-color) ::slotted(.segment-button-checked) {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
:host(.ion-color)::slotted(.segment-button-checked.activated),
|
||||
:host(.ion-color)::slotted(.segment-button-checked) {
|
||||
background: #{current-color(base)};
|
||||
color: #{current-color(contrast)};
|
||||
}
|
||||
|
||||
:host(.ion-color)::slotted(.segment-button-disabled) {
|
||||
color: #{current-color(base, $segment-ios-opacity-disabled)};
|
||||
}
|
||||
// Segment: Activated
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.ion-color)::slotted(.segment-button-checked.segment-button-disabled) {
|
||||
color: #{current-color(contrast, $segment-ios-opacity-disabled)};
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
:host(.ion-color)::slotted(ion-segment-button:hover:not(.segment-button-checked)) {
|
||||
background: #{current-color(base, .1)};
|
||||
}
|
||||
:host(.segment-activated) ::slotted(ion-segment-button) {
|
||||
--indicator-transform: scale(0.95);
|
||||
}
|
||||
|
||||
|
||||
// Segment: Default Toolbar
|
||||
// --------------------------------------------------
|
||||
|
||||
:host-context(ion-toolbar)::slotted(ion-segment-button) {
|
||||
max-width: $segment-button-ios-toolbar-button-max-width;
|
||||
:host(.in-toolbar) {
|
||||
@include margin(0, auto);
|
||||
|
||||
font-size: $segment-button-ios-toolbar-font-size;
|
||||
|
||||
line-height: $segment-button-ios-toolbar-line-height;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
:host-context(ion-toolbar:not(.ion-color)):not(.ion-color)::slotted(ion-segment-button) {
|
||||
border-color: #{var(--ion-toolbar-color-checked, var(--border-color))};
|
||||
|
||||
color: #{var(--ion-toolbar-color-unchecked, var(--color))};
|
||||
// Default Segment, In a Toolbar
|
||||
:host(.in-toolbar:not(.ion-color)) {
|
||||
background: var(--ion-toolbar-segment-background, $segment-ios-background-color);
|
||||
color: var(--ion-toolbar-segment-color, var(--color));
|
||||
}
|
||||
|
||||
:host-context(ion-toolbar:not(.ion-color)):not(.ion-color)::slotted(.segment-button-checked) {
|
||||
background: #{var(--ion-toolbar-color-checked, var(--background-checked))};
|
||||
|
||||
color: #{var(--ion-toolbar-background, var(--color-checked))};
|
||||
// Default Segment, In a Toolbar, Checked
|
||||
:host(.in-toolbar:not(.ion-color)) ::slotted(.segment-button-checked) {
|
||||
color: var(--ion-toolbar-segment-color-checked, var(--color-checked));
|
||||
}
|
||||
|
||||
|
||||
// Segment: Color Toolbar
|
||||
// --------------------------------------------------
|
||||
|
||||
:host-context(ion-toolbar.ion-color):not(.ion-color)::slotted(ion-segment-button) {
|
||||
--color: #{current-color(contrast)};
|
||||
--color-disabled: #{current-color(contrast, $segment-button-ios-opacity-disabled)};
|
||||
--color-checked: #{current-color(base)};
|
||||
--color-checked-disabled: #{current-color(contrast, $segment-button-ios-opacity-disabled)};
|
||||
--background-hover: #{current-color(contrast, $segment-button-ios-opacity-hover)};
|
||||
--background-activated: #{current-color(contrast, $segment-button-ios-opacity-activated)};
|
||||
--background-checked: #{current-color(contrast)};
|
||||
--border-color: #{current-color(contrast)};
|
||||
// Toolbar with Color, Default Segment
|
||||
:host(.in-toolbar-color:not(.ion-color)) {
|
||||
background: #{current-color(contrast, 0.11)};
|
||||
color: #{current-color(contrast)};
|
||||
}
|
||||
|
||||
// Toolbar with Color, Default Segment, Checked
|
||||
:host(.in-toolbar-color:not(.ion-color)) ::slotted(.segment-button-checked) {
|
||||
color: #{current-color(base)};
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
// Toolbar with Color, Default Segment, Checked / Hover
|
||||
:host(.in-toolbar-color:not(.ion-color)) ::slotted(.segment-button-checked:hover) {
|
||||
color: #{current-color(base)};
|
||||
}
|
||||
}
|
@ -4,5 +4,11 @@
|
||||
// iOS Segment
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Opacity of the disabled segment
|
||||
$segment-ios-opacity-disabled: .3 !default;
|
||||
/// @prop - Alpha of the segment for use in the backgrounds
|
||||
$segment-ios-background-alpha: 0.065 !default;
|
||||
|
||||
/// @prop - Background color of the segment
|
||||
$segment-ios-background-color: rgba($text-color-rgb, $segment-ios-background-alpha) !default;
|
||||
|
||||
/// @prop - Border radius of the segment
|
||||
$segment-ios-border-radius: 8px !default;
|
||||
|
@ -5,14 +5,7 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
--background: #{$segment-button-md-background};
|
||||
--background-checked: #{$segment-button-md-background-checked};
|
||||
--background-hover: #{$segment-button-md-background-hover};
|
||||
--background-activated: #{$segment-button-md-background-activated};
|
||||
--color: #{$segment-button-md-text-color};
|
||||
--color-checked: #{$segment-button-md-text-color-checked};
|
||||
--color-checked-disabled: var(--color-checked);
|
||||
--indicator-color: transparent;
|
||||
--background: transparent;
|
||||
}
|
||||
|
||||
:host(.segment-disabled) {
|
||||
@ -22,25 +15,20 @@
|
||||
// Segment: Color
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.ion-color)::slotted(ion-segment-button) {
|
||||
--background-activated: #{current-color(base, .16)};
|
||||
:host(.ion-color) ::slotted(ion-segment-button) {
|
||||
--ripple-color: #{current-color(base)};
|
||||
--indicator-color: #{current-color(base)};
|
||||
|
||||
background: transparent;
|
||||
color: $segment-button-md-text-color;
|
||||
}
|
||||
|
||||
:host(.ion-color)::slotted(.segment-button-checked) {
|
||||
--indicator-color-checked: #{current-color(base)};
|
||||
color: #{current-color(base)};
|
||||
}
|
||||
|
||||
:host(.ion-color)::slotted(.segment-button-checked.activated) {
|
||||
:host(.ion-color) ::slotted(.segment-button-checked) {
|
||||
color: #{current-color(base)};
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
:host(.ion-color)::slotted(ion-segment-button:hover) {
|
||||
:host(.ion-color) ::slotted(ion-segment-button:hover) {
|
||||
background: #{current-color(base, .04)};
|
||||
}
|
||||
}
|
||||
@ -48,24 +36,49 @@
|
||||
// Segment: Default Toolbar
|
||||
// --------------------------------------------------
|
||||
|
||||
:host-context(ion-toolbar:not(.ion-color)):not(.ion-color)::slotted(ion-segment-button) {
|
||||
color: #{var(--ion-toolbar-color-unchecked, var(--color))};
|
||||
// Default Segment, In a Toolbar
|
||||
:host(.in-toolbar:not(.ion-color)) ::slotted(ion-segment-button) {
|
||||
--indicator-color: #{var(--ion-toolbar-segment-color-checked, var(--color-checked))};
|
||||
|
||||
background: #{var(--ion-toolbar-segment-background, var(--background))};
|
||||
color: #{var(--ion-toolbar-segment-color, var(--color))};
|
||||
}
|
||||
|
||||
:host-context(ion-toolbar:not(.ion-color)):not(.ion-color)::slotted(.segment-button-checked) {
|
||||
--indicator-color-checked: #{var(--ion-toolbar-color-checked, var(--color-checked))};
|
||||
|
||||
color: #{var(--ion-toolbar-color-checked, var(--color-checked))};
|
||||
// Default Segment, In a Toolbar, Checked
|
||||
:host(.in-toolbar:not(.ion-color)) ::slotted(.segment-button-checked) {
|
||||
background: #{var(--ion-toolbar-segment-background-checked, var(--background-checked))};
|
||||
color: #{var(--ion-toolbar-segment-color-checked, var(--color-checked))};
|
||||
}
|
||||
|
||||
|
||||
// Segment: Toolbar Color
|
||||
// --------------------------------------------------
|
||||
|
||||
:host-context(ion-toolbar.ion-color):not(.ion-color)::slotted(ion-segment-button) {
|
||||
--background-hover: #{current-color(contrast, .04)};
|
||||
--background-activated: #{current-color(base)};
|
||||
--color: #{current-color(contrast, .6)};
|
||||
--color-checked: #{current-color(contrast)};
|
||||
--indicator-color-checked: #{current-color(contrast)};
|
||||
// Default Segment, In a Toolbar with Color
|
||||
:host(.in-toolbar-color:not(.ion-color)) ::slotted(ion-segment-button) {
|
||||
color: #{current-color(contrast, .6)};
|
||||
}
|
||||
|
||||
// Default Segment, In a Toolbar with Color, Checked
|
||||
:host(.in-toolbar-color:not(.ion-color)) ::slotted(.segment-button-checked) {
|
||||
color: #{current-color(contrast)};
|
||||
}
|
||||
|
||||
|
||||
// Segment: Toolbar Hover
|
||||
// --------------------------------------------------
|
||||
|
||||
@media (any-hover: hover) {
|
||||
// Default Segment, In a Toolbar with Color, Hover
|
||||
:host(.in-toolbar-color:not(.ion-color)) ::slotted(ion-segment-button:hover) {
|
||||
background: #{ion-color(primary, contrast, .04)};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Segment: Scrollable
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.segment-scrollable) ::slotted(ion-segment-button) {
|
||||
min-width: $segment-button-md-min-width;
|
||||
}
|
@ -4,22 +4,29 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
--indicator-color-checked: initial;
|
||||
/**
|
||||
* @prop --background: Background of the segment button
|
||||
*/
|
||||
--ripple-color: currentColor;
|
||||
--color-activated: initial;
|
||||
|
||||
@include font-smoothing();
|
||||
|
||||
display: flex;
|
||||
|
||||
position: relative;
|
||||
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background: var(--background);
|
||||
|
||||
font-family: $font-family-base;
|
||||
|
||||
text-align: center;
|
||||
|
||||
contain: paint;
|
||||
}
|
||||
|
||||
|
||||
@ -46,3 +53,19 @@
|
||||
:host(.segment-scrollable::-webkit-scrollbar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Segment Button: Hover
|
||||
// --------------------------------------------------
|
||||
|
||||
@media (any-hover: hover) {
|
||||
// Default Segment, In a Default Toolbar, Hover
|
||||
:host(.in-toolbar:not(.ion-color)) ::slotted(ion-segment-button:hover) {
|
||||
background: var(--ion-toolbar-segment-background-hover, var(--background-hover));
|
||||
color: var(--ion-toolbar-segment-color-hover, var(--color-hover, var(--ion-toolbar-segment-color, var(--color))));
|
||||
}
|
||||
|
||||
// Default Segment, In a Default Toolbar, Checked / Hover
|
||||
:host(.in-toolbar:not(.ion-color)) ::slotted(.segment-button-checked:hover) {
|
||||
color: var(--ion-toolbar-segment-color-hover, var(--color-hover, var(--ion-toolbar-segment-color-checked, var(--color-checked))));
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Listen, Prop, Watch, h } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Listen, Prop, State, Watch, h, writeTask } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, SegmentChangeEventDetail, StyleEventDetail } from '../../interface';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
import { Gesture, GestureDetail } from '../../utils/gesture';
|
||||
import { pointerCoord } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
@ -13,13 +15,16 @@ import { createColorClasses } from '../../utils/theme';
|
||||
ios: 'segment.ios.scss',
|
||||
md: 'segment.md.scss'
|
||||
},
|
||||
scoped: true
|
||||
shadow: true
|
||||
})
|
||||
export class Segment implements ComponentInterface {
|
||||
|
||||
private gesture?: Gesture;
|
||||
private didInit = false;
|
||||
private checked?: HTMLIonSegmentButtonElement;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
@Element() el!: HTMLIonSegmentElement;
|
||||
|
||||
@State() activated = false;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
@ -35,6 +40,8 @@ export class Segment implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* If `true`, the segment buttons will overflow and the user can swipe to see them.
|
||||
* In addition, this will disable the gesture to drag the indicator between the buttons
|
||||
* in order to swipe to see hidden buttons.
|
||||
*/
|
||||
@Prop() scrollable = false;
|
||||
|
||||
@ -43,14 +50,6 @@ export class Segment implements ComponentInterface {
|
||||
*/
|
||||
@Prop({ mutable: true }) value?: string | null;
|
||||
|
||||
@Watch('value')
|
||||
protected valueChanged(value: string | undefined) {
|
||||
if (this.didInit) {
|
||||
this.updateButtons();
|
||||
this.ionChange.emit({ value });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted when the value property has changed.
|
||||
*/
|
||||
@ -62,10 +61,32 @@ export class Segment implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
||||
|
||||
@Watch('value')
|
||||
protected valueChanged(value: string | undefined) {
|
||||
if (this.didInit) {
|
||||
this.updateButtons();
|
||||
this.ionChange.emit({ value });
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('disabled')
|
||||
disabledChanged() {
|
||||
if (this.gesture && !this.scrollable) {
|
||||
this.gesture.enable(!this.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('ionSelect')
|
||||
segmentClick(ev: CustomEvent) {
|
||||
const selectedButton = ev.target as HTMLIonSegmentButtonElement;
|
||||
this.value = selectedButton.value;
|
||||
const current = ev.target as HTMLIonSegmentButtonElement;
|
||||
const previous = this.checked;
|
||||
this.value = current.value;
|
||||
|
||||
if (previous && this.scrollable) {
|
||||
this.checkButton(previous, current);
|
||||
}
|
||||
|
||||
this.checked = current;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@ -78,11 +99,219 @@ export class Segment implements ComponentInterface {
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
componentWillLoad() {
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
async componentDidLoad() {
|
||||
this.updateButtons();
|
||||
this.setCheckedClasses();
|
||||
|
||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||
el: this.el,
|
||||
gestureName: 'segment',
|
||||
gesturePriority: 100,
|
||||
threshold: 0,
|
||||
passive: false,
|
||||
onStart: ev => this.onStart(ev),
|
||||
onMove: ev => this.onMove(ev),
|
||||
onEnd: ev => this.onEnd(ev),
|
||||
});
|
||||
this.gesture.enable(!this.scrollable);
|
||||
this.disabledChanged();
|
||||
|
||||
this.didInit = true;
|
||||
}
|
||||
|
||||
onStart(detail: GestureDetail) {
|
||||
this.activate(detail);
|
||||
}
|
||||
|
||||
onMove(detail: GestureDetail) {
|
||||
this.setNextIndex(detail);
|
||||
}
|
||||
|
||||
onEnd(detail: GestureDetail) {
|
||||
this.activated = false;
|
||||
|
||||
this.setNextIndex(detail, true);
|
||||
|
||||
detail.event.preventDefault();
|
||||
detail.event.stopImmediatePropagation();
|
||||
|
||||
this.addRipple(detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* The gesture blocks the segment button ripple. This
|
||||
* function adds the ripple based on the checked segment
|
||||
* and where the cursor ended.
|
||||
*/
|
||||
private addRipple(detail: GestureDetail) {
|
||||
const buttons = this.getButtons();
|
||||
const checked = buttons.find(button => button.checked === true);
|
||||
|
||||
const ripple = checked!.shadowRoot!.querySelector('ion-ripple-effect');
|
||||
|
||||
if (!ripple) { return; }
|
||||
|
||||
const { x, y } = pointerCoord(detail.event);
|
||||
|
||||
ripple.addRipple(x, y).then(remove => remove());
|
||||
}
|
||||
|
||||
private activate(detail: GestureDetail) {
|
||||
const clicked = detail.event.target as HTMLIonSegmentButtonElement;
|
||||
const buttons = this.getButtons();
|
||||
const checked = buttons.find(button => button.checked === true);
|
||||
|
||||
// Make sure we are only checking for activation on a segment button
|
||||
// since disabled buttons will get the click on the segment
|
||||
if (clicked.tagName !== 'ION-SEGMENT-BUTTON') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are no checked buttons, set the current button to checked
|
||||
if (!checked) {
|
||||
clicked.checked = true;
|
||||
}
|
||||
|
||||
// If the gesture began on the clicked button with the indicator
|
||||
// then we should activate the indicator
|
||||
if (clicked.checked) {
|
||||
this.activated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private getIndicator(button: HTMLIonSegmentButtonElement): HTMLDivElement | null {
|
||||
return button.shadowRoot && button.shadowRoot.querySelector('.segment-button-indicator');
|
||||
}
|
||||
|
||||
private checkButton(previous: HTMLIonSegmentButtonElement, current: HTMLIonSegmentButtonElement) {
|
||||
const previousIndicator = this.getIndicator(previous);
|
||||
const currentIndicator = this.getIndicator(current);
|
||||
|
||||
if (previousIndicator === null || currentIndicator === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousClientRect = previousIndicator.getBoundingClientRect();
|
||||
const currentClientRect = currentIndicator.getBoundingClientRect();
|
||||
|
||||
const widthDelta = previousClientRect.width / currentClientRect.width;
|
||||
const xPosition = previousClientRect.left - currentClientRect.left;
|
||||
|
||||
// Scale the indicator width to match the previous indicator width
|
||||
// and translate it on top of the previous indicator
|
||||
const transform = `translate3d(${xPosition}px, 0, 0) scaleX(${widthDelta})`;
|
||||
|
||||
writeTask(() => {
|
||||
// Remove the transition before positioning on top of the previous indicator
|
||||
currentIndicator.classList.remove('segment-button-indicator-animated');
|
||||
currentIndicator.style.setProperty('transform', transform);
|
||||
|
||||
// Force a repaint to ensure the transform happens
|
||||
currentIndicator.getBoundingClientRect();
|
||||
|
||||
// Add the transition to move the indicator into place
|
||||
currentIndicator.classList.add('segment-button-indicator-animated');
|
||||
|
||||
// Remove the transform to slide the indicator back to the button clicked
|
||||
currentIndicator.style.setProperty('transform', '');
|
||||
});
|
||||
|
||||
current.checked = true;
|
||||
this.setCheckedClasses();
|
||||
}
|
||||
|
||||
private setCheckedClasses() {
|
||||
const buttons = this.getButtons();
|
||||
const index = buttons.findIndex(button => button.checked === true);
|
||||
const next = index + 1;
|
||||
|
||||
// Keep track of the currently checked button
|
||||
this.checked = buttons.find(button => button.checked === true);
|
||||
|
||||
for (const button of buttons) {
|
||||
button.classList.remove('segment-button-after-checked');
|
||||
}
|
||||
if (next < buttons.length) {
|
||||
buttons[next].classList.add('segment-button-after-checked');
|
||||
}
|
||||
}
|
||||
|
||||
private setNextIndex(detail: GestureDetail, isEnd = false) {
|
||||
const isRTL = document.dir === 'rtl';
|
||||
const activated = this.activated;
|
||||
const buttons = this.getButtons();
|
||||
const index = buttons.findIndex(button => button.checked === true);
|
||||
const previous = buttons[index];
|
||||
let current;
|
||||
let nextIndex;
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the element that the touch event started on in case
|
||||
// it was the checked button, then we will move the indicator
|
||||
const rect = previous.getBoundingClientRect() as DOMRect;
|
||||
const left = rect.left;
|
||||
const width = rect.width;
|
||||
|
||||
// Get the element that the gesture is on top of based on the currentX of the
|
||||
// gesture event and the Y coordinate of the starting element, since the gesture
|
||||
// can move up and down off of the segment
|
||||
const currentX = detail.currentX;
|
||||
const previousY = rect.y;
|
||||
const nextEl = document.elementFromPoint(currentX, previousY) as HTMLIonSegmentButtonElement;
|
||||
|
||||
const decreaseIndex = isRTL ? currentX > (left + width) : currentX < left;
|
||||
const increaseIndex = isRTL ? currentX < left : currentX > (left + width);
|
||||
|
||||
// If the indicator is currently activated then we have started the gesture
|
||||
// on top of the checked button so we need to slide the indicator
|
||||
// by checking the button next to it as we move
|
||||
if (activated && !isEnd) {
|
||||
// Decrease index, move left in LTR & right in RTL
|
||||
if (decreaseIndex) {
|
||||
const newIndex = index - 1;
|
||||
|
||||
if (newIndex >= 0) {
|
||||
nextIndex = newIndex;
|
||||
}
|
||||
// Increase index, moves right in LTR & left in RTL
|
||||
} else if (increaseIndex) {
|
||||
if (activated && !isEnd) {
|
||||
|
||||
const newIndex = index + 1;
|
||||
|
||||
if (newIndex < buttons.length) {
|
||||
nextIndex = newIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextIndex !== undefined && !buttons[nextIndex].disabled) {
|
||||
current = buttons[nextIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// If the indicator is not activated then we will just set the indicator
|
||||
// to the element where the gesture ended
|
||||
if (!activated && isEnd) {
|
||||
current = nextEl;
|
||||
}
|
||||
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previous !== current) {
|
||||
this.checkButton(previous, current);
|
||||
}
|
||||
}
|
||||
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'segment': true
|
||||
@ -102,15 +331,20 @@ export class Segment implements ComponentInterface {
|
||||
|
||||
render() {
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={{
|
||||
...createColorClasses(this.color),
|
||||
[mode]: true,
|
||||
'in-toolbar': hostContext('ion-toolbar', this.el),
|
||||
'in-toolbar-color': hostContext('ion-toolbar[color]', this.el),
|
||||
'segment-activated': this.activated,
|
||||
'segment-disabled': this.disabled,
|
||||
'segment-scrollable': this.scrollable
|
||||
}}
|
||||
>
|
||||
<slot></slot>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
@ -4,12 +4,14 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Segment - Basic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<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>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload="listenForEvent()">
|
||||
<ion-app>
|
||||
@ -22,7 +24,7 @@
|
||||
<ion-toolbar>
|
||||
<ion-segment class="event-tester" value="Free">
|
||||
<ion-segment-button value="Paid">
|
||||
Paid
|
||||
PaidPaidPaid
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="Free">
|
||||
Free
|
||||
@ -62,23 +64,12 @@
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-segment color="danger">
|
||||
<ion-segment-button value="sunny">
|
||||
Sunny
|
||||
<ion-segment>
|
||||
<ion-segment-button value="all" checked>
|
||||
All
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="rainy" checked>
|
||||
Rainy
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar color="primary">
|
||||
<ion-segment color="light">
|
||||
<ion-segment-button value="sunny">
|
||||
Sunny
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="rainy" checked>
|
||||
Rainy
|
||||
<ion-segment-button value="missed">
|
||||
Missed
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
@ -87,26 +78,38 @@
|
||||
<ion-content>
|
||||
<div class="ion-padding">
|
||||
<ion-segment>
|
||||
<ion-segment-button><ion-label>Seg 1</ion-label></ion-segment-button>
|
||||
<ion-segment-button><ion-label>Seg 2</ion-label></ion-segment-button>
|
||||
<ion-segment-button><ion-label>Seg 3</ion-label></ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 1</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 3</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment disabled>
|
||||
<ion-segment-button><ion-label>Seg 2 1</ion-label></ion-segment-button>
|
||||
<ion-segment-button checked><ion-label>Seg 2 2</ion-label></ion-segment-button>
|
||||
<ion-segment-button><ion-label>Seg 2 3</ion-label></ion-segment-button>
|
||||
<ion-segment class="segment-no-animate">
|
||||
<ion-segment-button>
|
||||
<ion-label>Animate</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button checked>
|
||||
<ion-label>Is</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>False</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="dark" value="Reading List">
|
||||
<ion-segment-button value="Bookmarks">
|
||||
<ion-icon name="book"></ion-icon>
|
||||
<ion-icon name="md-book"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="Reading List">
|
||||
<ion-icon name="glasses"></ion-icon>
|
||||
<ion-icon name="search"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="Shared Links">
|
||||
<ion-icon name="at"></ion-icon>
|
||||
<ion-icon name="md-time"></ion-icon>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
@ -117,13 +120,13 @@
|
||||
<ion-segment-button value="440">
|
||||
<ion-label>440ml</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="500">
|
||||
<ion-segment-button disabled value="500">
|
||||
<ion-label>500ml</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="custom">
|
||||
<ion-icon name="create"></ion-icon>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment name="dynamicPropDisable" disabled color="danger">
|
||||
<ion-segment-button value="Bookmarks">
|
||||
@ -149,7 +152,7 @@
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment name="dynamicAttrElem" value="disabled">
|
||||
<ion-segment name="dynamicAttrElem">
|
||||
<ion-segment-button value="active">
|
||||
<ion-label>Active</ion-label>
|
||||
</ion-segment-button>
|
||||
@ -162,7 +165,7 @@
|
||||
</ion-segment>
|
||||
|
||||
<!-- Dynamic Buttons -->
|
||||
<ion-segment id="dynamicButtons" color="dark"></ion-segment>
|
||||
<ion-segment id="dynamicButtons"></ion-segment>
|
||||
</div>
|
||||
|
||||
<div class="ion-padding-horizontal">
|
||||
@ -204,7 +207,6 @@
|
||||
|
||||
async function listenForEvent() {
|
||||
const ionSegmentElement = document.querySelector('ion-segment.event-tester');
|
||||
await ionSegmentElement.componentOnReady();
|
||||
ionSegmentElement.addEventListener('ionChange', (event) => {
|
||||
console.log('event.target: ', event.target.value);
|
||||
});
|
||||
@ -218,13 +220,15 @@
|
||||
}, 4000);
|
||||
|
||||
function updateSegmentButtons(length) {
|
||||
dynamicButtons.innerHTML = '';
|
||||
const buttonsLength = dynamicButtons.children.length;
|
||||
const begin = buttonsLength === 0 ? 0 : buttonsLength;
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
dynamicButtons.innerHTML += `
|
||||
<ion-segment-button value="segment-${i}">
|
||||
<ion-label>Btn ${i}</ion-label>
|
||||
</ion-segment-button>`;
|
||||
for (var i = begin; i < length; i++) {
|
||||
const button = document.createElement('ion-segment-button');
|
||||
button.value = `segment-${i}`;
|
||||
button.innerHTML = `<ion-label>Btn ${i}</ion-label>`;
|
||||
|
||||
dynamicButtons.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,6 +240,10 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.segment-no-animate ion-segment-button {
|
||||
--indicator-transition: none;
|
||||
--indicator-transform: none;
|
||||
}
|
||||
</style>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
23
core/src/components/segment/test/colors/e2e.ts
Normal file
23
core/src/components/segment/test/colors/e2e.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('segment: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/segment/test/basic?ionic:_testing=true'
|
||||
});
|
||||
|
||||
await page.waitFor(250);
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('segment:rtl: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/segment/test/basic?ionic:_testing=true&rtl=true'
|
||||
});
|
||||
|
||||
await page.waitFor(250);
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
178
core/src/components/segment/test/colors/index.html
Normal file
178
core/src/components/segment/test/colors/index.html
Normal file
@ -0,0 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Segment - Colors</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>
|
||||
<ion-app>
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Segment - Colors</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-segment value="free">
|
||||
<ion-segment-button value="paid">
|
||||
Paid
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="free">
|
||||
Free
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="top">
|
||||
Top
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="primary" value="reading-list">
|
||||
<ion-segment-button value="bookmarks">
|
||||
Bookmarks
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="reading-list">
|
||||
Reading List
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="shared-links">
|
||||
Shared Links
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="secondary" value="active">
|
||||
<ion-segment-button value="active">
|
||||
Active
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="disabled" disabled="true">
|
||||
Disabled
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="inactive" disabled="false">
|
||||
Inactive
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="tertiary">
|
||||
<ion-segment-button value="all" checked>
|
||||
All
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="missed">
|
||||
Missed
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="success">
|
||||
<ion-segment-button checked value="330">
|
||||
<ion-label>330ml</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="440">
|
||||
<ion-label>440ml</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="500">
|
||||
<ion-label>500ml</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="custom">
|
||||
<ion-icon name="create"></ion-icon>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="warning" value="reading-list">
|
||||
<ion-segment-button value="bookmarks">
|
||||
<ion-icon name="book"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="reading-list">
|
||||
<ion-icon name="glasses"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="shared-links">
|
||||
<ion-icon name="at"></ion-icon>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="danger" value="bookmarks">
|
||||
<ion-segment-button value="bookmarks">
|
||||
<ion-label>Bookmarks</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="reading-list">
|
||||
<ion-label>Reading List</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="shared-links">
|
||||
<ion-label>Shared Links</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
|
||||
<ion-segment color="light">
|
||||
<ion-segment-button value="sunny">
|
||||
Sunny
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="rainy" checked>
|
||||
Rainy
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="medium">
|
||||
<ion-segment-button checked>
|
||||
<ion-label>Seg 1</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 3</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment color="dark">
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2 1</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button checked>
|
||||
<ion-label>Seg 2 2</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2 3</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment disabled color="danger">
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2 1</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button checked>
|
||||
<ion-label>Seg 2 2</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2 3</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment disabled color="medium">
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2 1</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button checked>
|
||||
<ion-label>Seg 2 2</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button>
|
||||
<ion-label>Seg 2 3</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-content>
|
||||
|
||||
<style>
|
||||
ion-content ion-segment {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -28,17 +28,31 @@
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-segment value="Free" class="custom-checked">
|
||||
<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-toolbar>
|
||||
<ion-segment class="custom-icon" value="instagram">
|
||||
<ion-segment-button class="segment-facebook" value="facebook">
|
||||
<ion-segment-button class="segment-button-facebook" value="facebook">
|
||||
<ion-label>Facebook</ion-label>
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="segment-instagram" value="instagram">
|
||||
<ion-segment-button class="segment-button-instagram" value="instagram">
|
||||
<ion-label>Instagram</ion-label>
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="segment-slack" value="slack">
|
||||
<ion-segment-button class="segment-button-slack" value="slack">
|
||||
<ion-label>Slack</ion-label>
|
||||
<ion-icon name="logo-slack"></ion-icon>
|
||||
</ion-segment-button>
|
||||
@ -61,11 +75,39 @@
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-segment class="custom-states-color" value="disabled">
|
||||
<ion-segment-button value="inactive">
|
||||
<ion-label>Inactive</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="inactive2">
|
||||
<ion-label>Inactive</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="disabled" disabled>
|
||||
<ion-label>Disabled</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar>
|
||||
<ion-segment class="custom-states-background" value="checked">
|
||||
<ion-segment-button value="checked">
|
||||
<ion-label>Checked</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="inactive">
|
||||
<ion-label>Inactive</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="disabled" disabled>
|
||||
<ion-label>Disabled</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-content class="ion-padding-top">
|
||||
|
||||
<ion-segment class="custom" value="active">
|
||||
<ion-segment class="custom-active" value="active">
|
||||
<ion-segment-button value="active">
|
||||
<ion-label>Active</ion-label>
|
||||
</ion-segment-button>
|
||||
@ -77,16 +119,16 @@
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment class="custom-icon" value="instagram">
|
||||
<ion-segment-button class="segment-facebook" value="facebook">
|
||||
<ion-segment class="custom-icon" value="slack">
|
||||
<ion-segment-button class="segment-button-facebook" value="facebook">
|
||||
<ion-label>Facebook</ion-label>
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="segment-instagram" value="instagram">
|
||||
<ion-segment-button class="segment-button-instagram" value="instagram">
|
||||
<ion-label>Instagram</ion-label>
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="segment-slack" value="slack">
|
||||
<ion-segment-button class="segment-button-slack" value="slack">
|
||||
<ion-label>Slack</ion-label>
|
||||
<ion-icon name="logo-slack"></ion-icon>
|
||||
</ion-segment-button>
|
||||
@ -107,34 +149,30 @@
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<h2>Activated</h2>
|
||||
|
||||
<ion-segment>
|
||||
<ion-segment-button class="activated" value="Paid">
|
||||
<ion-label>Paid</ion-label>
|
||||
<ion-segment class="custom-states-color" value="checked">
|
||||
<ion-segment-button value="checked">
|
||||
<ion-label>Checked</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="activated" value="Free">
|
||||
<ion-label>Free</ion-label>
|
||||
<ion-segment-button value="inactive">
|
||||
<ion-label>Inactive</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="activated" value="Top">
|
||||
<ion-label>Top</ion-label>
|
||||
<ion-segment-button value="disabled" disabled>
|
||||
<ion-label>Disabled</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment class="custom-icon">
|
||||
<ion-segment-button class="activated segment-facebook" value="facebook">
|
||||
<ion-label>Facebook</ion-label>
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
<ion-segment class="custom-states-background" value="checked">
|
||||
<ion-segment-button value="checked">
|
||||
<ion-label>Checked</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="activated segment-instagram" value="instagram">
|
||||
<ion-label>Instagram</ion-label>
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
<ion-segment-button value="inactive">
|
||||
<ion-label>Inactive</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="activated segment-slack" value="slack">
|
||||
<ion-label>Slack</ion-label>
|
||||
<ion-icon name="logo-slack"></ion-icon>
|
||||
<ion-segment-button value="disabled" disabled>
|
||||
<ion-label>Disabled</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
@ -153,107 +191,153 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
border: 1px solid #e6e9ee;
|
||||
background: white;
|
||||
margin: 10px;
|
||||
padding: 4px;
|
||||
line-height: 24px;
|
||||
/*
|
||||
* Custom Checked Segment (Paid, Free, Top)
|
||||
*
|
||||
* This tests that the colors are able to be overridden on
|
||||
* a segment inside of a toolbar
|
||||
*
|
||||
* Indicator color / ripple color can be set on ion-segment
|
||||
*
|
||||
* Backgrounds / colors for the button must be set on the
|
||||
* ion-segment-button
|
||||
*/
|
||||
.custom-checked {
|
||||
--ripple-color: purple;
|
||||
--indicator-color: purple;
|
||||
}
|
||||
|
||||
code {
|
||||
display: block;
|
||||
padding: 0.5em;
|
||||
background: #ffffff;
|
||||
word-wrap: normal;
|
||||
white-space: pre;
|
||||
color: #314361;
|
||||
.ios .custom-checked ion-segment-button {
|
||||
--color-checked: white;
|
||||
}
|
||||
|
||||
.md .custom-checked ion-segment-button {
|
||||
--background-hover: rgba(17, 228, 10, 0.5);
|
||||
--color-checked: purple;
|
||||
--color-hover: blue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom Themed Segment (Themed to Primary)
|
||||
*/
|
||||
.themed {
|
||||
--ion-toolbar-background: #3880ff;
|
||||
--ion-toolbar-color: #fff;
|
||||
--ion-toolbar-color-activated: #fff;
|
||||
--ion-toolbar-color-unchecked: rgba(255, 255, 255, .6);
|
||||
--ion-toolbar-color-checked: #fff;
|
||||
|
||||
/* Segment */
|
||||
--ion-toolbar-segment-indicator-color: #ffffff;
|
||||
}
|
||||
|
||||
.custom {
|
||||
/* Material Design Segment */
|
||||
.md .themed {
|
||||
--ion-toolbar-segment-color: rgba(255, 255, 255, .6);
|
||||
--ion-toolbar-segment-color-checked: #ffffff;
|
||||
--ion-toolbar-segment-background-hover: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* iOS Segment */
|
||||
.ios .themed {
|
||||
--ion-toolbar-segment-background: rgba(255, 255, 255, 0.065);
|
||||
--ion-toolbar-segment-color: #ffffff;
|
||||
--ion-toolbar-segment-color-checked: #3880ff;
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom Active Segment (Active, Inactive, Disabled)
|
||||
*/
|
||||
.custom-active {
|
||||
--background: papayawhip;
|
||||
--background-checked: navy;
|
||||
|
||||
--border-color: navy;
|
||||
--border-color-checked: navy;
|
||||
--border-color-disabled: navy;
|
||||
|
||||
--color: navy;
|
||||
--color-activated: purple;
|
||||
--color-checked: papayawhip;
|
||||
--color: purple;
|
||||
--color-disabled: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Custom Icon Segment MD */
|
||||
.md .custom-icon {
|
||||
--indicator-color: lightgray;
|
||||
.ios .custom-active {
|
||||
--color-checked: papayawhip;
|
||||
--indicator-color: navy;
|
||||
}
|
||||
|
||||
.md .segment-facebook {
|
||||
--background-hover: rgba(59, 89, 153, .04);
|
||||
--color-activated: #3b5999;
|
||||
--color-checked: #3b5999;
|
||||
}
|
||||
|
||||
.md .segment-instagram {
|
||||
--background-hover: rgba(228, 64, 95, .04);
|
||||
--color-activated: #e4405f;
|
||||
--color-checked: #e4405f;
|
||||
}
|
||||
|
||||
.md .segment-slack {
|
||||
--background-hover: rgba(58, 175, 133, .04);
|
||||
--color-activated: #3aaf85;
|
||||
--color-checked: #3aaf85;
|
||||
}
|
||||
|
||||
/* Custom Icon Segment iOS */
|
||||
.ios .custom-icon ion-segment-button {
|
||||
--border-width: 0;
|
||||
}
|
||||
|
||||
.ios .segment-facebook {
|
||||
--color: #3b5999;
|
||||
--color-checked: #ffffff;
|
||||
--background-hover: rgba(59, 89, 153, .1);
|
||||
--background-activated: rgba(59, 89, 153, .16);
|
||||
--background-checked: #3b5999;
|
||||
}
|
||||
|
||||
.ios .segment-instagram {
|
||||
--color: #e4405f;
|
||||
--color-checked: #ffffff;
|
||||
--background-hover: rgba(228, 64, 95, .1);
|
||||
--background-activated: rgba(228, 64, 95, .16);
|
||||
--background-checked: #e4405f;
|
||||
}
|
||||
|
||||
.ios .segment-slack {
|
||||
--color: #3aaf85;
|
||||
--color-checked: #ffffff;
|
||||
--background-hover: rgba(58, 175, 133, .1);
|
||||
--background-activated: rgba(58, 175, 133, .16);
|
||||
--background-checked: #3aaf85;
|
||||
.md .custom-active {
|
||||
--color-checked: navy;
|
||||
--indicator-color: navy;
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom Icon Segment (Facebook, Instagram, Slack)
|
||||
*/
|
||||
.custom-icon ion-icon {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
/*
|
||||
* MD Custom Icon Segment (Facebook, Instagram, Slack)
|
||||
*/
|
||||
.md .segment-button-facebook {
|
||||
--background-hover: rgb(58, 61, 70, .04);
|
||||
--color-checked: #3a3d46;
|
||||
--indicator-color: #3a3d46;
|
||||
}
|
||||
|
||||
.md .segment-button-instagram {
|
||||
--background-hover: rgb(228, 64, 95, .04);
|
||||
--color-checked: #e4405f;
|
||||
--indicator-color: #e4405f;
|
||||
}
|
||||
|
||||
.md .segment-button-slack {
|
||||
--background-hover: rgb(58, 175, 133, .04);
|
||||
--color-checked: #3aaf85;
|
||||
--indicator-color: #3aaf85;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Custom Icon Segment (Facebook, Instagram, Slack)
|
||||
*/
|
||||
.ios .segment-button-facebook {
|
||||
--color: #3a3d46;
|
||||
--color-checked: #ffffff;
|
||||
--indicator-color: #3a3d46;
|
||||
}
|
||||
|
||||
.ios .segment-button-instagram {
|
||||
--color: #e4405f;
|
||||
--color-checked: #ffffff;
|
||||
--indicator-color: #e4405f;
|
||||
}
|
||||
|
||||
.ios .segment-button-slack {
|
||||
--color: #3aaf85;
|
||||
--color-checked: #ffffff;
|
||||
--indicator-color: #3aaf85;
|
||||
}
|
||||
|
||||
/* This CSS should not apply */
|
||||
.custom-color {
|
||||
--background: purple;
|
||||
--color: blue;
|
||||
}
|
||||
|
||||
.custom-states-color {
|
||||
--color: red;
|
||||
--color-disabled: blue;
|
||||
--color-checked: indigo;
|
||||
--color-hover: orange;
|
||||
}
|
||||
|
||||
.custom-states-background ion-segment-button {
|
||||
--indicator-color: transparent;
|
||||
--indicator-box-shadow: none;
|
||||
--color: white;
|
||||
|
||||
--background: red;
|
||||
--background-disabled: blue;
|
||||
--background-checked: indigo;
|
||||
--background-hover: orange;
|
||||
}
|
||||
|
||||
.custom-states-background ion-segment-button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
</html>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script></head>
|
||||
|
||||
<body onLoad="setLayout()">
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar color="tertiary">
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
<ion-content>
|
||||
<!-- Label only -->
|
||||
<ion-segment>
|
||||
<ion-segment id="multi-color">
|
||||
<ion-segment-button checked>
|
||||
<ion-label>Item One</ion-label>
|
||||
</ion-segment-button>
|
||||
@ -96,7 +96,7 @@
|
||||
</ion-segment>
|
||||
|
||||
<!-- Icon start -->
|
||||
<ion-segment scrollable>
|
||||
<ion-segment>
|
||||
<ion-segment-button checked layout="icon-start">
|
||||
<ion-label>Item One</ion-label>
|
||||
<ion-icon name="call"></ion-icon>
|
||||
@ -112,7 +112,7 @@
|
||||
</ion-segment>
|
||||
|
||||
<!-- Icon end -->
|
||||
<ion-segment scrollable>
|
||||
<ion-segment>
|
||||
<ion-segment-button checked layout="icon-end">
|
||||
<ion-icon name="call"></ion-icon>
|
||||
<ion-label>Item One</ion-label>
|
||||
@ -144,7 +144,7 @@
|
||||
</ion-segment>
|
||||
|
||||
<!-- Color -->
|
||||
<ion-segment scrollable color="secondary">
|
||||
<ion-segment color="secondary">
|
||||
<ion-segment-button checked layout="icon-end">
|
||||
<ion-icon name="call"></ion-icon>
|
||||
<ion-label>Item One</ion-label>
|
||||
@ -188,14 +188,30 @@
|
||||
|
||||
<style>
|
||||
ion-content {
|
||||
--background: #e5e5e5;
|
||||
--padding-top: 16px;
|
||||
}
|
||||
|
||||
ion-content ion-segment {
|
||||
background: white;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.md ion-content {
|
||||
--background: #e5e5e5;
|
||||
}
|
||||
|
||||
.md ion-content ion-segment {
|
||||
background: white;
|
||||
}
|
||||
|
||||
#multi-color ion-segment-button:first-of-type {
|
||||
--indicator-color: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
#multi-color ion-segment-button:nth-of-type(2) {
|
||||
--indicator-color: rgba(0, 255, 0, 0.5);
|
||||
}
|
||||
#multi-color ion-segment-button:nth-of-type(3) {
|
||||
--indicator-color: rgba(0, 0, 255, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@ -209,6 +225,11 @@
|
||||
segmentButtons[i].layout = (mode === 'ios') ? 'icon-hide' : 'icon-top';
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
requestAnimationFrame(() => {
|
||||
setLayout();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</ion-app>
|
||||
|
@ -4,14 +4,16 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Segment - Standalone</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<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/core.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>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body onLoad="onLoad()">
|
||||
<ion-toolbar>
|
||||
<ion-segment value="Free">
|
||||
<ion-segment-button value="Paid">
|
||||
@ -77,15 +79,15 @@
|
||||
</ion-segment>
|
||||
|
||||
<ion-segment class="custom-icon" value="instagram">
|
||||
<ion-segment-button class="segment-facebook" value="facebook">
|
||||
<ion-segment-button class="segment-button-facebook" value="facebook">
|
||||
<ion-label>Facebook</ion-label>
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="segment-instagram" value="instagram">
|
||||
<ion-segment-button class="segment-button-instagram" value="instagram">
|
||||
<ion-label>Instagram</ion-label>
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button class="segment-slack" value="slack">
|
||||
<ion-segment-button class="segment-button-slack" value="slack">
|
||||
<ion-label>Slack</ion-label>
|
||||
<ion-icon name="logo-slack"></ion-icon>
|
||||
</ion-segment-button>
|
||||
@ -145,6 +147,29 @@
|
||||
results.innerHTML = value.charAt(0).toUpperCase() + value.slice(1);
|
||||
segment.value = value;
|
||||
}
|
||||
|
||||
async function onLoad() {
|
||||
const customIconSegments = document.querySelectorAll('.custom-icon');
|
||||
|
||||
for (var i = 0; i < customIconSegments.length; i++) {
|
||||
const customIconSegment = customIconSegments[i];
|
||||
|
||||
await customIconSegment.componentOnReady();
|
||||
addIconClass(customIconSegment, customIconSegment.value);
|
||||
|
||||
customIconSegment.addEventListener('ionChange', function (ev) {
|
||||
addIconClass(customIconSegment, ev.detail.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addIconClass(el, value) {
|
||||
console.log('adding class to', el, value);
|
||||
if (value) {
|
||||
el.classList.remove('segment-facebook-checked', 'segment-instagram-checked', 'segment-slack-checked');
|
||||
el.classList.add(`segment-${value}-checked`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -188,23 +213,64 @@
|
||||
--color: navy;
|
||||
}
|
||||
|
||||
.custom-icon {
|
||||
--indicator-color: lightgray;
|
||||
/*
|
||||
* Custom Icon Segment (Facebook, Instagram, Slack)
|
||||
*/
|
||||
.custom-icon ion-icon {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.segment-facebook {
|
||||
--background-hover: rgba(59, 89, 153, .04);
|
||||
--color-checked: #3b5999;
|
||||
/*
|
||||
* MD Custom Icon Segment (Facebook, Instagram, Slack)
|
||||
*/
|
||||
.md .segment-facebook-checked .segment-button-facebook {
|
||||
--color-checked: #3a3d46;
|
||||
--indicator-color: #3a3d46;
|
||||
}
|
||||
|
||||
.segment-instagram {
|
||||
--background-hover: rgba(228, 64, 95, .04);
|
||||
.md .segment-instagram-checked .segment-button-instagram {
|
||||
--color-checked: #e4405f;
|
||||
--indicator-color: #e4405f;
|
||||
}
|
||||
|
||||
.segment-slack {
|
||||
--background-hover: rgba(58, 175, 133, .04);
|
||||
.md .segment-slack-checked .segment-button-slack {
|
||||
--color-checked: #3aaf85;
|
||||
--indicator-color: #3aaf85;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Custom Icon Segment (Facebook, Instagram, Slack)
|
||||
*/
|
||||
.ios .custom-icon {
|
||||
--indicator-color: transparent;
|
||||
--indicator-transition: none;
|
||||
}
|
||||
|
||||
.ios .segment-facebook-checked .segment-button-facebook {
|
||||
--background-checked: #3a3d46;
|
||||
--color-checked: #ffffff;
|
||||
}
|
||||
|
||||
.ios .segment-instagram-checked .segment-button-instagram {
|
||||
--background-checked: #e4405f;
|
||||
--color-checked: #ffffff;
|
||||
}
|
||||
|
||||
.ios .segment-slack-checked .segment-button-slack {
|
||||
--background-checked: #3aaf85;
|
||||
--color-checked: #ffffff;
|
||||
}
|
||||
|
||||
.ios .segment-button-facebook {
|
||||
--color: #3a3d46;
|
||||
}
|
||||
|
||||
.ios .segment-button-instagram {
|
||||
--color: #e4405f;
|
||||
}
|
||||
|
||||
.ios .segment-button-slack {
|
||||
--color: #3aaf85;
|
||||
}
|
||||
|
||||
.custom-indicator {
|
||||
@ -215,6 +281,7 @@
|
||||
.large-icon ion-icon {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</html>
|
||||
|
23
core/src/components/segment/test/toolbar/e2e.ts
Normal file
23
core/src/components/segment/test/toolbar/e2e.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('segment: toolbar', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/segment/test/toolbar?ionic:_testing=true'
|
||||
});
|
||||
|
||||
await page.waitFor(250);
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('segment:rtl: toolbar', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/segment/test/toolbar?ionic:_testing=true&rtl=true'
|
||||
});
|
||||
|
||||
await page.waitFor(250);
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
@ -16,7 +16,7 @@
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Segment - Colors</ion-title>
|
||||
<ion-title>Segment - Toolbar</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar>
|
||||
@ -114,6 +114,17 @@
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
|
||||
<ion-toolbar color="primary">
|
||||
<ion-segment color="light">
|
||||
<ion-segment-button value="sunny">
|
||||
Sunny
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="rainy" checked>
|
||||
Rainy
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
|
||||
<!-- Label only -->
|
||||
<ion-toolbar color="primary">
|
||||
<ion-segment>
|
||||
@ -264,13 +275,17 @@
|
||||
}
|
||||
|
||||
.ios .themed {
|
||||
--ion-toolbar-color-unchecked: #fff;
|
||||
--ion-toolbar-color-checked: #3880ff;
|
||||
--ion-toolbar-segment-background: rgba(255, 255, 255, 0.11);
|
||||
--ion-toolbar-segment-color: #fff;
|
||||
|
||||
--ion-toolbar-segment-color-checked: #3880ff;
|
||||
}
|
||||
|
||||
.md .themed {
|
||||
--ion-toolbar-color-unchecked: rgba(255, 255, 255, 0.6);
|
||||
--ion-toolbar-color-checked: #fff;
|
||||
--ion-toolbar-segment-color: rgba(255, 255, 255, 0.6);
|
||||
--ion-toolbar-segment-color-checked: #fff;
|
||||
|
||||
--ion-toolbar-segment-indicator-color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
Reference in New Issue
Block a user