feat(css): add new css utility classes for display and flex utils (#30567)

Issue number: resolves #22469

---------

- Adds new responsive display classes with the following values: `none`, `inline`, `inline-block`, `block`, `flex`, `inline-flex`, `grid`, `inline-grid`, `table`, `table-cell`, `table-row`
- Adds new responsive flex util classes for the following properties: `align-content`, `align-items`, `align-self`, `justify-content`, `flex-direction`, `flex-wrap`, `flex`, `flex-grow` , `flex-shrink`, `order`
- Adds e2e tests to verify the correct classes are in the CSS files

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2025-07-28 16:32:50 -04:00
committed by Brandy Smith
parent d5627c7368
commit 75f6c05fb9
6 changed files with 378 additions and 86 deletions

View File

@ -125,7 +125,7 @@
<ion-toolbar color="dark">
<ion-buttons slot="start">
<ion-back-button class="ion-hide"></ion-back-button>
<ion-back-button class="ion-display-none"></ion-back-button>
</ion-buttons>
<ion-title>Hidden</ion-title>
</ion-toolbar>

View File

@ -107,6 +107,7 @@ const renderProgress = (value: number, buffer: number) => {
* When finalBuffer === 1, we use display: none
* instead of removing the element to avoid flickering.
*/
// TODO(FW-6697): change `ion-hide` class to `ion-display-none` or another class
<div
class={{ 'buffer-circles-container': true, 'ion-hide': finalBuffer === 1 }}
style={{ transform: `translateX(${finalBuffer * 100}%)` }}

View File

@ -2,9 +2,14 @@
@import "../themes/ionic.mixins";
// Display
// --------------------------------------------------
// Modifies display of a particular element based on the given classes
// ------------------------------------------------------------------
// Provides utility classes to control the CSS display property
// of elements. Includes responsive variants for toggling between
// block, inline, flex, grid, and other display values at different
// breakpoints.
// TODO(FW-6697): remove ion-hide-* classes in favor of the new
// ion-display-* classes
.ion-hide {
display: none !important;
}
@ -29,3 +34,29 @@
}
}
}
$display-values: (
none,
inline,
inline-block,
block,
flex,
inline-flex,
grid,
inline-grid,
table,
table-cell,
table-row
);
@each $display in $display-values {
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
.ion-display#{$infix}-#{$display} {
display: #{$display} !important;
}
}
}
}

View File

@ -1,99 +1,211 @@
@import "../themes/ionic.globals";
@import "../themes/ionic.mixins";
// Flex Utilities
// --------------------------------------------------
// Creates flex classes to align flex containers
// and items
// ------------------------------------------------------------------
// Provides utility classes to control flexbox layout, alignment,
// and sizing of elements. Includes responsive variants for managing
// flex direction, alignment, justification, wrapping, growth,
// shrinking, and ordering at different breakpoints.
// Align Self
// --------------------------------------------------
// Align Content
// ------------------------------------------------------------------
.ion-align-self-start {
align-self: flex-start !important;
$align-content-values: (
start: flex-start,
end: flex-end,
center: center,
between: space-between,
around: space-around,
stretch: stretch
);
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
@each $key, $value in $align-content-values {
.ion-align-content#{$infix}-#{$key} {
align-content: #{$value} !important;
}
}
}
}
.ion-align-self-end {
align-self: flex-end !important;
}
.ion-align-self-center {
align-self: center !important;
}
.ion-align-self-stretch {
align-self: stretch !important;
}
.ion-align-self-baseline {
align-self: baseline !important;
}
.ion-align-self-auto {
align-self: auto !important;
}
// Flex Wrap
// --------------------------------------------------
.ion-wrap {
flex-wrap: wrap !important;
}
.ion-nowrap {
flex-wrap: nowrap !important;
}
.ion-wrap-reverse {
flex-wrap: wrap-reverse !important;
}
// Justify Content
// --------------------------------------------------
.ion-justify-content-start {
justify-content: flex-start !important;
}
.ion-justify-content-center {
justify-content: center !important;
}
.ion-justify-content-end {
justify-content: flex-end !important;
}
.ion-justify-content-around {
justify-content: space-around !important;
}
.ion-justify-content-between {
justify-content: space-between !important;
}
.ion-justify-content-evenly {
justify-content: space-evenly !important;
}
// Align Items
// --------------------------------------------------
// ------------------------------------------------------------------
.ion-align-items-start {
align-items: flex-start !important;
$align-items-values: (
start,
end,
center,
stretch,
baseline
);
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
@each $value in $align-items-values {
.ion-align-items#{$infix}-#{$value} {
align-items: #{$value} !important;
}
}
}
}
.ion-align-items-center {
align-items: center !important;
// Align Self
// ------------------------------------------------------------------
$align-self-values: (
start,
end,
center,
stretch,
baseline,
auto
);
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
@each $value in $align-self-values {
.ion-align-self#{$infix}-#{$value} {
align-self: #{$value} !important;
}
}
}
}
.ion-align-items-end {
align-items: flex-end !important;
// Justify Content
// ------------------------------------------------------------------
$justify-content-values: (
start: flex-start,
end: flex-end,
center: center,
between: space-between,
around: space-around,
evenly: space-evenly
);
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
@each $key, $value in $justify-content-values {
.ion-justify-content#{$infix}-#{$key} {
justify-content: #{$value} !important;
}
}
}
}
.ion-align-items-stretch {
align-items: stretch !important;
// Flex Direction
// ------------------------------------------------------------------
$flex-direction-values: (
row,
row-reverse,
column,
column-reverse
);
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
@each $value in $flex-direction-values {
.ion-flex#{$infix}-#{$value} {
flex-direction: #{$value} !important;
}
}
}
}
.ion-align-items-baseline {
align-items: baseline !important;
// Flex Wrap
// ------------------------------------------------------------------
$flex-wrap-values: (
wrap,
nowrap,
wrap-reverse
);
@each $value in $flex-wrap-values {
// TODO(FW-6697): remove ion-wrap, ion-nowrap, ion-wrap-reverse
// in favor of the new ion-flex-wrap, ion-flex-nowrap, and
// ion-flex-wrap-reverse classes
.ion-#{$value} {
flex-wrap: #{$value} !important;
}
}
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
@each $value in $flex-wrap-values {
.ion-flex#{$infix}-#{$value} {
flex-wrap: #{$value} !important;
}
}
}
}
// Flex Fill
// ------------------------------------------------------------------
$flex-fill-values: (
1,
auto,
initial,
none
);
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
@each $value in $flex-fill-values {
.ion-flex#{$infix}-#{$value} {
flex: #{$value} !important;
}
}
}
}
// Flex Grow and Shrink
// ------------------------------------------------------------------
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
.ion-flex#{$infix}-grow-0 {
flex-grow: 0 !important;
}
.ion-flex#{$infix}-grow-1 {
flex-grow: 1 !important;
}
.ion-flex#{$infix}-shrink-0 {
flex-shrink: 0 !important;
}
.ion-flex#{$infix}-shrink-1 {
flex-shrink: 1 !important;
}
}
}
// Flex Order
// ------------------------------------------------------------------
@each $breakpoint in map-keys($screen-breakpoints) {
$infix: breakpoint-infix($breakpoint, $screen-breakpoints);
@include media-breakpoint-up($breakpoint, $screen-breakpoints) {
.ion-order#{$infix}-first { order: -1 !important; }
@for $i from 0 through 12 {
.ion-order#{$infix}-#{$i} { order: #{$i} !important; }
}
.ion-order#{$infix}-last { order: 13 !important; }
}
}

