- Must be written following the kebab-case style;
- Must be prefixed with
kup-
. - Being a web component, it must include a Shadow DOM.
- Must be written following the PascalCase style and prefixed with
KupComponentname
(i.e.:KupCardData
).
- Must be written following the kebab-case style and prefixed with
kup-componentname
(i.e.:kup-buttonlist-click
).
- Must be written following the kebab-case style and prefixed with
--kup-
(i.e.:--kup-drawer-width
); - Color variables must always have the
-color
suffix: this way RGB, hue, saturation and lightness variables (used for shaders) will be automatically created byKupTheme
.
- Variables for internal use or created dynamically by the component must be written following this syntax:
--kup_
+ component + "_variable-name" (i.e.:--kup_card_height
); - Variables for internal use should be declared as the following example:
--kup_radio_font_family: var(--kup-radio-font-family, var(--kup-font-family))
; - Public variables must be written mirroring internal variables, but in kebab-case (i.e.:
--kup-radio-font-family
);
Important note about variables: CSS variables declared theme-wide are variables that have an impact on the whole UI and which should be available globally. For instance, --kup-navbar-height is a component specific variable (related to kup-nav-bar) but it's handy for the application to set paddings, margins, etc, for this reason it makes sense for it to be a theme variable.
When a component needs to use multiple TypeScript interfaces for complex data, those interfaces must be placed in a TS file having the same name as the component file and the suffix -declarations
before the file extension.
In order to make components' code more readable and accessible, their structure should be as similar as possible.
Ideally, component classes should be structured like this:
@Element
decorator (namedrootElement
).
/**
* References the root HTML element of the component (<kup-card>).
*/
@Element() rootElement: HTMLElement;
@State
decorators (sorted alphabetically and with a description which includes the default value).
/*-------------------------------------------------*/
/* S t a t e s */
/*-------------------------------------------------*/
/**
* The value of the component.
* @default ""
*/
@State() value: string = '';
@Props
decorators (sorted alphabetically and with a description which includes the default value).
/*-------------------------------------------------*/
/* P r o p s */
/*-------------------------------------------------*/
/**
* Custom style of the component.
* @default ""
* @see https://smeup.github.io/ketchup/#/customization
*/
@Prop() customStyle: string = '';
- Internal variables.
/*-------------------------------------------------*/
/* I n t e r n a l V a r i a b l e s */
/*-------------------------------------------------*/
/**
* Instance of the KupManager class.
*/
private kupManager: KupManager = kupManagerInstance();
@Events
decorators (with a description).
/*-------------------------------------------------*/
/* E v e n t s */
/*-------------------------------------------------*/
/**
* Triggered when the card is clicked.
*/
@Event({
eventName: 'kup-card-click',
composed: true,
cancelable: false,
bubbles: true,
})
kupClick: EventEmitter<KupEventPayload>;
@Listen
decorators.
/*-------------------------------------------------*/
/* L i s t e n e r s */
/*-------------------------------------------------*/
@Listen('keydown')
listenKeydown(e: KeyboardEvent) {
if (this.keyboardNavigation) {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
e.stopPropagation();
this.focusNext();
break;
case 'ArrowUp':
e.preventDefault();
e.stopPropagation();
this.focusPrevious();
break;
case 'Enter':
e.preventDefault();
e.stopPropagation();
this.handleSelection(this.focused);
break;
}
}
}
@Watch
decorators.
/*-------------------------------------------------*/
/* W a t c h e r s */
/*-------------------------------------------------*/
@Watch('filter')
watchFilter() {
this.filteredItems = [];
let index = 0;
this.data.map((item) => {
this.setUnselected(item, index++);
});
this.data = [...this.data];
}
@Method
decorators (should have a description of the method, the arguments and the returns).
/*-------------------------------------------------*/
/* P u b l i c M e t h o d s */
/*-------------------------------------------------*/
/**
* Used to retrieve component's props values.
* @param {boolean} descriptions - When provided and true, the result will be the list of props with their description.
* @returns {Promise<GenericObject>} List of props as object, each key will be a prop.
*/
@Method()
async getProps(descriptions?: boolean): Promise<GenericObject> {
return getProps(this, KupCardProps, descriptions);
}
- Private methods.
/*-------------------------------------------------*/
/* P r i v a t e M e t h o d s */
/*-------------------------------------------------*/
/**
* Set the events of the component.
*/
private setEvents(): void {
const root: ShadowRoot = this.rootElement.shadowRoot;
if (root) {
// The dialog "X" button.
const dialogClose: HTMLElement = root.querySelector(
'#' + KupCardIds.DIALOG_CLOSE
);
if (dialogClose) {
dialogClose.onclick = () => this.rootElement.remove();
}
// When an element can be clicked. Ideally anchors/links.
const links: NodeListOf<HTMLElement> = root.querySelectorAll(
'.' + KupCardCSSClasses.CLICKABLE_LINK
);
for (let index = 0; index < links.length; index++) {
const link: HTMLElement = links[index];
link.onclick = (e) => {
e.stopPropagation();
this.onKupClick(link.id);
};
}
}
}
- Lifecycle hooks
/*-------------------------------------------------*/
/* L i f e c y c l e H o o k s */
/*-------------------------------------------------*/
componentWillLoad() {
this.kupManager.debug.logLoad(this, false);
this.kupManager.language.register(this);
this.kupManager.theme.register(this);
this.registerListeners();
}
Almost all Ketchup components should include these features:
- customStyle
- debugging
The customStyle
prop allows a custom stylesheet to be defined inside a component. More info here.
Debugging is useful to understand a component's performances.
Both these features (among many others) reside in an API called KupManager
, which is a singleton that must be declared inside the internal variables section:
/**
* Instance of the KupManager class.
*/
private kupManager: KupManager = kupManagerInstance();
- Define the
customStyle
prop:
/**
* Custom style of the component.
* @default ""
* @see https://smeup.github.io/ketchup/#/customization
*/
@Prop() customStyle: string = '';
- Register the component through the KupTheme API inside the
componentWillLoad
lifecycle hook:
this.kupManager.theme.register(this);
- Define the
customStyle
constant in therender()
hook, just before returning the JSX of the component:
const customStyle: string = this.kupManager.theme.setKupStyle(this.rootElement as KupComponent);
- Use the
customStyle
constant just below the<Host>
tag:
return (
<Host style={style}>
{customStyle ? <style>{customStyle}</style> : null}
<div
id={componentWrapperId}
onClick={() => this.onKupClick(null)}
>
{this.getLayout()}
</div>
</Host>
);
- Unregister the component through the KupTheme API inside the
disconnectedCallback
lifecycle hook:
this.kupManager.theme.unregister(this);
- Call the following methods in the following lifecycle hooks:
componentWillLoad() {
this.kupManager.debug.logLoad(this, false);
//..
//Actual willLoad code (below)
}
componentDidLoad() {
//Actual didLoad code (above)
//..
this.kupManager.debug.logLoad(this, true);
}
componentWillRender() {
this.kupManager.debug.logRender(this, false);
//..
//Actual willRender code (below)
}
componentDidRender() {
//Actual didRender code (above)
//..
this.kupManager.debug.logRender(this, true);
}
The markup returned by the render() method should be like this:
<Host>
{customStyle ? <style>{customStyle}</style> : null}
<div id={componentWrapperId}>
...
</div>
</Host>
The definition of Host
makes it easy to add styles, classes or events directly on the root element. The componentWrapperId
wrapper helps specificity when defining styles, for example when 3rd party stylesheets must be overridden or when a customStyle must be applied.
When declaring variables, always use const
for constants and let
for dynamic variables.
Avoid using the any
type whenever possible, otherwise you're losing all the benefits of TypeScript.
All public methods and props should be documented using JSDoc.
Here's an example of a method:
/**
* Converts an HSL color to its RGB values.
* @param {number} h - Hue (range [0, 360)).
* @param {number} s - Saturation (range [0, 1)).
* @param {number} l - Lightness (range [0, 1)).
* @returns {Array} RGB values.
*/
And an example of a prop:
/**
* Text displayed by the nav bar.
* @default null
*/
@Prop() label: string = null;
Commits should wrap all the edits concerning a common fix or feature and they must be formatted like this:
Component: name of the fix/feature.
A few examples below.
FTextField: handled input type.
kup-box: added kanban feature.
kup-data-table: improved render performances.
Showcase: created family tree page.
For testing Style Guide see testing.