mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-09 16:16:41 +08:00
feat(action-sheet): add disabled button (#28723)
Issue number: N/A --------- <!-- 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? <!-- Please describe the current behavior that you are modifying. --> Action sheet buttons cannot be disabled. This behavior exists in iOS 17. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Action sheet buttons can be disabled ## 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/.github/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. --> --------- Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
@ -60,6 +60,7 @@ ion-action-sheet,css-prop,--button-background-selected
|
|||||||
ion-action-sheet,css-prop,--button-background-selected-opacity
|
ion-action-sheet,css-prop,--button-background-selected-opacity
|
||||||
ion-action-sheet,css-prop,--button-color
|
ion-action-sheet,css-prop,--button-color
|
||||||
ion-action-sheet,css-prop,--button-color-activated
|
ion-action-sheet,css-prop,--button-color-activated
|
||||||
|
ion-action-sheet,css-prop,--button-color-disabled
|
||||||
ion-action-sheet,css-prop,--button-color-focused
|
ion-action-sheet,css-prop,--button-color-focused
|
||||||
ion-action-sheet,css-prop,--button-color-hover
|
ion-action-sheet,css-prop,--button-color-hover
|
||||||
ion-action-sheet,css-prop,--button-color-selected
|
ion-action-sheet,css-prop,--button-color-selected
|
||||||
|
|||||||
@ -26,4 +26,12 @@ export interface ActionSheetButton<T = any> {
|
|||||||
htmlAttributes?: { [key: string]: any };
|
htmlAttributes?: { [key: string]: any };
|
||||||
handler?: () => boolean | void | Promise<boolean | void>;
|
handler?: () => boolean | void | Promise<boolean | void>;
|
||||||
data?: T;
|
data?: T;
|
||||||
|
/**
|
||||||
|
* When `disabled` is `true` the action
|
||||||
|
* sheet button will not be interactive. Note
|
||||||
|
* that buttons with a 'cancel' role cannot
|
||||||
|
* be disabled as that would make it difficult for
|
||||||
|
* users to dismiss the action sheet.
|
||||||
|
*/
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
--button-background-selected: #{$action-sheet-ios-button-background-selected};
|
--button-background-selected: #{$action-sheet-ios-button-background-selected};
|
||||||
--button-background-selected-opacity: 1;
|
--button-background-selected-opacity: 1;
|
||||||
--button-color: #{$action-sheet-ios-button-text-color};
|
--button-color: #{$action-sheet-ios-button-text-color};
|
||||||
|
--button-color-disabled: #{$text-color-step-150};
|
||||||
--color: #{$action-sheet-ios-title-color};
|
--color: #{$action-sheet-ios-title-color};
|
||||||
|
|
||||||
text-align: $action-sheet-ios-text-align;
|
text-align: $action-sheet-ios-text-align;
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
--button-background-focused: currentColor;
|
--button-background-focused: currentColor;
|
||||||
--button-background-focused-opacity: .12;
|
--button-background-focused-opacity: .12;
|
||||||
--button-color: #{$action-sheet-md-button-text-color};
|
--button-color: #{$action-sheet-md-button-text-color};
|
||||||
|
--button-color-disabled: var(--button-color);
|
||||||
--color: #{$action-sheet-md-title-color};
|
--color: #{$action-sheet-md-title-color};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
* @prop --button-color-hover: Color of the action sheet button on hover
|
* @prop --button-color-hover: Color of the action sheet button on hover
|
||||||
* @prop --button-color-focused: Color of the action sheet button when tabbed to
|
* @prop --button-color-focused: Color of the action sheet button when tabbed to
|
||||||
* @prop --button-color-selected: Color of the selected action sheet button
|
* @prop --button-color-selected: Color of the selected action sheet button
|
||||||
|
* @prop --button-color-disabled: Color of the selected action sheet button when disabled
|
||||||
*/
|
*/
|
||||||
--color: initial;
|
--color: initial;
|
||||||
--button-color-activated: var(--button-color);
|
--button-color-activated: var(--button-color);
|
||||||
@ -102,6 +103,12 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-sheet-button:disabled {
|
||||||
|
color: var(--button-color-disabled);
|
||||||
|
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
.action-sheet-button-inner {
|
.action-sheet-button-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@ -220,7 +227,7 @@
|
|||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
@media (any-hover: hover) {
|
@media (any-hover: hover) {
|
||||||
.action-sheet-button:hover {
|
.action-sheet-button:not(:disabled):hover {
|
||||||
color: var(--button-color-hover);
|
color: var(--button-color-hover);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
|
|||||||
@ -403,6 +403,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
|||||||
id={b.id}
|
id={b.id}
|
||||||
class={buttonClass(b)}
|
class={buttonClass(b)}
|
||||||
onClick={() => this.buttonClick(b)}
|
onClick={() => this.buttonClick(b)}
|
||||||
|
disabled={b.disabled}
|
||||||
>
|
>
|
||||||
<span class="action-sheet-button-inner">
|
<span class="action-sheet-button-inner">
|
||||||
{b.icon && <ion-icon icon={b.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />}
|
{b.icon && <ion-icon icon={b.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />}
|
||||||
@ -415,6 +416,11 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
{cancelButton && (
|
{cancelButton && (
|
||||||
<div class="action-sheet-group action-sheet-group-cancel">
|
<div class="action-sheet-group action-sheet-group-cancel">
|
||||||
|
{/*
|
||||||
|
Cancel buttons intentionally do not
|
||||||
|
receive a disabled state here as we should
|
||||||
|
not make it difficult to dismiss the overlay.
|
||||||
|
*/}
|
||||||
<button
|
<button
|
||||||
{...cancelButton.htmlAttributes}
|
{...cancelButton.htmlAttributes}
|
||||||
type="button"
|
type="button"
|
||||||
@ -443,8 +449,8 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
|||||||
const buttonClass = (button: ActionSheetButton): CssClassMap => {
|
const buttonClass = (button: ActionSheetButton): CssClassMap => {
|
||||||
return {
|
return {
|
||||||
'action-sheet-button': true,
|
'action-sheet-button': true,
|
||||||
'ion-activatable': true,
|
'ion-activatable': !button.disabled,
|
||||||
'ion-focusable': true,
|
'ion-focusable': !button.disabled,
|
||||||
[`action-sheet-${button.role}`]: button.role !== undefined,
|
[`action-sheet-${button.role}`]: button.role !== undefined,
|
||||||
...getClassMap(button.cssClass),
|
...getClassMap(button.cssClass),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
import { configs, test } from '@utils/test/playwright';
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
import { ActionSheetFixture } from './fixture';
|
import { ActionSheetFixture } from './fixture';
|
||||||
@ -40,3 +41,28 @@ configs().forEach(({ config, screenshot, title }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||||
|
test.describe(title('action sheet: disabled buttons'), () => {
|
||||||
|
test('should render disabled button', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-action-sheet></ion-action-sheet>
|
||||||
|
<script>
|
||||||
|
const actionSheet = document.querySelector('ion-action-sheet');
|
||||||
|
actionSheet.buttons = [
|
||||||
|
{ text: 'Disabled', disabled: true }
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const actionSheet = page.locator('ion-action-sheet');
|
||||||
|
|
||||||
|
await actionSheet.evaluate((el: HTMLIonActionSheetElement) => el.present());
|
||||||
|
|
||||||
|
await expect(actionSheet).toHaveScreenshot(screenshot('action-sheet-disabled'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@ -22,3 +22,40 @@ describe('action sheet: htmlAttributes inheritance', () => {
|
|||||||
await expect(actionSheet.getAttribute('data-testid')).toBe('basic-action-sheet');
|
await expect(actionSheet.getAttribute('data-testid')).toBe('basic-action-sheet');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('action sheet: disabled buttons', () => {
|
||||||
|
it('regular button should be disabled', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [ActionSheet],
|
||||||
|
template: () => (
|
||||||
|
<ion-action-sheet buttons={[{ text: 'cancel', disabled: true }]} overlayIndex={1}></ion-action-sheet>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionSheet = page.body.querySelector('ion-action-sheet')!;
|
||||||
|
|
||||||
|
await actionSheet.present();
|
||||||
|
|
||||||
|
const button = actionSheet.querySelector<HTMLButtonElement>('.action-sheet-button')!;
|
||||||
|
await expect(button.hasAttribute('disabled')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cancel button should not be disabled', async () => {
|
||||||
|
const page = await newSpecPage({
|
||||||
|
components: [ActionSheet],
|
||||||
|
template: () => (
|
||||||
|
<ion-action-sheet
|
||||||
|
buttons={[{ text: 'cancel', role: 'cancel', disabled: true }]}
|
||||||
|
overlayIndex={1}
|
||||||
|
></ion-action-sheet>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const actionSheet = page.body.querySelector('ion-action-sheet')!;
|
||||||
|
|
||||||
|
await actionSheet.present();
|
||||||
|
|
||||||
|
const cancelButton = actionSheet.querySelector<HTMLButtonElement>('.action-sheet-cancel')!;
|
||||||
|
await expect(cancelButton.hasAttribute('disabled')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export const createButtonActiveGesture = (el: HTMLElement, isButton: (refEl: HTM
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = document.elementFromPoint(x, y) as HTMLElement | null;
|
const target = document.elementFromPoint(x, y) as HTMLElement | null;
|
||||||
if (!target || !isButton(target)) {
|
if (!target || !isButton(target) || (target as HTMLButtonElement).disabled) {
|
||||||
clearActiveButton();
|
clearActiveButton();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user