Skip to content

Commit

Permalink
Merge pull request #8 from newjersey/button-component-tests
Browse files Browse the repository at this point in the history
Updates to NJWDS button web component + new icon component
  • Loading branch information
jasnoo authored Sep 26, 2024
2 parents de8ed3e + 1aabce6 commit ebd53b4
Show file tree
Hide file tree
Showing 22 changed files with 1,104 additions and 412 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { JSX } from '../../../../stencil-library/dist';



export const MyComponent = /*@__PURE__*/createReactComponent<JSX.MyComponent, HTMLMyComponentElement>('my-component');
export const NjwdsAlert = /*@__PURE__*/createReactComponent<JSX.NjwdsAlert, HTMLNjwdsAlertElement>('njwds-alert');
export const NjwdsBanner = /*@__PURE__*/createReactComponent<JSX.NjwdsBanner, HTMLNjwdsBannerElement>('njwds-banner');
export const NjwdsButton = /*@__PURE__*/createReactComponent<JSX.NjwdsButton, HTMLNjwdsButtonElement>('njwds-button');
export const NjwdsIcon = /*@__PURE__*/createReactComponent<JSX.NjwdsIcon, HTMLNjwdsIconElement>('njwds-icon');
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { camelToDashCase } from './case';

export const attachProps = (node: HTMLElement, newProps: any, oldProps: any = {}) => {
// some test frameworks don't render DOM elements, so we test here to make sure we are dealing with DOM first
if (node instanceof Element) {
// add any classes in className to the class list
const className = getClassName(node.classList, newProps, oldProps);
if (className !== '') {
node.className = className;
}

Object.keys(newProps).forEach((name) => {
if (
name === 'children' ||
name === 'style' ||
name === 'ref' ||
name === 'class' ||
name === 'className' ||
name === 'forwardedRef'
) {
return;
}
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2);
const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);

if (!isCoveredByReact(eventNameLc)) {
syncEvent(node, eventNameLc, newProps[name]);
}
} else {
(node as any)[name] = newProps[name];
const propType = typeof newProps[name];
if (propType === 'string') {
node.setAttribute(camelToDashCase(name), newProps[name]);
}
}
});
}
};

export const getClassName = (classList: DOMTokenList, newProps: any, oldProps: any) => {
const newClassProp: string = newProps.className || newProps.class;
const oldClassProp: string = oldProps.className || oldProps.class;
// map the classes to Maps for performance
const currentClasses = arrayToMap(classList);
const incomingPropClasses = arrayToMap(newClassProp ? newClassProp.split(' ') : []);
const oldPropClasses = arrayToMap(oldClassProp ? oldClassProp.split(' ') : []);
const finalClassNames: string[] = [];
// loop through each of the current classes on the component
// to see if it should be a part of the classNames added
currentClasses.forEach((currentClass) => {
if (incomingPropClasses.has(currentClass)) {
// add it as its already included in classnames coming in from newProps
finalClassNames.push(currentClass);
incomingPropClasses.delete(currentClass);
} else if (!oldPropClasses.has(currentClass)) {
// add it as it has NOT been removed by user
finalClassNames.push(currentClass);
}
});
incomingPropClasses.forEach((s) => finalClassNames.push(s));
return finalClassNames.join(' ');
};

/**
* Transforms a React event name to a browser event name.
*/
export const transformReactEventName = (eventNameSuffix: string) => {
switch (eventNameSuffix) {
case 'doubleclick':
return 'dblclick';
}
return eventNameSuffix;
};

/**
* Checks if an event is supported in the current execution environment.
* @license Modernizr 3.0.0pre (Custom Build) | MIT
*/
export const isCoveredByReact = (eventNameSuffix: string) => {
if (typeof document === 'undefined') {
return true;
} else {
const eventName = 'on' + transformReactEventName(eventNameSuffix);
let isSupported = eventName in document;

if (!isSupported) {
const element = document.createElement('div');
element.setAttribute(eventName, 'return;');
isSupported = typeof (element as any)[eventName] === 'function';
}

return isSupported;
}
};

export const syncEvent = (
node: Element & { __events?: { [key: string]: ((e: Event) => any) | undefined } },
eventName: string,
newEventHandler?: (e: Event) => any
) => {
const eventStore = node.__events || (node.__events = {});
const oldEventHandler = eventStore[eventName];

// Remove old listener so they don't double up.
if (oldEventHandler) {
node.removeEventListener(eventName, oldEventHandler);
}

// Bind new listener.
node.addEventListener(
eventName,
(eventStore[eventName] = function handler(e: Event) {
if (newEventHandler) {
newEventHandler.call(this, e);
}
})
);
};

