You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

kui-framework

Package Overview
Dependencies
Maintainers
0
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

kui-framework

A lightweight framework for building web applications.

1.0.9
latest
npmnpm
Version published
Weekly downloads
0
-100%
Maintainers
0
Weekly downloads
 
Created
Source

UI Framework

License Version TypeScript

A lightweight and flexible UI framework for building modern web applications.

Features

You can create all kinds of interfaces quickly with this framework.

Email Client Example

  • 🚀 Lightweight and fast
  • 🧩 Modular component system
  • 🎨 Customizable styling
  • 🔌 Extensible plugin architecture
  • 📱 Responsive design support
  • ✨ Animation support for UI elements
  • 📝 Templating system for rendering dynamic content

How it Works

The code in src/lib is implementing a lightweight UI framework for building web applications. It provides a set of reusable components and utilities to create interactive user interfaces. Here's a brief overview of its functionality:

  • Core components: View, Container, Button, Form, List, and Modal.
  • Templating system for rendering dynamic content.
  • Animation support for UI elements.
  • Styling utilities for consistent appearance.
  • Plugin system for extensibility.

Simple Example:

import { Container, Button, Form, Animation } from "../index";

export function loginexample()
{
    const app = new Container();

    const login = new Button({
    text: "Login",
    hoverAnimation: Animation("grow", 300),
    });
    login.setOnClick(() => alert("Hello!"));

    const loginForm = new Form({
    fields: [
        { type: "text", name: "username", label: "Username" },
        { type: "password", name: "password", label: "Password" },
    ],
    onSubmit: (data) => console.log("Login:", data),
    });

    app.addChild(loginForm);
    app.addChild(login);
    app.mount(document.body);
}

This example creates a simple app with a greeting button and a login form.

In-depth explanation

The framework is built around the concept of Views, which are the basic building blocks of the UI. The View class provides core functionality for creating and managing UI elements.

// view.ts

import { AnimationOptions } from "./animations";

export interface ViewOptions {
  id?: string;
  className?: string;
  template?: string;
}

export class View {
  protected element: HTMLElement;
  protected children: View[] = [];
  private mountedPromise: Promise<void>;
  private mountedResolve!: () => void;

  constructor(options: ViewOptions = {}) {
    this.element = document.createElement("div");
    if (options.id) this.element.id = options.id;
    if (options.className) this.element.className = options.className;
    if (options.template) this.setTemplate(options.template);

    this.mountedPromise = new Promise((resolve) => {
      this.mountedResolve = resolve;
    });
  }

  setTemplate(template: string): void {
    this.element.innerHTML = template;
  }

  appendChild(child: View): void {
    this.children.push(child);
    this.element.appendChild(child.getElement());
  }

  getElement(): HTMLElement {
    return this.element;
  }

  async onMount(): Promise<void> {
    // To be overridden by subclasses
  }

  async onUnmount(): Promise<void> {
    // To be overridden by subclasses
  }

  mounted(): Promise<void> {
    return this.mountedPromise;
  }

  mount(parent: HTMLElement): void {
    parent.appendChild(this.element);
    this.mountedResolve();
    this.onMount();
  }

  unmount(): void {
    this.element.remove();
    this.onUnmount();
  }

  centerInRow(): this {
    this.element.style.justifySelf = "center";
    return this;
  }

  centerInColumn(): this {
    this.element.style.alignSelf = "center";
    return this;
  }
}

The Container class extends View and allows for more complex layouts, including grid systems and flexible positioning of child elements.

// container.ts

import { View, ViewOptions } from "./view";
import { Button, ButtonOptions } from "./button";
import { Form, FormOptions } from "./form";
import { framework } from "./framework";

// Initialize the framework
framework.init();

export interface GridOptions {
  columns: number | string;
  rows?: number | string;
  gap?: string;
}

export type RowAlignment =
  | "left"
  | "center"
  | "right"
  | "space-between"
  | "space-around"
  | "space-evenly";

export interface ContainerOptions extends ViewOptions {
  gap?: string;
  padding?: string;
  grid?: GridOptions;
  scrollable?: boolean;
  rowAlignment?: RowAlignment;
  equalWidthChildren?: boolean;
  columns?: number | string;
  rows?: number | string;
}

Other components like Button, Form, and List build upon these base classes to provide specific functionality. For example, the Button class adds click handling and hover effects.

// button.ts

import { View, ViewOptions } from "./view";
import { renderTemplate } from "./templating";
import { AnimationOptions, applyAnimation } from "./animations";

export interface ButtonOptions extends ViewOptions {
  text: string;
  onClick?: () => void;
  color?: string;
  hoverColor?: string;
  hoverAnimation?: AnimationOptions;
  clickAnimation?: AnimationOptions;
}

const buttonTemplate = `
  <button class="ui-button" style="background-color: {{color}}">
    {{text}}
  </button>
`;

