Skip to content

Commit a35e13d

Browse files
authored
feat(select): add labelPlacement prop, justify prop, and rich content for select options (#31241)
Issue number: resolves #29890
1 parent 7a95d47 commit a35e13d

334 files changed

Lines changed: 4584 additions & 302 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/api.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,6 +1741,20 @@ ion-select,css-prop,--placeholder-opacity,ios
17411741
ion-select,css-prop,--placeholder-opacity,md
17421742
ion-select,css-prop,--ripple-color,ios
17431743
ion-select,css-prop,--ripple-color,md
1744+
ion-select,css-prop,--select-text-gap,ios
1745+
ion-select,css-prop,--select-text-gap,md
1746+
ion-select,css-prop,--select-text-media-border-color,ios
1747+
ion-select,css-prop,--select-text-media-border-color,md
1748+
ion-select,css-prop,--select-text-media-border-radius,ios
1749+
ion-select,css-prop,--select-text-media-border-radius,md
1750+
ion-select,css-prop,--select-text-media-border-style,ios
1751+
ion-select,css-prop,--select-text-media-border-style,md
1752+
ion-select,css-prop,--select-text-media-border-width,ios
1753+
ion-select,css-prop,--select-text-media-border-width,md
1754+
ion-select,css-prop,--select-text-media-height,ios
1755+
ion-select,css-prop,--select-text-media-height,md
1756+
ion-select,css-prop,--select-text-media-width,ios
1757+
ion-select,css-prop,--select-text-media-width,md
17441758
ion-select,part,bottom
17451759
ion-select,part,container
17461760
ion-select,part,error-text
@@ -1760,7 +1774,11 @@ ion-select-modal,prop,multiple,boolean | undefined,undefined,false,false
17601774
ion-select-modal,prop,options,SelectModalOption[],[],false,false
17611775

17621776
ion-select-option,shadow
1777+
ion-select-option,prop,description,string | undefined,undefined,false,false
17631778
ion-select-option,prop,disabled,boolean,false,false,false
1779+
ion-select-option,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false
1780+
ion-select-option,prop,labelPlacement,"end" | "start" | undefined,undefined,false,false
1781+
ion-select-option,prop,mode,"ios" | "md",undefined,false,false
17641782
ion-select-option,prop,value,any,undefined,false,false
17651783

17661784
ion-skeleton-text,shadow

core/src/components.d.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,10 +3143,26 @@ export namespace Components {
31433143
}
31443144
interface IonSelectOption {
31453145
/**
3146-
* If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
3146+
* Text that is placed underneath the option text to provide additional details about the option.
3147+
*/
3148+
"description"?: string;
3149+
/**
3150+
* If `true`, the user cannot interact with the select option.
31473151
* @default false
31483152
*/
31493153
"disabled": boolean;
3154+
/**
3155+
* How to pack the label and the option's selection control within a line. `"start"`: The label and radio will appear on the left in LTR and on the right in RTL. `"end"`: The label and radio will appear on the right in LTR and on the left in RTL. `"space-between"`: The label and radio will appear on opposite ends of the line with space between the two elements. Applies to the `alert`, `popover`, and `modal` interfaces, but has no visible effect on radio options in `popover` or `modal` on `md` (the radio control is hidden there). When unset, the interface picks a default based on mode and control type.
3156+
*/
3157+
"justify"?: 'start' | 'end' | 'space-between';
3158+
/**
3159+
* Where the label is placed relative to the option's selection control (radio circle or checkbox box) when the option is rendered in an `alert`, `popover`, or `modal` interface. `"start"`: The label will appear to the left of the radio in LTR and to the right in RTL. `"end"`: The label will appear to the right of the radio in LTR and to the left in RTL. Applies to the `alert`, `popover`, and `modal` interfaces, but has no visible effect on radio options in `popover` or `modal` on `md` (the radio control is hidden there). When unset, the interface picks a default based on mode and control type.
3160+
*/
3161+
"labelPlacement"?: 'start' | 'end';
3162+
/**
3163+
* The mode determines the platform behaviors of the component.
3164+
*/
3165+
"mode"?: "ios" | "md";
31503166
/**
31513167
* The text value of the option.
31523168
*/
@@ -8345,10 +8361,26 @@ declare namespace LocalJSX {
83458361
}
83468362
interface IonSelectOption {
83478363
/**
8348-
* If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
8364+
* Text that is placed underneath the option text to provide additional details about the option.
8365+
*/
8366+
"description"?: string;
8367+
/**
8368+
* If `true`, the user cannot interact with the select option.
83498369
* @default false
83508370
*/
83518371
"disabled"?: boolean;
8372+
/**
8373+
* How to pack the label and the option's selection control within a line. `"start"`: The label and radio will appear on the left in LTR and on the right in RTL. `"end"`: The label and radio will appear on the right in LTR and on the left in RTL. `"space-between"`: The label and radio will appear on opposite ends of the line with space between the two elements. Applies to the `alert`, `popover`, and `modal` interfaces, but has no visible effect on radio options in `popover` or `modal` on `md` (the radio control is hidden there). When unset, the interface picks a default based on mode and control type.
8374+
*/
8375+
"justify"?: 'start' | 'end' | 'space-between';
8376+
/**
8377+
* Where the label is placed relative to the option's selection control (radio circle or checkbox box) when the option is rendered in an `alert`, `popover`, or `modal` interface. `"start"`: The label will appear to the left of the radio in LTR and to the right in RTL. `"end"`: The label will appear to the right of the radio in LTR and to the left in RTL. Applies to the `alert`, `popover`, and `modal` interfaces, but has no visible effect on radio options in `popover` or `modal` on `md` (the radio control is hidden there). When unset, the interface picks a default based on mode and control type.
8378+
*/
8379+
"labelPlacement"?: 'start' | 'end';
8380+
/**
8381+
* The mode determines the platform behaviors of the component.
8382+
*/
8383+
"mode"?: "ios" | "md";
83528384
/**
83538385
* The text value of the option.
83548386
*/
@@ -9528,6 +9560,9 @@ declare namespace LocalJSX {
95289560
interface IonSelectOptionAttributes {
95299561
"disabled": boolean;
95309562
"value": string;
9563+
"description": string;
9564+
"labelPlacement": 'start' | 'end';
9565+
"justify": 'start' | 'end' | 'space-between';
95319566
}
95329567
interface IonSelectPopoverAttributes {
95339568
"header": string;

core/src/components/action-sheet/action-sheet.ios.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@use "../select-option/select-option.ios.overlay";
12
@import "./action-sheet";
23
@import "./action-sheet.ios.vars";
34

core/src/components/action-sheet/action-sheet.md.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
@use "../select-option/select-option.md.overlay";
12
@import "./action-sheet";
23
@import "./action-sheet.md.vars";
34

4-
// Material Design Action Sheet Title
5+
// Material Design Action Sheet
56
// -----------------------------------------
67

78
:host {

core/src/components/action-sheet/action-sheet.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
opacity: 0.4;
110110
}
111111

112+
.action-sheet-button:disabled ion-icon {
113+
color: currentColor;
114+
}
115+
112116
.action-sheet-button-inner {
113117
display: flex;
114118

@@ -213,14 +217,20 @@
213217
// Action Sheet: Focused
214218
// --------------------------------------------------
215219

216-
.action-sheet-button.ion-focused {
220+
.action-sheet-button.ion-focused:not(.ion-activated) {
217221
color: var(--button-color-focused);
218222

219223
&::after {
220224
background: var(--button-background-focused);
221225

222226
opacity: var(--button-background-focused-opacity);
223227
}
228+
229+
&.action-sheet-selected::after {
230+
background: var(--button-background-focused, var(--button-background-selected));
231+
232+
opacity: var(--button-background-focused-opacity, var(--button-background-selected-opacity));
233+
}
224234
}
225235

226236
// Action Sheet: Hover

core/src/components/action-sheet/action-sheet.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import {
1717
safeCall,
1818
setOverlayId,
1919
} from '@utils/overlays';
20+
import { renderOptionLabel } from '@utils/select-option-render';
2021
import { getClassMap } from '@utils/theme';
2122

2223
import { getIonMode } from '../../global/ionic-global';
2324
import type { AnimationBuilder, CssClassMap, FrameworkDelegate, OverlayInterface } from '../../interface';
2425
import type { OverlayEventDetail } from '../../utils/overlays-interface';
26+
import type { SelectActionSheetButton } from '../select/select-interface';
2527

2628
import type { ActionSheetButton } from './action-sheet-interface';
2729
import { iosEnterAnimation } from './animations/ios.enter';
@@ -562,6 +564,21 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
562564
htmlAttrs['aria-checked'] = isActiveRadio ? 'true' : 'false';
563565
}
564566

567+
/**
568+
* Cast to `SelectActionSheetButton` to access rich content
569+
* fields (`startContent`, `endContent`, `description`)
570+
* that are passed through from `ion-select` but not
571+
* part of the public `ActionSheetButton` interface.
572+
*/
573+
const richButton = b as SelectActionSheetButton;
574+
const optionLabelOptions = {
575+
id: buttonId,
576+
label: richButton.text,
577+
startContent: richButton.startContent,
578+
endContent: richButton.endContent,
579+
description: richButton.description,
580+
};
581+
565582
return (
566583
<button
567584
{...htmlAttrs}
@@ -583,7 +600,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
583600
>
584601
<span class="action-sheet-button-inner">
585602
{b.icon && <ion-icon icon={b.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />}
586-
{b.text}
603+
{renderOptionLabel(optionLabelOptions, 'action-sheet-button-label', true)}
587604
</span>
588605
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
589606
</button>

core/src/components/action-sheet/test/basic/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
.my-color-class {
4747
--background: #292929;
4848
--button-background-selected: #222222;
49+
--button-background-activated: #393838;
50+
--button-background-activated-opacity: 1;
4951

5052
--color: #dfdfdf;
5153
--button-color: #dfdfdf;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { configs, test } from '@utils/test/playwright';
2+
3+
import { ActionSheetFixture } from '../basic/fixture';
4+
5+
/**
6+
* This behavior does not vary across directions.
7+
*/
8+
configs({ directions: ['ltr'], modes: ['ios', 'md'] }).forEach(({ config, screenshot, title }) => {
9+
test.describe(title('action sheet: states'), () => {
10+
/**
11+
* `(any-hover: hover)` evaluates to "none" in all three mobile-emulated
12+
* projects, suppressing the hover rules:
13+
*
14+
* - Chromium and WebKit suppress it because of hasTouch and isMobile.
15+
* - Headless Firefox doesn't detect input devices and reports no hover
16+
* capability regardless of context options, so override it via
17+
* launchOptions.firefoxUserPrefs. Bit values: 4 = FINE (mouse),
18+
* 8 = HOVER, 12 = FINE | HOVER.
19+
*
20+
* Viewport, userAgent, and scale factor remain mobile-sized.
21+
*/
22+
test.use({
23+
hasTouch: false,
24+
isMobile: false,
25+
});
26+
27+
test('should render all button states', async ({ page }) => {
28+
await page.goto(`/src/components/action-sheet/test/states`, config);
29+
30+
const actionSheetFixture = new ActionSheetFixture(page, screenshot);
31+
32+
await actionSheetFixture.open('#basic');
33+
34+
const defaultButton = page.locator('ion-action-sheet button.action-sheet-button').first();
35+
await defaultButton.hover();
36+
37+
await actionSheetFixture.screenshot('states');
38+
});
39+
});
40+
});
Loading
Loading

0 commit comments

Comments
 (0)