Skip to content

Commit

Permalink
Feat/SFINT-3327: Add DisableableToggleActionButton (#85)
Browse files Browse the repository at this point in the history
* feat(SFINT-3327): Add enable and disable support for ActionButton.
 * Add enable method override. UT [x]
 * Add disable method override. UT [x]
 * Modify click event listener behavior to process the click only if enabled.

* feat(SFINT-3327): Add disable styling.
- [A] Section for disabled button in dev pages
- [A] Styling for disabled buttons
- [M] Disable/Enable now affect the disabled attributes of the element directly too

* feat(SFINT-3327): Biggo refacto

* feat(SFINT-3327): clean html

* feat(SFINT-3327): finish cleanup of actionbutton

* chore(SFINT-3327): Buncho o' doc and small refacto

* chore(SFINT-3327): Simplify

* feat(SFINT-3327): Improve typings

* test(SFINT-3327): UT

* feat(SFINT-3327): Add loggingName for states

* feat(SFINT-3327): More logs and checks

* review

* review

* review

* empty commit for CI unstuck

* empty commit for CI unstuck-lint

* feat(SFINT-3327): DisableableToggleActionButton

* feat(SFINT-3327): Adapt logging name and new interfaces

* ongoin'

* cleaning + specs

* test states

* test(SFINT-3327): Complete UT

* feat(SFINT-3327): Ensure button is exported properly + page for test

* unactivated->deactivated
  • Loading branch information
louis-bompart authored Jul 24, 2020
1 parent 39410b2 commit 3c3a73c
Show file tree
Hide file tree
Showing 13 changed files with 763 additions and 56 deletions.
21 changes: 21 additions & 0 deletions pages/toggle_action_button.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@
<div class="CoveoSearchbox" data-enable-omnibox="true"></div>
</div>

<div>
<h2>Disabled Disableable Toggle Button</h2>
<br />
<button
id="disabled-button"
class="CoveoDisableableToggleActionButton"
data-activate-icon='&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.997 78.997" height="298.571" width="298.571"&gt;&lt;circle r="39.499" cy="120.385" cx="84.1" transform="translate(-44.601 -80.887)"/&gt;&lt;/svg&gt;'
data-disabled-icon='&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.997 78.997" height="298.571" width="298.571"&gt;&lt;circle r="39.499" cy="120.385" cx="84.1" transform="translate(-44.601 -80.887)"/&gt;&lt;/svg&gt;'
data-disabled-tooltip="Disabled feature"
data-activate-tooltip="Activate feature"
data-deactivate-tooltip="Deactivate feature"
data-deactivate-icon='&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.997 78.997" height="298.571" width="298.571"&gt;&lt;path d="M39.434 0A39.499 39.499 0 000 39.498a39.499 39.499 0 0039.498 39.5 39.499 39.499 0 0039.5-39.5A39.499 39.499 0 0039.497 0a39.499 39.499 0 00-.064 0zm.59 7.273a31.948 31.948 0 0131.948 31.949A31.948 31.948 0 0140.023 71.17 31.948 31.948 0 018.075 39.222 31.948 31.948 0 0140.023 7.273z"/&gt;&lt;/svg&gt;'
></button>
<button
title="Disable Coveo button"
onclick="Coveo.get(document.getElementById('disabled-button'), 'DisableableToggleActionButton').disable()"
>
Disable the button above
</button>
</div>

<div>
<h2>Toggle Button</h2>
<br />
Expand Down
1 change: 1 addition & 0 deletions src/Index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

export { ActionButton } from './components/ActionButton/ActionButton';
export { ToggleActionButton } from './components/ActionButton/ToggleActionButton';
export { DisableableToggleActionButton } from './components/ActionButton/DisableableToggleActionButton';
export { AttachResult } from './components/AttachResult/AttachResult';
export { UserActivity } from './components/UserActions/UserActivity';
export { UserActions } from './components/UserActions/UserActions';
Expand Down
15 changes: 15 additions & 0 deletions src/components/ActionButton/DisableableActionButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import './ActionButton.scss';
@import '../../sass/Variables.scss';

$disable-color: $primary-color-light;

button.CoveoActionButton.coveo-actionbutton.coveo-actionbutton-disabled {
background-color: $primary-color-lightest;
border-color: $disable-color;

.coveo-actionbutton_icon {
svg {
fill: $disable-color;
}
}
}
34 changes: 34 additions & 0 deletions src/components/ActionButton/DisableableButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { StatefulActionButton, IStatefulActionButtonOptionsWithIcon } from './StatefulActionButton';

export interface IDisableableButtonOptions {
disabledIcon: string;
disabledTooltip: string;
}

export interface IDisableableButton {
options: IDisableableButtonOptions;
}

export class DisabledState implements IStatefulActionButtonOptionsWithIcon {
static DISABLED_CLASS_NAME = 'coveo-actionbutton-disabled';
public readonly onStateEntry: (this: StatefulActionButton) => void;
public readonly onStateExit: (this: StatefulActionButton) => void;
public readonly click: () => void;
public readonly icon: string;
public readonly tooltip: string;
public readonly name = 'DisabledState';

constructor(disabledButton: IDisableableButton) {
this.onStateEntry = function () {
this.element.classList.add(DisabledState.DISABLED_CLASS_NAME);
this.element.setAttribute('disabled', '');
};
this.onStateExit = function () {
this.element.classList.remove(DisabledState.DISABLED_CLASS_NAME);
this.element.removeAttribute('disabled');
};
this.click = () => {};
this.icon = disabledButton.options.disabledIcon;
this.tooltip = disabledButton.options.disabledTooltip;
}
}
120 changes: 120 additions & 0 deletions src/components/ActionButton/DisableableToggleActionButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { ComponentOptions, IResultsComponentBindings, Component, Initialization } from 'coveo-search-ui';
import { StatefulActionButton } from './StatefulActionButton';
import {
ToggleActivatedState as ActivatedState,
ToggleDeactivatedState as DeactivatedState,
IToggleableButton,
IToggleableButtonOptions,
} from './ToggleableButton';
import { IDisableableButton, IDisableableButtonOptions, DisabledState } from './DisableableButton';
import { ToggleActionButton } from './ToggleActionButton';

export interface IDisableableToggleActionButtonOptions extends IToggleableButtonOptions, IDisableableButtonOptions {}

export class DisableableToggleActionButton extends Component implements IToggleableButton, IDisableableButton {
static ID = 'DisableableToggleActionButton';
static ACTIVATED_CLASS_NAME = 'coveo-toggleactionbutton-activated';

private innerStatefulActionButton: StatefulActionButton;
private activatedState: ActivatedState;
private deactivatedState: DeactivatedState;
private disabledState: DisabledState;

static options: IDisableableToggleActionButtonOptions = {
...ToggleActionButton.options,
disabledTooltip: ComponentOptions.buildStringOption(),
disabledIcon: ComponentOptions.buildStringOption(),
};

constructor(public element: HTMLElement, public options: IDisableableToggleActionButtonOptions, public bindings?: IResultsComponentBindings) {
super(element, DisableableToggleActionButton.ID, bindings);
this.options = ComponentOptions.initComponentOptions(element, DisableableToggleActionButton, options);

this.createInnerButton(bindings);
}

/**
* Indicates whether the toggle button is in the activated state.
*/
public isActivated(): boolean {
return this.innerStatefulActionButton.getCurrentState() === this.activatedState;
}

/**
* Indicates whether the disableable toggle button is in the disable state.
*/
public isDisabled(): boolean {
return this.innerStatefulActionButton.getCurrentState() === this.disabledState;
}

/**
* Sets the toggle button to the specified state.
* @param activated Whether the button is activated.
*/
public setActivated(activated: boolean): void {
if (this.isDisabled() && !activated) {
this.innerStatefulActionButton.switchTo(this.deactivatedState);
}
if (!this.isDisabled() && activated !== this.isActivated()) {
this.innerStatefulActionButton.switchTo(activated ? this.activatedState : this.deactivatedState);
}
}

public setEnabled(enabled: boolean): void {
if (enabled) {
this.enable();
} else {
this.disable();
}
}

public disable(): void {
if (this.isDisabled()) {
return;
}
if (this.isActivated()) {
this.innerStatefulActionButton.switchTo(this.deactivatedState);
}
this.innerStatefulActionButton.switchTo(this.disabledState);
}

public enable(): void {
if (this.isDisabled()) {
this.innerStatefulActionButton.switchTo(this.deactivatedState);
}
}

public onClick(): void {
if (this.isDisabled()) {
return;
}
this.setActivated(!this.isActivated());

if (this.options.click) {
this.options.click();
}
}

private createInnerButton(bindings?: IResultsComponentBindings): void {
this.deactivatedState = new DeactivatedState(this);
this.disabledState = new DisabledState(this);
this.activatedState = new ActivatedState(this);

this.innerStatefulActionButton = new StatefulActionButton(
this.element,
{
initialState: this.deactivatedState,
states: [this.deactivatedState, this.activatedState, this.disabledState],
allowedTransitions: [
{ from: this.deactivatedState, to: this.disabledState },
{ from: this.disabledState, to: this.deactivatedState },
{ from: this.deactivatedState, to: this.activatedState },
{ from: this.activatedState, to: this.deactivatedState },
],
},
bindings
);
}
}

Initialization.registerAutoCreateComponent(DisableableToggleActionButton);
4 changes: 3 additions & 1 deletion src/components/ActionButton/StatefulActionButton.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { IResultsComponentBindings } from 'coveo-search-ui';
import { ActionButton, ActionButtonOptions } from './ActionButton';
import { ActionButton, ActionButtonOptions, IActionButtonOptionsWithTitle, IActionButtonOptionsWithIcon } from './ActionButton';

/**
* Represent a state that can be used by a StatefulActionButton.
*/
export type StatefulActionButtonState = ActionButtonOptions & IStateOptions;
export interface IStatefulActionButtonOptionsWithTitle extends IActionButtonOptionsWithTitle, IStateOptions {}
export interface IStatefulActionButtonOptionsWithIcon extends IActionButtonOptionsWithIcon, IStateOptions {}

export interface IStateOptions {
/**
Expand Down
59 changes: 7 additions & 52 deletions src/components/ActionButton/ToggleActionButton.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,12 @@
import { ComponentOptions, IResultsComponentBindings, Component, Initialization } from 'coveo-search-ui';
import { ToggleActivatedState, ToggleDeactivatedState, IToggleableButton, IToggleableButtonOptions } from './ToggleableButton';
import { StatefulActionButtonState, StatefulActionButton } from './StatefulActionButton';

export interface IToggleActionButtonOptions {
activateIcon: string;
activateTooltip: string;
deactivateIcon: string;
deactivateTooltip: string;
click?: () => void;
activate?: () => void;
deactivate?: () => void;
}

export class ToggleActionButton extends Component {
/**
* Create the deactivated state for a given ToggleActionButton
* @param button {ToggleActionButton}
*/
static generateDeactivatedStateInstance(button: ToggleActionButton): StatefulActionButtonState {
return {
name: 'DeactivatedState',
icon: button.options.activateIcon,
tooltip: button.options.activateTooltip,
click: () => button.onClick(),
};
}

/**
* Create the activated state for a given ToggleActionButton
* @param button {ToggleActionButton}
*/
static generateActivatedStateInstance(button: ToggleActionButton): StatefulActionButtonState {
return {
onStateEntry: function () {
this.element.classList.add(ToggleActionButton.ACTIVATED_CLASS_NAME);
this.element.setAttribute('aria-pressed', 'true');
button.options.activate?.apply(button);
},
onStateExit: function () {
this.element.classList.remove(ToggleActionButton.ACTIVATED_CLASS_NAME);
this.element.setAttribute('aria-pressed', 'false');
button.options.deactivate?.apply(button);
},
name: 'ActivatedState',
click: () => button.onClick(),
icon: button.options.deactivateIcon,
tooltip: button.options.deactivateTooltip,
};
}

export class ToggleActionButton extends Component implements IToggleableButton {
static ID = 'ToggleActionButton';
static ACTIVATED_CLASS_NAME = 'coveo-toggleactionbutton-activated';

static options: IToggleActionButtonOptions = {
static options: IToggleableButtonOptions = {
/**
* Specifies the button SVG icon displayed to activate the button.
* Note: The SVG markup has to be HTML encoded when set using the HTML attributes.
Expand Down Expand Up @@ -145,7 +100,7 @@ export class ToggleActionButton extends Component {
private activatedState: StatefulActionButtonState;
private deactivatedState: StatefulActionButtonState;

constructor(public element: HTMLElement, public options: IToggleActionButtonOptions, public bindings?: IResultsComponentBindings) {
constructor(public element: HTMLElement, public options: IToggleableButtonOptions, public bindings?: IResultsComponentBindings) {
super(element, ToggleActionButton.ID, bindings);
this.options = ComponentOptions.initComponentOptions(element, ToggleActionButton, options);

Expand All @@ -169,7 +124,7 @@ export class ToggleActionButton extends Component {
}
}

protected onClick(): void {
public onClick(): void {
this.setActivated(!this.isActivated());

if (this.options.click) {
Expand All @@ -178,8 +133,8 @@ export class ToggleActionButton extends Component {
}

private createInnerButton(bindings?: IResultsComponentBindings): void {
this.activatedState = ToggleActionButton.generateActivatedStateInstance(this);
this.deactivatedState = ToggleActionButton.generateDeactivatedStateInstance(this);
this.deactivatedState = new ToggleDeactivatedState(this);
this.activatedState = new ToggleActivatedState(this);

this.innerStatefulActionButton = new StatefulActionButton(
this.element,
Expand Down
53 changes: 53 additions & 0 deletions src/components/ActionButton/ToggleableButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { StatefulActionButton, IStatefulActionButtonOptionsWithIcon } from './StatefulActionButton';

export interface IToggleableButtonOptions {
activateIcon: string;
activateTooltip: string;
deactivateIcon: string;
deactivateTooltip: string;
click?: () => void;
activate?: () => void;
deactivate?: () => void;
}

export interface IToggleableButton {
options: IToggleableButtonOptions;
onClick: () => void;
}

export class ToggleDeactivatedState implements IStatefulActionButtonOptionsWithIcon {
public readonly name = 'ToggleDeactivatedState';
public readonly icon: string;
public readonly tooltip: string;
public readonly click: { (): void; (): void; (): void };
constructor(toggleableButton: IToggleableButton) {
this.icon = toggleableButton.options.activateIcon;
this.tooltip = toggleableButton.options.activateTooltip;
this.click = () => toggleableButton.onClick();
}
}

export class ToggleActivatedState implements IStatefulActionButtonOptionsWithIcon {
static ACTIVATED_CLASS_NAME = 'coveo-toggleactionbutton-activated';
public readonly name = 'ToggleActivatedState';
public readonly onStateEntry: (this: StatefulActionButton) => void;
public readonly onStateExit: (this: StatefulActionButton) => void;
public readonly click: () => void;
public readonly icon: string;
public readonly tooltip: string;
constructor(toggleableButton: IToggleableButton) {
this.onStateEntry = function () {
this.element.classList.add(ToggleActivatedState.ACTIVATED_CLASS_NAME);
this.element.setAttribute('aria-pressed', 'true');
toggleableButton.options.activate?.apply(toggleableButton);
};
this.onStateExit = function () {
this.element.classList.remove(ToggleActivatedState.ACTIVATED_CLASS_NAME);
this.element.setAttribute('aria-pressed', 'false');
toggleableButton.options.deactivate?.apply(toggleableButton);
};
this.click = () => toggleableButton.onClick();
this.icon = toggleableButton.options.deactivateIcon;
this.tooltip = toggleableButton.options.deactivateTooltip;
}
}
1 change: 1 addition & 0 deletions src/sass/Index.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import '../components/ActionButton/ActionButton.scss';
@import '../components/ActionButton/ToggleActionButton.scss';
@import '../components/ActionButton/DisableableActionButton.scss';
@import '../components/AttachResult/AttachResult.scss';
@import '../components/UserActions/UserActions.scss';
@import '../components/ViewedByCustomer/ViewedByCustomer.scss';
Expand Down
Loading

0 comments on commit 3c3a73c

Please sign in to comment.