Skip to content

Commit

Permalink
Merge 246e2d7 into 39410b2
Browse files Browse the repository at this point in the history
  • Loading branch information
louis-bompart committed Jul 24, 2020
2 parents 39410b2 + 246e2d7 commit 2313987
Show file tree
Hide file tree
Showing 10 changed files with 504 additions and 56 deletions.
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: $activated-color;
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,
ToggleUnactivatedState as UnactivatedState,
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: UnactivatedState;
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 UnactivatedState(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(ToggleActionButton);
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, ToggleUnactivatedState, 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 ToggleUnactivatedState(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 ToggleUnactivatedState implements IStatefulActionButtonOptionsWithIcon {
public readonly name = 'ToggleUnactivatedState';
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;
}
}
50 changes: 50 additions & 0 deletions tests/components/ActionButton/DisableableButton.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DisabledState } from '../../../src/components/ActionButton/DisableableButton';

describe('DisabledState', () => {
let testElement: HTMLElement;
let testSubject: DisabledState;
let fakeStatefulActionButton: { element: HTMLElement };

beforeEach(() => {
testElement = document.createElement('div');
fakeStatefulActionButton = { element: testElement };
testSubject = new DisabledState({ options: { disabledIcon: 'someSvgIcon', disabledTooltip: 'someTooltip' } });
});

describe('constructor', () => {
it('should use the icon and tooltip from the option of the disabledButton', () => {
expect(testSubject.icon).toBe('someSvgIcon');
expect(testSubject.tooltip).toBe('someTooltip');
});
});

describe('onStateEntry', () => {
beforeEach(() => {
testSubject.onStateEntry.apply(fakeStatefulActionButton);
});

it('should add coveo-actionbutton-disabled to the classlist on this.element of the caller', () => {
expect(testElement.classList.value).toBe('coveo-actionbutton-disabled');
});

it('should add the attribute disabled to this.element of the caller', () => {
expect(testElement.hasAttribute('disabled')).toBeTrue();
});
});

describe('onStateExit', () => {
beforeEach(() => {
testElement.classList.value = 'coveo-actionbutton-disabled';
testElement.setAttribute('disabled', '');
testSubject.onStateExit.apply(fakeStatefulActionButton);
});

it('should remove coveo-actionbutton-disabled to the classlist on this.element of the caller', () => {
expect(testElement.classList.value).toBe('');
});

it('should remove the attribute disabled to this.element of the caller', () => {
expect(testElement.hasAttribute('disabled')).toBeFalse();
});
});
});
Loading

0 comments on commit 2313987

Please sign in to comment.