Skip to content

Instantly share code, notes, and snippets.

@andyjessop
Last active February 16, 2024 20:08
Show Gist options
  • Save andyjessop/1973c6a39e3381a93179198a687ad5fe to your computer and use it in GitHub Desktop.
Save andyjessop/1973c6a39e3381a93179198a687ad5fe to your computer and use it in GitHub Desktop.
Web Component Prompt

Web Component Development Prompt

Overview

Create a web component following best practices for architectural and stylistic elements. The component should be implemented using the lit library, with particular attention to accessibility, customisability, and reusability.

Objectives

  • The component must be fully functional, encapsulating all logic and styles necessary for its operation.
  • Implement static styles using css imported from lit, ensuring all CSS properties are exposed as custom properties on the host element for easy styling customisation.
  • Define slots and CSS parts to allow users to customise the content and style of different component parts.
  • Ensure the component is accessible, including proper ARIA attributes and roles where necessary.
  • Utilise @property and @state decorators to manage the component's reactive state and expose public properties for customization.
  • Implement a clear and concise API documentation comment block above the class definition, detailing the purpose, usage, dependencies, events, slots, and CSS parts of the component.

Technical Requirements

  1. Component Structure: Your component should extend LitElement.
  2. Styling: Expose each defined CSS property as a custom property via the host to allow for external styling.
  3. Functionality: Implement necessary logic for interactive elements (e.g., button clicks, focus management) while maintaining a clear separation between presentation and behaviour.
  4. Accessibility: Ensure the component adheres to WCAG guidelines, including keyboard navigability and ARIA roles.
  5. Properties and Attributes: Define properties for customisation, such as variant, size, disabled, ensuring they reflect to attributes as needed for CSS targeting.
  6. Slots and Parts: Use slots to allow for content injection and CSS parts for styling customisation of the component's internal elements.
  7. Event Handling: Implement event handlers to manage user interactions and emit custom events to integrate with external application logic.
  8. Form Integration: If applicable, ensure the component can be used within forms, managing its own validity state and interacting with form submission/reset processes.
  9. Inline Documentation: Should use the following docblock elements (where necessary): @summary, @slot, @csspart, @cssproperty, @event

Deliverables

  • A TypeScript class that extends LitElement, with detailed inline documentation.
  • CSS styles defined using lit's css function, exposed as custom properties.
  • A comprehensive set of properties and states to cover various use cases.
  • Event handling for common interactions, emitting custom events where appropriate.
  • Slots and CSS parts definitions for customisation.
  • Example usage documentation, demonstrating how to incorporate the component into a web page.

Ensure your code is efficient, readable, and follows best practices for web component development. Use TypeScript for type safety and better developer experience. Comments should be clear, concise, and informative, guiding the user through the component's functionality and customisation options.

The functionality of the component is as follows:

@andyjessop
Copy link
Author

Example:

...above prompt

- a login form with email, password, remember me, login
- fields are vertically aligned with labels above each field
- remember me is a checkbox, left aligned and on the same line as the login button (right aligned)
- checkbox and login button are vertically aligned
import { LitElement, html, css, property, state } from 'lit';
import { customElement } from 'lit/decorators.js';

/**
 * @summary `LoginForm` is a reusable and customizable web component for creating a login form. It supports custom styles, accessibility features, and emits a custom event upon form submission.
 * 
 * @slot - This component does not use slots.
 * @csspart button - The login button part.
 * @csspart input - The input fields (email and password) part.
 * @csspart label - The labels for the input fields.
 * @csspart checkbox - The "remember me" checkbox.
 * @cssproperty --login-form-background - The background of the login form.
 * @cssproperty --login-form-color - The text color of the login form.
 * @cssproperty --input-border - The border style for the input fields.
 * @cssproperty --button-background - The background color of the button.
 * @cssproperty --button-color - The text color of the button.
 * @event login-submit - Event emitted when the login form is submitted with detail of email, password, and rememberMe state.
 */
@customElement('login-form')
class LoginForm extends LitElement {
  static styles = css`
    :host {
      display: block;
      padding: 16px;
      background: var(--login-form-background, #fff);
      color: var(--login-form-color, #000);
    }

    .form-control {
      margin-bottom: 16px;
    }

    label {
      display: block;
      margin-bottom: 8px;
    }

    input[type="text"],
    input[type="password"] {
      width: 100%;
      padding: 8px;
      margin-bottom: 8px;
      border: var(--input-border, 1px solid #ccc);
      box-sizing: border-box;
    }

    .actions {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .checkbox {
      display: flex;
      align-items: center;
    }

    button {
      padding: 8px 16px;
      background: var(--button-background, #007bff);
      color: var(--button-color, #fff);
      border: none;
      cursor: pointer;
    }

    button:disabled {
      background: #ccc;
    }
  `;

  @property({ type: String }) email = '';
  @property({ type: String }) password = '';
  @property({ type: Boolean, reflect: true }) disabled = false;
  @property({ type: Boolean }) rememberMe = false;

  @state() private _emailError = '';
  @state() private _passwordError = '';

  private _validateEmail(): boolean {
    // Simple validation for demonstration purposes
    const valid = /\S+@\S+\.\S+/.test(this.email);
    this._emailError = valid ? '' : 'Invalid email address';
    return valid;
  }

  private _validatePassword(): boolean {
    // Simple validation for demonstration purposes
    const valid = this.password.length >= 6;
    this._passwordError = valid ? '' : 'Password must be at least 6 characters';
    return valid;
  }

  private _handleSubmit(e: Event) {
    e.preventDefault();
    if (this._validateEmail() && this._validatePassword()) {
      this.dispatchEvent(new CustomEvent('login-submit', { detail: { email: this.email, password: this.password, rememberMe: this.rememberMe }, bubbles: true, composed: true }));
    }
  }

  private _handleEmailChange(e: Event) {
    this.email = (e.target as HTMLInputElement).value;
  }

  private _handlePasswordChange(e: Event) {
    this.password = (e.target as HTMLInputElement).value;
  }

  private _handleRememberMeChange(e: Event) {
    this.rememberMe = (e.target as HTMLInputElement).checked;
  }

  render() {
    return html`
      <form @submit="${this._handleSubmit}">
        <div class="form-control">
          <label for="email">Email:</label>
          <input @input="${this._handleEmailChange}" type="text" id="email" name="email" .value="${this.email}" ?disabled="${this.disabled}" part="input" aria-describedby="emailError" />
          ${this._emailError ? html`<span id="emailError" role="alert">${this._emailError}</span>` : ''}
        </div>
        <div class="form-control">
          <label for="password">Password:</label>
          <input @input="${this._handlePasswordChange}" type="password" id="password" name="password" .value="${this.password}" ?disabled="${this.disabled}" part="input" aria-describedby="passwordError" />
          ${this._passwordError ? html`<span id="passwordError" role="alert">${this._passwordError}</span>` : ''}
        </div>
        <div class="actions">
          <div class="checkbox">
            <label>
              <input type="checkbox" @change="${this._handleRememberMeChange}" ?checked="${this.rememberMe}" ?disabled="${this.disabled}" part="checkbox"> Remember me
            </label>
          </div>
          <button type="submit" ?disabled="${this.disabled}" part="button">Login</button>
        </div>
      </form>
    `;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment