Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[@next][button-area] refactor to use tokens, state-layer, and focus-indicator #489

Merged
merged 23 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
66122fb
chore(stack): refactored the stack component to use tokens
nickonometry Jan 31, 2024
d9cfb17
chore(stack): added another variation/example to the EJS page
nickonometry Feb 16, 2024
b3a4562
Merge branch 'next' of https://github.com/tyler-technologies-oss/forg…
nickonometry Feb 16, 2024
cd49ce4
feat(stack): added a justify property to the stack component and crea…
nickonometry Feb 16, 2024
07be525
chore: removed unused host selector from stack SCSS
nickonometry Feb 16, 2024
f496f14
chore: visual bug fixes to the close icon button
nickonometry Feb 21, 2024
d08d509
feat(button-area): add tokens, state-layer, focus-indicator
derekmoss Feb 28, 2024
fa1f22a
chore: lint
derekmoss Feb 28, 2024
da7c5d2
Merge branch 'next' into next-button-area-refactor
derekmoss Feb 28, 2024
a3ae64a
chore: remove unused tokens
derekmoss Feb 28, 2024
b0fe816
chore: rename content slot listeners
derekmoss Feb 28, 2024
d6fc9c7
fix: revert defered listener change
derekmoss Feb 28, 2024
09dfc3a
chore: remove whitespace
derekmoss Feb 28, 2024
ef7a2b3
fix: remove appearance styles
derekmoss Mar 4, 2024
cb73c49
chore: switch to disabled attr style
derekmoss Mar 4, 2024
08bedd0
chore: remove demo delta
derekmoss Mar 4, 2024
6cd7f07
chore: remove BEM
derekmoss Mar 4, 2024
9ad848c
chore: remove unused doc comment
derekmoss Mar 4, 2024
3eaf20c
chore: remove webkit tap color when used with card or expansion panel…
DRiFTy17 Mar 5, 2024
9013468
chore: remove unused code
derekmoss Mar 6, 2024
9b10e95
chore: add more test coverage
derekmoss Mar 6, 2024
7c1208f
Merge branch 'next-button-area-refactor' of github.com:tyler-technolo…
derekmoss Mar 6, 2024
d5be569
Merge branch 'next' into next-button-area-refactor
derekmoss Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/lib/button-area/_core.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@use './token-utils' as *;

@forward './token-utils';

@mixin host {
display: block;
position: relative;
}

@mixin base {
position: relative;
overflow: hidden;
}

@mixin enabled {
cursor: #{token(cursor)};
}
@mixin disabled {
cursor: #{token(disabled-cursor)};
}
42 changes: 0 additions & 42 deletions src/lib/button-area/_mixins.scss

This file was deleted.

25 changes: 25 additions & 0 deletions src/lib/button-area/_token-utils.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@use '../core/styles/tokens/button-area/tokens';
@use '../core/styles/tokens/token-utils';

$_module: button-area;
$_tokens: tokens.$tokens;

@mixin provide-theme($theme) {
@include token-utils.provide-theme($_module, $_tokens, $theme);
}

@function token($name, $type: token) {
@return token-utils.token($_module, $_tokens, $name, $type);
}

@function declare($token) {
@return token-utils.declare($_module, $token);
}

@mixin override($token, $token-or-value, $type: token) {
@include token-utils.override($_module, $_tokens, $token, $token-or-value, $type);
}

@mixin tokens($includes: null, $excludes: null) {
@include token-utils.tokens($_module, $_tokens, $includes, $excludes);
}
146 changes: 44 additions & 102 deletions src/lib/button-area/button-area-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,85 @@
import { addClass, getShadowElement, removeClass, toggleClass } from '@tylertech/forge-core';
import { getShadowElement } from '@tylertech/forge-core';

import { BaseAdapter, IBaseAdapter, createUserInteractionListener } from '../core';
import { ForgeRipple, ForgeRippleAdapter, ForgeRippleCapableSurface, ForgeRippleFoundation } from '../ripple';
import { BaseAdapter, IBaseAdapter } from '../core';
import { FOCUS_INDICATOR_CONSTANTS, IFocusIndicatorComponent } from '../focus-indicator';
import { IStateLayerComponent, STATE_LAYER_CONSTANTS } from '../state-layer';
import { IButtonAreaComponent } from './button-area';
import { BUTTON_AREA_CONSTANTS } from './button-area-constants';

