Files
ionic-framework/core/src/components/segment-button/segment-button.scss
Brandy Carney 448e63fef0 feat(segment): display segment as a grid and add an ellipsis to overflowing text in a segment button (#27457)
Issue number: resolves #16532

---------

## What is the current behavior?
Text that is too long to fit in a segment button does not ellipsis,
instead it centers all of the text and cuts it off at the beginning and
end of the text.

## What is the new behavior?
Text that is wider than the segment button will now add an ellipsis and
cut the text off instead of horizontally centering the text and
overflowing the button (while being cut off visually).

While researching how to fix this issue I discovered that the button
text was not properly overflowing when it should due to a limitation of
flex. I was able to mock segments using divs and see that certain
buttons were adding an ellipsis when there was room to grow. This is due
to a combination of using `flex-basis: 0` on the segment buttons and
`width: auto` on the segment inside of a toolbar. This
[blog](https://css-tricks.com/equal-columns-with-flexbox-its-more-complicated-than-you-might-think)
sums it up well, but it is not something I could work around with
segment set as `display: flex`. When I changed the mocked segment to use
`display: grid`, it allowed the text to properly grow, while overflowing
and adding an ellipsis when it couldn't grow. This can be seen in my
[Codepen
example](https://codepen.io/brandyscarney/pen/poOpbWE?editors=1100).

As a result, I made the following updates:

- Changed the `ion-segment` to [`display:
grid`](https://github.com/ionic-team/ionic-framework/pull/27457/files#diff-dedcf5921daa49880ebae649e04d4f488a6b965c885a7bb1fdf29a5f1b3d501fR14)
(`display: inline-grid` could not be used because the highlight was not
properly aligned to the bottom of a toolbar for `md` mode)
- Moves the `max-width` for Material Design segment buttons to the
parent segment by using [`grid-auto-columns: minmax(auto,
$segment-button-md-max-width);`](https://github.com/ionic-team/ionic-framework/pull/27457/files#diff-8df7c6681b616fdc975b068e3d31282cc4997222e786db6365ebcef3bccbb6d3R10).
This is necessary for the buttons to properly center inside of the grid
when they all hit the max-width (360px at the moment).
- The Material Design segment buttons will now take up equal widths.
This matches the [MD2 spec for fixed
segments](https://m2.material.io/components/tabs#fixed-tabs).
- Sets [`grid-row:
1`](https://github.com/ionic-team/ionic-framework/pull/27457/files#diff-761a18ae6f41275e0eb63e9710045cafd5b221721ef1dad1d46d562e50404615R75)
on the host segment button. This tells the segment buttons to stay on
the same row.
- Sets [`max-width:
100%`](https://github.com/ionic-team/ionic-framework/pull/27457/files#diff-761a18ae6f41275e0eb63e9710045cafd5b221721ef1dad1d46d562e50404615R214)
and [`overflow:
hidden`](https://github.com/ionic-team/ionic-framework/pull/27457/files#diff-761a18ae6f41275e0eb63e9710045cafd5b221721ef1dad1d46d562e50404615R222)
on the native button. This allows the text itself to ellipsis.
- Added tests for segment wrapping & went through all existing tests to
add missing `ion-label` elements in the segment buttons

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

### High-level changes between main and this branch

| `main` | `FW-3401` |
| ---| ---|
|
![main-ios](https://github.com/ionic-team/ionic-framework/assets/6577830/b245fffa-e09c-4a96-bc6e-c4d2ee68cd16)
|
![branch-ios](https://github.com/ionic-team/ionic-framework/assets/6577830/397d95f5-24d3-4421-b960-1a8a6ace7c26)
|
|
![main-md](https://github.com/ionic-team/ionic-framework/assets/6577830/75c77373-0dde-4e7d-a7fa-fc082fcb5c7a)
|
![branch-md](https://github.com/ionic-team/ionic-framework/assets/6577830/402f8293-8ea5-47fb-bc13-7dc2d85b361a)
|
2023-05-24 11:42:57 -04:00

319 lines
7.2 KiB
SCSS

@import "../../themes/ionic.globals";
// Segment Button
// --------------------------------------------------
:host {
/**
* @prop --background: Background of the segment button
* @prop --background-checked: Background of the checked segment button
*
* @prop --background-hover: Background of the segment button on hover
* @prop --background-focused: Background of the segment button when focused with the tab key
*
* @prop --background-hover-opacity: Opacity of the segment button background on hover
* @prop --background-focused-opacity: Opacity of the segment button background when focused with the tab key
*
* @prop --color: Color of the segment button
* @prop --color-checked: Color of the checked segment button
* @prop --color-hover: Color of the segment button on hover
* @prop --color-focused: Color of the segment button when focused with the tab key
*
* @prop --border-radius: Radius of the segment button border
* @prop --border-color: Color of the segment button border
* @prop --border-style: Style of the segment button border
* @prop --border-width: Width of the segment button border
*
* @prop --margin-top: Top margin of the segment button
* @prop --margin-end: Right margin if direction is left-to-right, and left margin if direction is right-to-left of the segment button
* @prop --margin-bottom: Bottom margin of the segment button
* @prop --margin-start: Left margin if direction is left-to-right, and right margin if direction is right-to-left of the segment button
*
* @prop --padding-top: Top padding of the segment button
* @prop --padding-end: Right padding if direction is left-to-right, and left padding if direction is right-to-left of the segment button
* @prop --padding-bottom: Bottom padding of the segment button
* @prop --padding-start: Left padding if direction is left-to-right, and right padding if direction is right-to-left of the segment button
*
* @prop --transition: Transition of the segment button
*
* @prop --indicator-height: Height of the indicator for the checked segment button
* @prop --indicator-box-shadow: Box shadow on the indicator for the checked segment button
* @prop --indicator-color: Color of the indicator for the checked segment button
* @prop --indicator-transition: Transition of the indicator for the checked segment button
* @prop --indicator-transform: Transform of the indicator for the checked segment button
*/
--color: initial;
--color-hover: var(--color);
--color-checked: var(--color);
--color-disabled: var(--color);
--padding-start: 0;
--padding-end: 0;
--padding-top: 0;
--padding-bottom: 0;
@include border-radius(var(--border-radius));
display: flex;
position: relative;
flex-direction: column;
height: auto;
background: var(--background);
color: var(--color);
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
grid-row: 1;
font-kerning: none;
}
.button-native {
@include border-radius(0);
@include text-inherit();
@include margin(var(--margin-top), var(--margin-end), var(--margin-bottom), var(--margin-start));
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
@include transform(translate3d(0,0,0));
display: flex;
position: relative;
flex-direction: inherit;
flex-grow: 1;
align-items: center;
justify-content: center;
width: 100%;
min-width: inherit;
max-width: inherit;
height: auto;
min-height: inherit;
max-height: inherit;
transition: var(--transition);
border: none;
outline: none;
background: transparent;
contain: content;
pointer-events: none;
overflow: hidden;
z-index: 2;
}
.button-native::after {
@include button-state();
}
.button-inner {
display: flex;
position: relative;
flex-flow: inherit;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
z-index: 1;
}
// Segment Button: Checked
// --------------------------------------------------
:host(.segment-button-checked) {
background: var(--background-checked);
color: var(--color-checked);
}
// Segment Button: Disabled
// --------------------------------------------------
:host(.segment-button-disabled) {
cursor: default;
pointer-events: none;
}
// Segment Button: Focused
// --------------------------------------------------
:host(.ion-focused) .button-native {
color: var(--color-focused);
&::after {
background: var(--background-focused);
opacity: var(--background-focused-opacity);
}
}
:host(:focus) {
outline: none;
}
// Segment Button: Hover
// --------------------------------------------------
@media (any-hover: hover) {
:host(:hover) .button-native {
color: var(--color-hover);
&::after {
background: var(--background-hover);
opacity: var(--background-hover-opacity);
}
}
:host(.segment-button-checked:hover) .button-native {
color: var(--color-checked);
}
}
// Segment Button Icon
// --------------------------------------------------
::slotted(ion-icon) {
flex-shrink: 0;
order: -1;
pointer-events: none;
}
// Segment Button Label
// --------------------------------------------------
::slotted(ion-label) {
display: block;
align-self: center;
max-width: 100%;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
pointer-events: none;
}
// Segment Button Layout
// --------------------------------------------------
// Layout: icon top
:host(.segment-button-layout-icon-top) .button-native {
flex-direction: column;
}
// Layout: icon start
:host(.segment-button-layout-icon-start) .button-native {
flex-direction: row;
}
// Layout: icon end
:host(.segment-button-layout-icon-end) .button-native {
flex-direction: row-reverse;
}
// Layout: icon bottom
:host(.segment-button-layout-icon-bottom) .button-native {
flex-direction: column-reverse;
}
// Layout: icon hide
:host(.segment-button-layout-icon-hide) ::slotted(ion-icon) {
display: none;
}
// Layout: label hide
:host(.segment-button-layout-label-hide) ::slotted(ion-label) {
display: none;
}
// Segment Button Ripple
// --------------------------------------------------
ion-ripple-effect {
color: var(--ripple-color, var(--color-checked));
}
// Segment Button: Indicator
// --------------------------------------------------
.segment-button-indicator {
@include transform-origin(left);
position: absolute;
opacity: 0;
box-sizing: border-box;
will-change: transform, opacity;
pointer-events: none;
}
.segment-button-indicator-background {
width: 100%;
height: var(--indicator-height);
transform: var(--indicator-transform);
box-shadow: var(--indicator-box-shadow);
pointer-events: none;
}
.segment-button-indicator-animated {
transition: var(--indicator-transition);
}
:host(.segment-button-checked) .segment-button-indicator {
opacity: 1;
}
// Segment: Reduced Motion
// --------------------------------------------------
@media (prefers-reduced-motion: reduce) {
.segment-button-indicator-background {
transform: none;
}
.segment-button-indicator-animated {
transition: none;
}
}