const arrayToMap = (arr: string[] | DOMTokenList) => {
const map = new Map<string, string>();
(arr as string[]).forEach((s: string) => map.set(s, s));
return map;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const dashToPascalCase = (str: string) =>
str
.toLowerCase()
.split('-')
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
.join('');
export const camelToDashCase = (str: string) => str.replace(/([A-Z])/g, (m: string) => `-${m[0].toLowerCase()}`);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const isDevMode = () => {
return process && process.env && process.env.NODE_ENV === 'development';
};

const warnings: { [key: string]: boolean } = {};

export const deprecationWarning = (key: string, message: string) => {
if (isDevMode()) {
if (!warnings[key]) {
console.warn(message);
warnings[key] = true;
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';

import type { StyleReactProps } from '../interfaces';

export type StencilReactExternalProps<PropType, ElementType> = PropType &
Omit<React.HTMLAttributes<ElementType>, 'style'> &
StyleReactProps;

// This will be replaced with React.ForwardedRef when react-output-target is upgraded to React v17
export type StencilReactForwardedRef<T> = ((instance: T | null) => void) | React.MutableRefObject<T | null> | null;

export const setRef = (ref: StencilReactForwardedRef<any> | React.Ref<any> | undefined, value: any) => {
if (typeof ref === 'function') {
ref(value);
} else if (ref != null) {
// Cast as a MutableRef so we can assign current
(ref as React.MutableRefObject<any>).current = value;
}
};

export const mergeRefs = (
...refs: (StencilReactForwardedRef<any> | React.Ref<any> | undefined)[]
): React.RefCallback<any> => {
return (value: any) => {
refs.forEach((ref) => {
setRef(ref, value);
});
};
};

export const createForwardRef = <PropType, ElementType>(ReactComponent: any, displayName: string) => {
const forwardRef = (
props: StencilReactExternalProps<PropType, ElementType>,
ref: StencilReactForwardedRef<ElementType>
) => {
return <ReactComponent {...props} forwardedRef={ref} />;
};
forwardRef.displayName = displayName;

return React.forwardRef(forwardRef);
};

export const defineCustomElement = (tagName: string, customElement: any) => {
if (customElement !== undefined && typeof customElements !== 'undefined' && !customElements.get(tagName)) {
customElements.define(tagName, customElement);
}
};

export * from './attachProps';
export * from './case';
8 changes: 4 additions & 4 deletions packages/stencil-library/njwds/css/styles.css

Large diffs are not rendered by default.

311 changes: 310 additions & 1 deletion packages/stencil-library/njwds/css/styles.css.map

Large diffs are not rendered by default.

72 changes: 29 additions & 43 deletions packages/stencil-library/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,9 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { ButtonVariant } from "./components/button/button";
import { Mode } from "./interface";
export { ButtonVariant } from "./components/button/button";
export { Mode } from "./interface";
import { ButtonVariant, IconPosition, Mode } from "./interface";
export { ButtonVariant, IconPosition, Mode } from "./interface";
export namespace Components {
interface MyComponent {
/**
* The first name
*/
"first": string;
/**
* The last name
*/
"last": string;
/**
* The middle name
*/
"middle": string;
}
interface NjwdsAlert {
"noIcon": boolean;
"slim": boolean;
Expand All @@ -32,18 +16,20 @@ export namespace Components {
interface NjwdsBanner {
}
interface NjwdsButton {
"asChild": boolean;
"icon"?: string;
"iconPosition": IconPosition;
"iconTitle"?: string;
"mode": Mode;
"variant": ButtonVariant;
}
interface NjwdsIcon {
"decorative": boolean;
"icon": string;
"iconTitle"?: string;
"size": "3" | "4"| "5" | "scale";
}
}
declare global {
interface HTMLMyComponentElement extends Components.MyComponent, HTMLStencilElement {
}
var HTMLMyComponentElement: {
prototype: HTMLMyComponentElement;
new (): HTMLMyComponentElement;
};
interface HTMLNjwdsAlertElement extends Components.NjwdsAlert, HTMLStencilElement {
}
var HTMLNjwdsAlertElement: {
Expand All @@ -62,28 +48,20 @@ declare global {
prototype: HTMLNjwdsButtonElement;
new (): HTMLNjwdsButtonElement;
};
interface HTMLNjwdsIconElement extends Components.NjwdsIcon, HTMLStencilElement {
}
var HTMLNjwdsIconElement: {
prototype: HTMLNjwdsIconElement;
new (): HTMLNjwdsIconElement;
};
interface HTMLElementTagNameMap {
"my-component": HTMLMyComponentElement;
"njwds-alert": HTMLNjwdsAlertElement;
"njwds-banner": HTMLNjwdsBannerElement;
"njwds-button": HTMLNjwdsButtonElement;
"njwds-icon": HTMLNjwdsIconElement;
}
}
declare namespace LocalJSX {
interface MyComponent {
/**
* The first name
*/
"first"?: string;
/**
* The last name
*/
"last"?: string;
/**
* The middle name
*/
"middle"?: string;
}
interface NjwdsAlert {
"noIcon"?: boolean;
"slim"?: boolean;
Expand All @@ -92,25 +70,33 @@ declare namespace LocalJSX {
interface NjwdsBanner {
}
interface NjwdsButton {
"asChild"?: boolean;
"icon"?: string;
"iconPosition"?: IconPosition;
"iconTitle"?: string;
"mode"?: Mode;
"variant"?: ButtonVariant;
}
interface NjwdsIcon {
"decorative"?: boolean;
"icon"?: string;
"iconTitle"?: string;
"size"?: "3" | "4"| "5" | "scale";
}
interface IntrinsicElements {
"my-component": MyComponent;
"njwds-alert": NjwdsAlert;
"njwds-banner": NjwdsBanner;
"njwds-button": NjwdsButton;
"njwds-icon": NjwdsIcon;
}
}
export { LocalJSX as JSX };
declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"my-component": LocalJSX.MyComponent & JSXBase.HTMLAttributes<HTMLMyComponentElement>;
"njwds-alert": LocalJSX.NjwdsAlert & JSXBase.HTMLAttributes<HTMLNjwdsAlertElement>;
"njwds-banner": LocalJSX.NjwdsBanner & JSXBase.HTMLAttributes<HTMLNjwdsBannerElement>;
"njwds-button": LocalJSX.NjwdsButton & JSXBase.HTMLAttributes<HTMLNjwdsButtonElement>;
"njwds-icon": LocalJSX.NjwdsIcon & JSXBase.HTMLAttributes<HTMLNjwdsIconElement>;
}
}
}
Loading

0 comments on commit ebd53b4

Please sign in to comment.