export class Button extends View {
  private buttonElement: HTMLButtonElement;

  constructor(options: ButtonOptions) {
    super({
      ...options,
      template: renderTemplate(buttonTemplate, {
        text: options.text,
        color: options.color || "#007bff",
      }),
    });

    this.buttonElement = this.element.querySelector(
      "button"
    ) as HTMLButtonElement;

    if (options.onClick) {
      this.buttonElement.addEventListener("click", () => {
        if (options.clickAnimation) {
          applyAnimation(this.buttonElement, options.clickAnimation);
        }
        // Add null check before invoking onClick
        options.onClick?.();
      });
    }

    if (options.hoverAnimation || options.hoverColor) {
      this.buttonElement.addEventListener("mouseenter", () => {
        if (options.hoverAnimation) {
          applyAnimation(this.buttonElement, options.hoverAnimation);
        }
        if (options.hoverColor) {
          this.buttonElement.style.backgroundColor = options.hoverColor;
        }
      });

      this.buttonElement.addEventListener("mouseleave", () => {
        if (options.hoverAnimation) {
          this.buttonElement.style.animation = "none";
        }
        if (options.hoverColor) {
          this.buttonElement.style.backgroundColor = options.color || "#007bff";
        }
      });
    }
  }

  setText(text: string): void {
    this.buttonElement.textContent = text;
  }

  setColor(color: string): void {
    this.buttonElement.style.backgroundColor = color;
  }
}

The framework also includes a templating system for rendering dynamic content, and an animation system (referenced in button.ts) for adding visual effects to UI elements.

// templating.ts

export function renderTemplate(
  template: string,
  data: { [key: string]: any }
): string {
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] || "");
}

Complex Example: Email Client Example

// emailClientExample.ts

import { Container, View, Button, Form, List } from "../index";
import { initStyles, appendStyles } from "../styles";

interface Email {
  id: number;
  from: string;
  subject: string;
  body: string;
  read: boolean;
}

class EmailItem extends View {
  constructor(email: Email, onSelect: (id: number) => void) {
    super({
      className: `email-item ${email.read ? "read" : "unread"}`,
      template: `
        <div class="email-sender">${email.from}</div>
        <div class="email-subject">${email.subject}</div>
      `,
    });

    this.element.addEventListener("click", () => onSelect(email.id));
  }
}

class EmailView extends View {
  constructor(email: Email | null) {
    super({
      className: "email-view",
      template: email
        ? `
        <h2>${email.subject}</h2>
        <p><strong>From:</strong> ${email.from}</p>
        <div class="email-body">${email.body}</div>
      `
        : "<p>Select an email to view</p>",
    });
  }
}

export function createEmailClientExample(): View {
  initStyles();
  appendStyles(emailClientCSS);

  const mainContainer = new Container({
    className: "email-client",
    gap: "20px",
  });

  // Header
  const header = new View({
    className: "header",
    template: "<h1>Simple Email Client</h1>",
  });
  mainContainer.addChild(header);

  // Main content area
  const content = new Container({
    className: "content",
    gap: "20px",
    columns: "150px 300px 1fr", // Define three columns
  });
  mainContainer.addChild(content);

  // Sidebar (Folders)
  const sidebar = new Container({
    className: "sidebar",
    gap: "10px",
    equalWidthChildren: true,
  });
  ["Inbox", "Sent", "Drafts", "Trash"].forEach((folder) => {
    sidebar.addChild(
      new Button({
        text: folder,
        hoverColor: "#0056b3",
        hoverAnimation: { type: "grow", duration: 300 },
        clickAnimation: { type: "push", duration: 200 },
        onClick: () => console.log(`Switched to ${folder}`),
      })
    );
  });
  content.addChild(sidebar);

  // Email list
  const emailListContainer = new Container({
    className: "email-list-container",
    gap: "10px",
  });
  content.addChild(emailListContainer);

  const emailList = new List({ className: "email-list" });
  emailListContainer.addChild(emailList);

  // Compose button
  const composeButton = new Button({
    text: "Compose",
    onClick: () => showComposeForm(),
  });
  emailListContainer.addChild(composeButton);

  // Email view
  const emailViewContainer = new Container({
    className: "email-view-container",
    gap: "10px",
  });
  content.addChild(emailViewContainer);

  let currentEmailView = new EmailView(null);
  emailViewContainer.addChild(currentEmailView);

  // Sample emails
  const emails: Email[] = [
    {
      id: 1,
      from: "john@example.com",
      subject: "Hello",
      body: "Hi there!",
      read: false,
    },
    {
      id: 2,
      from: "jane@example.com",
      subject: "Meeting tomorrow",
      body: "Don't forget our meeting.",
      read: true,
    },
    {
      id: 3,
      from: "boss@example.com",
      subject: "Urgent: Report needed",
      body: "Please send the report ASAP.",
      read: false,
    },
  ];

  function renderEmails() {
    emailList.clear();
    emails.forEach((email) => {
      emailList.addItem(new EmailItem(email, selectEmail));
    });
  }

  function selectEmail(id: number) {
    const email = emails.find((e) => e.id === id);
    if (email) {
      email.read = true;
      currentEmailView = new EmailView(email);
      emailViewContainer.clear();
      emailViewContainer.addChild(currentEmailView);
      renderEmails(); // Re-render to update read/unread status
    }
  }

  function showComposeForm() {
    const composeForm = new Form({
      fields: [
        { type: "text", name: "to", label: "To:", required: true },
        { type: "text", name: "subject", label: "Subject:", required: true },
        { type: "textarea", name: "body", label: "Message:", required: true },
      ],
      onSubmit: (data) => {
        console.log("Sending email:", data);
        // Here you would typically send the email and update the sent folder
        emailViewContainer.clear();
        emailViewContainer.addChild(new EmailView(null));
      },
    });
    emailViewContainer.clear();
    emailViewContainer.addChild(composeForm);
  }

  renderEmails();

  return mainContainer;
}

