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:
Liam DeBeasi
2023-12-19 16:48:25 -05:00
committed by GitHub
parent 2c20a6bdb4
commit e76d72989a
15 changed files with 91 additions and 4 deletions

View File

@ -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

View File

@ -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;
} }

View File

@ -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;

View File

@ -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};
} }

View File

@ -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 {

View File

@ -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),
}; };

View File

@ -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'));
});
});
});

View File

@ -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);
});
});

View File

@ -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;
} }