export interface IButtonAreaAdapter extends IBaseAdapter {
destroy(): void;
setDisabled(value: boolean): void;
addListener(type: string, listener: (event: Event) => void): void;
removeListener(type: string, listener: (event: Event) => void): void;
addSlotChangeListener(listener: () => void): void;
removeSlotChangeListener(listener: () => void): void;
addListener(type: string, listener: EventListener, capture?: boolean): void;
removeListener(type: string, listener: EventListener, capture?: boolean): void;
addButtonSlotListener(type: string, listener: EventListener): void;
removeButtonSlotListener(type: string, listener: EventListener): void;
addContentSlotListener(type: string, listener: EventListener): void;
removeContentSlotListener(type: string, listener: EventListener): void;
animateStateLayer(): void;
startButtonObserver(callback: MutationCallback): void;
stopButtonObserver(): void;
detectSlottedButton(): void;
buttonIsDisabled(): boolean;
isButtonDisabled(): boolean;
requestDisabledButtonFrame(): void;
createRipple(): Promise<void>;
}

export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> implements IButtonAreaAdapter, ForgeRippleCapableSurface {
export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> implements IButtonAreaAdapter {
private _rootElement: HTMLElement;
private _buttonSlotElement: HTMLSlotElement;
private _contentSlotElement: HTMLSlotElement;
private _buttonElement?: HTMLButtonElement;
private _rippleInstance?: ForgeRipple;
private _buttonObserver?: MutationObserver;
private _destroyUserInteractionListener: (() => void) | undefined;
private _focusIndicatorElement: IFocusIndicatorComponent;
private _stateLayerElement: IStateLayerComponent;


constructor(component: IButtonAreaComponent) {
super(component);
this._rootElement = getShadowElement(component, BUTTON_AREA_CONSTANTS.selectors.ROOT);
this._buttonSlotElement = getShadowElement(component, BUTTON_AREA_CONSTANTS.selectors.BUTTON_SLOT) as HTMLSlotElement;
this._contentSlotElement = getShadowElement(component, BUTTON_AREA_CONSTANTS.selectors.CONTENT_SLOT) as HTMLSlotElement;
this._focusIndicatorElement = getShadowElement(component, FOCUS_INDICATOR_CONSTANTS.elementName) as IFocusIndicatorComponent;
this._stateLayerElement = getShadowElement(component, STATE_LAYER_CONSTANTS.elementName) as IStateLayerComponent;
}

public destroy(): void {
if (typeof this._destroyUserInteractionListener === 'function') {
this._destroyUserInteractionListener();
this._destroyUserInteractionListener = undefined;
}
}
public destroy(): void {}

public get root(): HTMLElement {
return this._rootElement;
public setDisabled(value: boolean): void {
this._buttonElement?.toggleAttribute(BUTTON_AREA_CONSTANTS.attributes.DISABLED, value);
if (value) {
this._focusIndicatorElement.remove();
this._stateLayerElement.remove();
} else {
this._rootElement.append(this._focusIndicatorElement, this._stateLayerElement);
}
}

public get unbounded(): boolean | undefined {
return false;
public addListener(type: string, listener: EventListener, capture?: boolean): void {
this._rootElement.addEventListener(type, listener, { capture });
}

public get disabled(): boolean | undefined {
return this.buttonIsDisabled();
public removeListener(type: string, listener: EventListener, capture?: boolean): void {
this._rootElement.removeEventListener(type, listener, { capture });
}

public setDisabled(value: boolean): void {
toggleClass(this._rootElement, value, BUTTON_AREA_CONSTANTS.classes.DISABLED);
this._buttonElement?.toggleAttribute(BUTTON_AREA_CONSTANTS.attributes.DISABLED, value);
public addButtonSlotListener(type: string, listener: EventListener): void {
this._buttonSlotElement.addEventListener(type, listener);
}

public addListener(type: string, listener: (event: Event) => void): void {
this._rootElement.addEventListener(type, listener);
public removeButtonSlotListener(type: string, listener: EventListener): void {
this._buttonSlotElement.removeEventListener(type, listener);
}

public removeListener(type: string, listener: (event: Event) => void): void {
this._rootElement.removeEventListener(type, listener);
public addContentSlotListener(type: string, listener: EventListener): void {
this._contentSlotElement.addEventListener(type, listener);
}

public addSlotChangeListener(listener: () => void): void {
this._buttonSlotElement.addEventListener('slotchange', listener);
public removeContentSlotListener(type: string, listener: EventListener): void {
this._contentSlotElement.removeEventListener(type, listener);
}

public removeSlotChangeListener(listener: () => void): void {
this._buttonSlotElement.removeEventListener('slotchange', listener);
public animateStateLayer(): void {
this._stateLayerElement.playAnimation();
}

public startButtonObserver(callback: MutationCallback): void {
Expand All @@ -92,7 +100,7 @@ export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> impleme
this._buttonElement = this._buttonSlotElement.assignedElements()[0] as HTMLButtonElement | undefined;
}

public buttonIsDisabled(): boolean {
public isButtonDisabled(): boolean {
return this._buttonElement?.disabled ?? true;
}

Expand All @@ -103,70 +111,4 @@ export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> impleme
requestAnimationFrame(() => this._buttonElement!.disabled = false);
}
}

public async createRipple(): Promise<void> {
if (!this._rippleInstance) {
const { userInteraction, destroy } = await createUserInteractionListener(this._rootElement);
this._destroyUserInteractionListener = destroy;
const { type } = await userInteraction;
this._destroyUserInteractionListener = undefined;

if (!this.isConnected) {
return;
}

if (!this._rippleInstance) {
const adapter: ForgeRippleAdapter = {
...ForgeRipple.createAdapter(this),
isSurfaceActive: () => this._rootElement.matches(':active') || (this._buttonElement?.matches(':active') ?? false),
isSurfaceDisabled: () => this.disabled ?? true,
isUnbounded: () => !!this.unbounded,
registerInteractionHandler: (evtType, handler) => {
if (this._isRootEvent(evtType)) {
this._rootElement.addEventListener(evtType, handler, { passive: true });
} else {
this._buttonElement?.addEventListener(evtType, handler, { passive: true});
}
},
deregisterInteractionHandler: (evtType, handler) => {
if (this._isRootEvent(evtType)) {
this._rootElement.removeEventListener(evtType, handler, { passive: true } as AddEventListenerOptions);
} else {
this._buttonElement?.removeEventListener(evtType, handler, { passive: true } as AddEventListenerOptions);
}
},
addClass: (className) => addClass(className, this._rootElement),
removeClass: (className) => removeClass(className, this._rootElement),
updateCssVariable: (varName, value) => this._rootElement.style.setProperty(varName, value)
};

this._rippleInstance = new ForgeRipple(this._rootElement, new ForgeRippleFoundation(adapter));
if (type === 'focusin') {
this._rippleInstance.handleFocus();
}
}
} else {
if (typeof this._destroyUserInteractionListener === 'function') {
this._destroyUserInteractionListener();
}

this._rippleInstance.destroy();
this._rippleInstance = undefined;
}
}

private async _userInteractionListener(): Promise<string> {
if (typeof this._destroyUserInteractionListener === 'function') {
this._destroyUserInteractionListener();
}
const { userInteraction, destroy } = createUserInteractionListener(this._rootElement);
this._destroyUserInteractionListener = destroy;
const { type } = await userInteraction;
this._destroyUserInteractionListener = undefined;
return type;
}

private _isRootEvent(evtType: string): boolean {
return ['touchstart', 'pointerdown', 'mousedown'].includes(evtType);
}
}
8 changes: 5 additions & 3 deletions src/lib/button-area/button-area-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ const attributes = {

const ids = {
ROOT: 'root',
BUTTON_SLOT: 'button'
BUTTON_SLOT: 'button',
CONTENT_SLOT: 'content'
};

const classes = {
DISABLED: `forge-button-area--disabled`
ROOT: `forge-button-area`
};

const selectors = {
ROOT: `#${ids.ROOT}`,
BUTTON_SLOT: `slot[name=button]`
BUTTON_SLOT: `slot[name=button]`,
CONTENT_SLOT: `#${ids.CONTENT_SLOT}`
};

export const BUTTON_AREA_CONSTANTS = {
Expand Down
Loading
Loading