feat(range): adding custom styling for ionic theme (#29979)

Issue number: 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?
No ionic theme custom implementation existed for the ion-range
component.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->
- Added ionic theme specific styling (based on the ios styling) for the
range according to UX requirements.

## 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. -->

![image](https://github.com/user-attachments/assets/1b78cba4-0f0f-45d4-abdc-d48cdb917ea2)
This commit is contained in:
Pedro Lourenço
2024-11-05 13:03:11 +00:00
committed by GitHub
parent b046f37d17
commit 175dc929a5
19 changed files with 305 additions and 64 deletions

View File

@@ -1,4 +1,4 @@
@import "../../themes/native/native.globals";
@import "../../themes/mixins";
@import "./range.vars.scss";
// Range
@@ -18,7 +18,6 @@
* @prop --pin-background: Background of the range pin (only available in MD mode)
* @prop --pin-color: Color of the range pin (only available in MD mode)
*/
--knob-handle-size: calc(var(--knob-size) * 2);
display: flex;
position: relative;
@@ -26,10 +25,7 @@
flex: 3;
align-items: center;
font-family: $font-family-base;
user-select: none;
z-index: $z-index-item-input;
}
:host(.range-disabled) {
@@ -40,10 +36,6 @@
flex: initial;
}
::slotted(ion-icon[slot]) {
font-size: 24px;
}
.range-slider {
position: relative;
@@ -214,8 +206,6 @@
* only the label would show and users
* would not be able to see the range.
*/
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
@@ -254,15 +244,6 @@
flex-direction: row;
}
:host(.range-label-placement-start) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the end
* when the label sits at the start.
*/
@include margin(0, $form-control-label-margin, 0, 0);
}
// Range Label Placement - End
// ----------------------------------------------------------------
@@ -274,27 +255,9 @@
flex-direction: row-reverse;
}
/**
* The margin between the label and
* the range should be on the start
* when the label sits at the end.
*/
:host(.range-label-placement-end) .label-text-wrapper {
@include margin(0, 0, 0, $form-control-label-margin);
}
// Range Label Placement - Fixed
// ----------------------------------------------------------------
:host(.range-label-placement-fixed) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the end
* when the label sits at the start.
*/
@include margin(0, $form-control-label-margin, 0, 0);
}
/**
* Label is on the left of the range in LTR and
* on the right in RTL. Label also has a fixed width.
@@ -321,26 +284,4 @@
:host(.range-label-placement-stacked) .label-text-wrapper {
@include transform-origin(start, top);
@include transform(scale(#{$form-control-label-stacked-scale}));
/**
* The margin between the label and
* the range should be on the bottom
* when the label sits on top.
*/
@include margin(null, 0, $form-control-label-margin, 0);
/**
* Label text should not extend
* beyond the bounds of the range.
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}
:host(.in-item.range-label-placement-stacked) .label-text-wrapper {
@include margin($range-item-label-margin-top, null, $form-control-label-margin, null);
}
:host(.in-item.range-label-placement-stacked) .native-wrapper {
@include margin(null, null, $range-item-label-margin-bottom, null);
}

View File

@@ -0,0 +1,217 @@
@use "../../themes/ionic/ionic.globals.scss" as globals;
@import "./range.common";
// Ionic Range
// --------------------------------------------------
:host {
--knob-border-radius: #{globals.$ion-border-radius-full};
--knob-background: #{globals.$ion-primitives-base-white};
--knob-box-shadow: none;
--knob-size: #{globals.$ion-scale-600};
--knob-handle-size: #{globals.$ion-scale-1000};
--bar-height: #{globals.$ion-scale-200};
--bar-background: #{globals.$ion-primitives-neutral-100};
--bar-background-active: #{globals.ion-color(primary, base)};
--bar-border-radius: #{globals.$ion-border-radius-400};
--height: #{globals.$ion-scale-1000};
@include globals.typography(globals.$ion-body-md-regular);
z-index: globals.$ion-z-index-100;
}
:host(.range-item-start-adjustment) {
@include padding(null, null, null, globals.$ion-space-600);
}
:host(.range-item-end-adjustment) {
@include padding(null, globals.$ion-space-600, null, null);
}
:host(.ion-color) .range-bar-active,
:host(.ion-color) .range-tick-active {
background: globals.current-color(base);
}
::slotted(ion-icon[slot]) {
font-size: globals.$ion-font-size-600;
}
::slotted([slot="start"]) {
@include margin(0, globals.$ion-space-400, 0, 0);
}
::slotted([slot="end"]) {
@include margin(0, 0, 0, globals.$ion-space-400);
}
::slotted([slot="label"]) {
max-width: globals.$ion-scale-5000;
}
:host(.range-has-pin:not(.range-label-placement-stacked)) {
/**
* The pin should not overlap any elements that are
* above the range. By adding padding to the top of the
* range, it provides a buffer for the pin to move into
* when it is pressed.
*
* The padding is not included when the label is stacked
* because the pin is below the label.
* It still requires a buffer to prevent the pin from
* overlapping the range. The buffer is added to the
* bottom of the range label instead of the host.
*/
@include padding(calc(globals.$ion-space-200 + globals.dynamic-font(12px)), null, null, null);
}
:host(.range-has-pin.range-label-placement-stacked) .label-text-wrapper {
/**
* The pin should not overlap the stacked label. By adding
* margin to the bottom of the label, it provides a buffer
* for the pin to move into when it is pressed.
*/
@include margin(null, null, calc(globals.$ion-space-200 + globals.dynamic-font(12px)), null);
}
.range-bar-active {
bottom: 0;
width: auto;
background: var(--bar-background-active);
&.has-ticks {
@include border-radius(0);
@include margin(null, calc(-1 * globals.$ion-scale-100 * 0.5), null, calc(-1 * globals.$ion-scale-100 * 0.5));
}
}
.range-tick {
@include margin-horizontal(calc(globals.$ion-scale-100 * -0.5), null);
@include border-radius(globals.$ion-border-radius-0);
position: absolute;
top: calc((var(--height) * 0.5) - (globals.$ion-scale-300 * 0.5));
width: globals.$ion-scale-100;
height: globals.$ion-scale-300;
background: globals.$ion-primitives-neutral-100;
pointer-events: none;
}
.range-tick-active {
background: var(--bar-background-active);
}
.range-pin {
@include transform(translate3d(0, 100%, 0), scale(0.01));
@include padding(globals.$ion-space-200, globals.$ion-space-200, globals.$ion-space-200, globals.$ion-space-200);
min-width: globals.$ion-scale-700;
transition: transform 120ms ease;
background: transparent;
font-size: globals.dynamic-font(12px);
text-align: center;
}
.range-knob {
border-width: globals.$ion-border-size-025;
border-style: globals.$ion-border-style-solid;
border-color: globals.ion-color(primary, base);
box-sizing: border-box;
}
/**
* The -100% ensures the pin sits on top
* of the range-knob-handle container.
* We apply 11px so that the pin
* text is closer to the knob inside of the container.
* We also apply the 11px here instead of using "top"
* otherwise the pin text will translate below the knob
* when the text is scaled.
*/
.range-knob-pressed .range-pin,
.range-knob-handle.ion-focused .range-pin {
transform: translate3d(0, calc(-100% + 11px), 0) scale(1);
}
// Ionic Range: Disabled (based on iOS Range)
// --------------------------------------------------
// When the range is disabled, the entire range,
// range label, any slotted labels, and any slotted
// icons need to receive the same opacity.
:host(.range-disabled) {
opacity: 0.3;
}
// Range Label Placement - Start
// ----------------------------------------------------------------
:host(.range-label-placement-start) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the end
* when the label sits at the start.
*/
@include margin(0, globals.$ion-space-400, 0, 0);
}
// Range Label Placement - End
// ----------------------------------------------------------------
:host(.range-label-placement-end) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the start
* when the label sits at the end.
*/
@include margin(0, 0, 0, globals.$ion-space-400);
}
// Range Label Placement - Fixed
// ----------------------------------------------------------------
:host(.range-label-placement-fixed) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the end
* when the label sits at the start.
*/
@include margin(0, globals.$ion-space-400, 0, 0);
}
// Range Label Placement - Stacked
// ----------------------------------------------------------------
:host(.range-label-placement-stacked) .label-text-wrapper {
@include transform(scale(0.75));
/**
* The margin between the label and
* the range should be on the bottom
* when the label sits on top.
*/
@include margin(null, 0, globals.$ion-space-400, 0);
/**
* Label text should not extend
* beyond the bounds of the range.
*/
max-width: calc(100% / 0.75);
}
:host(.in-item.range-label-placement-stacked) .label-text-wrapper {
@include margin(globals.$ion-space-250, null, globals.$ion-space-400, null);
}
:host(.in-item.range-label-placement-stacked) .native-wrapper {
@include margin(null, null, globals.$ion-space-0, null);
}