View File

@ -0,0 +1,48 @@
import { test, expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';
test.describe('display css utility classes', () => {
let css: string;
test.beforeAll(() => {
css = fs.readFileSync(path.resolve(__dirname, '../../../css/display.css'), 'utf8');
});
const INFIXES = ['', '-sm', '-md', '-lg', '-xl'];
// TODO(FW-6697): remove `ion-hide classes` test
test('ion-hide classes', () => {
expect(css).toContain('.ion-hide');
const values = ['up', 'down'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-hide${infix}-${value}`);
}
}
});
test('ion-display classes', () => {
const values = [
'none',
'inline',
'inline-block',
'block',
'flex',
'inline-flex',
'grid',
'inline-grid',
'table',
'table-cell',
'table-row',
];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-display${infix}-${value}`);
}
}
});
});

View File

@ -0,0 +1,100 @@
import { test, expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';
test.describe('flex-utils css utility classes', () => {
let css: string;
test.beforeAll(() => {
css = fs.readFileSync(path.resolve(__dirname, '../../../css/flex-utils.css'), 'utf8');
});
const INFIXES = ['', '-sm', '-md', '-lg', '-xl'];
test('align-content classes', () => {
const values = ['start', 'end', 'center', 'between', 'around', 'stretch'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-align-content${infix}-${value}`);
}
}
});
test('align-items classes', () => {
const values = ['start', 'center', 'end', 'stretch', 'baseline'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-align-items${infix}-${value}`);
}
}
});
test('align-self classes', () => {
const values = ['start', 'end', 'center', 'stretch', 'baseline', 'auto'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-align-self${infix}-${value}`);
}
}
});
test('justify-content classes', () => {
const values = ['start', 'center', 'end', 'around', 'between', 'evenly'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-justify-content${infix}-${value}`);
}
}
});
test('flex-direction classes', () => {
const values = ['row', 'row-reverse', 'column', 'column-reverse'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-flex${infix}-${value}`);
}
}
});
test('flex-wrap classes', () => {
const values = ['wrap', 'nowrap', 'wrap-reverse'];
// TODO(FW-6697): remove all `ion-wrap-*` expects
for (const value of values) {
expect(css).toContain(`.ion-${value}`);
}
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-flex${infix}-${value}`);
}
}
});
test('flex-fill classes', () => {
const values = ['1', 'auto', 'initial', 'none'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-flex${infix}-${value}`);
}
}
});
test('flex-grow and flex-shrink classes', () => {
const values = ['grow', 'shrink'];
for (const value of values) {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-flex${infix}-${value}-0`);
expect(css).toContain(`.ion-flex${infix}-${value}-1`);
}
}
});
test('flex-order classes', () => {
for (const infix of INFIXES) {
expect(css).toContain(`.ion-order${infix}-first`);
expect(css).toContain(`.ion-order${infix}-last`);
for (let i = 0; i <= 12; i++) {
expect(css).toContain(`.ion-order${infix}-${i}`);
}
}
});
});