Skip to main content

Takeoff UI Coding Standards

This document outlines comprehensive guidelines for component development using the Stencil library. These standards ensure consistency and maintainability across projects.

Folder Structure

Each component directory should contain:

tk-component/
├── tk-component.tsx # Component logic
├── tk-component.scss # Component styles
├── interfaces.ts # Type definitions (if needed)
└── test/
├── tk-component.spec.tsx # Unit tests
└── tk-component.e2e.tsx # E2E tests (if needed)

Naming Conventions

Component Naming

File/Tag Name: Use dash-case with tk- prefix

// Example: tk-button.tsx
@Component({
tag: 'tk-button',
styleUrl: 'tk-button.scss',
shadow: true
})

Class Names

Component Class: Use PascalCase with Tk prefix

export class TkButton implements ComponentInterface {
// ...
}

Decorators and Methods

@Element: Use el as name

@Element() el: HTMLTkButtonElement;

@State, @Prop: Use camelCase

@Prop() buttonSize: 'small' | 'medium' | 'large' = 'medium';
@State() isPressed = false;

@Watch: Use camelCase with Changed suffix

@Watch('value')
valueChanged(newValue: string) {
// ...
}

@Event: Use camelCase with tk prefix

@Event() tkChange: EventEmitter<string>;
@Event() tkFocus: EventEmitter<void>;

Handler Methods: Use camelCase with handle prefix

private handleClick() {
// ...
}

Component Implementation

Code Organization Template

import { Component, h, Prop, State, Event, Element, Watch } from '@stencil/core';
import { ComponentInterface } from '@stencil/core';
import classNames from 'classnames';

@Component({
tag: 'tk-component',
styleUrl: 'tk-component.scss',
shadow: true
})
export class TkComponent implements ComponentInterface {
// 1. Element decorator
@Element() el: HTMLTkMyComponentElement;

// 2. State decorators
@State() private isActive = false;

// 3. Prop decorators
@Prop() value: string;

// 4. Watch decorators (immediately after related prop)
@Watch('value')
valueChanged(newValue: string) {
// ...
}

// 5. Event decorators
@Event() tkChange: EventEmitter<string>;

// 6. Lifecycle methods
componentWillLoad() {
// ...
}

// 7. Public methods
@Method()
async setValue(value: string) {
// ...
}

// 8. Private methods
private handleClick() {
// ...
}

// 9. Render - Create methods
private createHeaderLabel() {
// ...
return (
<span class="tk-component-header-label">
{this.header}
</span>
);
}

private renderHeader() {
// ...
return (
<div class={classNames('tk-component-header', {
/** ... */
})}>
/** Use create prefix for outer of render*/
{this.createHeaderLabel()}
</div>
);
}

render() {
return (
<div class={classNames('tk-component', {
'tk-component-active': this.isActive
})}>
{this.renderHeader()}
</div>
);
}
}

Testing Standards

Test Types

Unit Tests (*.spec.tsx)

  • Located in test directory
  • Tests component props, states, and synchronous methods
  • Uses newSpecPage() for virtual DOM testing
  • Focuses on isolated component behavior

E2E Tests (*.e2e.ts)

  • Located in test directory alongside spec files
  • Tests user interactions and animations
  • Uses newE2EPage() for browser environment testing
  • Validates component behavior in real browser context

Test Structure

describe('tk-component', () => {
// Basic rendering tests
describe('basic rendering', () => {
it('renders with default props', async () => {
const page = await newSpecPage({
components: [TkComponent],
html: `<tk-component></tk-component>`,
});
expect(page.root).toBeTruthy();
});
});

// Event testing
describe('event handling', () => {
it('emits change event', async () => {
const page = await newE2EPage();
await page.setContent(`<tk-component></tk-component>`);
const eventSpy = await page.spyOnEvent('tkChange');
const element = await page.find('[data-testid="interactive-element"]');
await element.click();
expect(eventSpy).toHaveReceivedEvent();
});
});
});

Best Practices

DOM Queries

// Recommended: Use data-testid
const element = await page.find('tk-component >>> [data-testid="component-element"]');

// Not Recommended: Direct DOM manipulation
const element = page.root.shadowRoot.querySelector('.component-class');

Asynchronous Operations

// Recommended: Wait for changes
await page.waitForChanges();

// Not Recommended: Arbitrary timeouts
await page.waitForTimeout(1000);

Event Testing

// Recommended: Simulate user interaction
const button = await page.find('[data-testid="submit-button"]');
await button.click();

// Not Recommended: Direct method calls
await element.callMethod('submit');

Coverage Requirements

The following coverage thresholds must be maintained:

  • Statements: 90%
  • Branches: 80%
  • Functions: 90%
  • Lines: 90%

Test Implementation Checklist

Before submitting a component, ensure all these aspects are tested:

  • Basic rendering with default props
  • All prop combinations
    • Different variants (primary, secondary, etc.)
    • Different sizes
    • Different states (disabled, readonly, etc.)
  • State changes and updates
  • Event emissions
  • Public methods (if present)
  • Edge cases
    • Null values
    • Undefined values
    • Minimum/maximum values
  • Lifecycle methods
    • componentWillLoad
    • componentDidLoad
    • componentWillUpdate
    • componentDidUpdate
  • Responsive behavior (if applicable)

Test Development Flow

  1. Start with basic render tests
  2. Add variant testing
  3. Implement state testing
  4. Add event testing
  5. Test public methods
  6. Add edge case testing
  7. Check coverage report
  8. Address coverage gaps

Styling

SCSS Notes

  • Use design system variables from Figma
  • Follow tk- naming with component prefix
.tk-component {
color: var(--static-white);

&.tk-component-large {
font-size: var(--desktop-body-m-base-size);
}

&.tk-component-active {
background: var(--primary-sub-base);
}
}

Framework Integration

Binding Support

The framework integration is handled through the Stencil configuration. Component developers should ensure their components emit proper events for framework bindings:

For v-model support (Vue.js):

@Event() tkChange: EventEmitter<string>;
@Prop() value: string;