const emailClientCSS = `
  .email-client {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
  }

  .content {
    display: grid;
    grid-template-columns: 150px 300px 1fr;
    gap: 20px;
  }

  .sidebar .ui-button button {
    width: 100%;
    text-align: left;
    padding: 10px;
  }

  .email-list-container {
    overflow-y: auto;
    max-height: 80vh;
  }

  .email-item {
    padding: 10px;
    border-bottom: 1px solid #40444B;
    cursor: pointer;
  }

  .email-item:hover {
    background-color: #40444B;
  }

  .email-item.unread {
    font-weight: bold;
  }

  .email-sender {
    font-size: 0.9em;
  }

  .email-subject {
    font-size: 1em;
  }

  .email-view {
    background-color: #40444B;
    padding: 20px;
    border-radius: 4px;
  }

  .email-body {
    margin-top: 20px;
    white-space: pre-wrap;
  }
`;

This example creates a more complex task management application using various components and demonstrating their interactions.

Adding and Using Plugins

The framework includes a plugin system that allows for extending its functionality.

// plugin-system.ts

/**
 * Interface for plugins that can be registered with the PluginManager.
 */
interface Plugin {
  readonly name: string;
  install(framework: any): void;
}

To add a plugin:

  • Create a plugin object that implements the Plugin interface:
const myPlugin: Plugin = {
  name: 'myPlugin',
  install(framework: any) {
    // Add new functionality to the framework
    framework.newMethod = () => console.log('New method added by plugin');
  }
};
  • Register the plugin with the framework:
import { framework } from './lib/framework';

framework.registerPlugin(myPlugin);
  • Use the new functionality provided by the plugin:
framework.newMethod(); // Outputs: "New method added by plugin"

This plugin system allows for modular extension of the framework's capabilities without modifying its core code. Here is a state management plugin:

import { Plugin } from '../plugin-system';

type Reducer<S, A> = (state: S, action: A) => S;
type Listener = () => void;
type Unsubscribe = () => void;

interface Store<S, A> {
  getState: () => S;
  dispatch: (action: A) => void;
  subscribe: (listener: Listener) => Unsubscribe;
}

const stateManagementPlugin: Plugin = {
  name: 'StateManagementPlugin',
  install(framework) {
    framework.createStore = function<S, A>(reducer: Reducer<S, A>, initialState: S): Store<S, A> {
      let state = initialState;
      const listeners: Listener[] = [];

      const store: Store<S, A> = {
        getState: () => state,
        dispatch: (action: A) => {
          state = reducer(state, action);
          listeners.forEach(listener => listener());
        },
        subscribe: (listener: Listener): Unsubscribe => {
          listeners.push(listener);
          return () => {
            const index = listeners.indexOf(listener);
            if (index > -1) listeners.splice(index, 1);
          };
        }
      };

      return store;
    };

    framework.View.prototype.connectToStore = function<S, P>(store: Store<S, any>, mapStateToProps: (state: S) => P) {
      const updateView = () => {
        const stateProps = mapStateToProps(store.getState());
        Object.assign(this, stateProps);
        this.render();
      };
      const unsubscribe = store.subscribe(updateView);
      updateView();

      // Clean up subscription when view is unmounted
      const originalUnmount = this.unmount;
      this.unmount = () => {
        unsubscribe();
        originalUnmount.call(this);
      };
    };
  }
};

export default stateManagementPlugin;

This plugin adds a Redux-like state management system to the framework:

  • It defines a createStore function that creates a store with getState, dispatch, and subscribe methods.
  • The connectToStore method is added to the View prototype, allowing views to easily connect to the store and update when the state changes.
  • It uses the reducer pattern to update the state immutably.
  • The plugin handles subscription cleanup when a view is unmounted.

Keywords

typescript

FAQs

Package last updated on 02 Jul 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.