View File

@@ -1,4 +1,4 @@
@import "./range";
@import "./range.native";
@import "./range.ios.vars";
// iOS Range

View File

@@ -1,4 +1,4 @@
@import "./range";
@import "./range.native";
@import "./range.md.vars";
// Material Design Range

View File

@@ -0,0 +1,83 @@
@import "range.common";
@import "../../themes/native/native.globals";
@import "./range.vars.scss";
:host {
--knob-handle-size: calc(var(--knob-size) * 2);
font-family: $font-family-base;
z-index: $z-index-item-input;
}
::slotted(ion-icon[slot]) {
font-size: 24px;
}
::slotted([slot="label"]) {
max-width: 200px;
}
// Range Label Placement - Start
// ----------------------------------------------------------------
:host(.range-label-placement-start) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the end
* when the label sits at the start.
*/
@include margin(0, $form-control-label-margin, 0, 0);
}
// Range Label Placement - End
// ----------------------------------------------------------------
:host(.range-label-placement-end) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the start
* when the label sits at the end.
*/
@include margin(0, 0, 0, $form-control-label-margin);
}
// Range Label Placement - Fixed
// ----------------------------------------------------------------
:host(.range-label-placement-fixed) .label-text-wrapper {
/**
* The margin between the label and
* the range should be on the end
* when the label sits at the start.
*/
@include margin(0, $form-control-label-margin, 0, 0);
}
// Range Label Placement - Stacked
// ----------------------------------------------------------------
:host(.range-label-placement-stacked) .label-text-wrapper {
@include transform(scale(#{$form-control-label-stacked-scale}));
/**
* The margin between the label and
* the range should be on the bottom
* when the label sits on top.
*/
@include margin(null, 0, $form-control-label-margin, 0);
/**
* Label text should not extend
* beyond the bounds of the range.
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}
:host(.in-item.range-label-placement-stacked) .label-text-wrapper {
@include margin($range-item-label-margin-top, null, $form-control-label-margin, null);
}
:host(.in-item.range-label-placement-stacked) .native-wrapper {
@include margin(null, null, $range-item-label-margin-bottom, null);
}

View File

@@ -43,7 +43,7 @@ import type {
styleUrls: {
ios: 'range.ios.scss',
md: 'range.md.scss',
ionic: 'range.md.scss',
ionic: 'range.ionic.scss',
},
shadow: true,
})

View File

@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('range: basic'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/range/test/basic', config);

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB