UNPKG

ng-hub-ui-board

Version:

An Angular-based Kanban board component with Trello-like drag-and-drop, customizable columns, and straightforward event handling.

747 lines (554 loc) β€’ 25.1 kB
# ng-hub-ui-board [EspaΓ±ol](./README.es.md) | **English** [![NPM Version](https://img.shields.io/npm/v/ng-hub-ui-board.svg)](https://www.npmjs.com/package/ng-hub-ui-board) > **⚠️ BREAKING CHANGES:** Version 21.1.0 removes the public stylesheet entry point. Styles are now encapsulated within the component. Please read the [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) file before upgrading. ## Documentation and Live Examples This package is part of [Hub UI](https://hubui.dev/), a collection of Angular component libraries for standalone apps. - Docs: https://hubui.dev/board/overview/ - Live examples: https://hubui.dev/board/examples/ - Hub UI: https://hubui.dev/ ## 🧩 Library Family `ng-hub-ui` This library is part of the **ng-hub-ui** ecosystem: - [ng-hub-ui-accordion](https://www.npmjs.com/package/ng-hub-ui-accordion) (deprecated β€” use ng-hub-ui-panels) - [ng-hub-ui-action-sheet](https://www.npmjs.com/package/ng-hub-ui-action-sheet) - [ng-hub-ui-avatar](https://www.npmjs.com/package/ng-hub-ui-avatar) - [ng-hub-ui-board](https://www.npmjs.com/package/ng-hub-ui-board) ← You are here - [ng-hub-ui-breadcrumbs](https://www.npmjs.com/package/ng-hub-ui-breadcrumbs) - [ng-hub-ui-calendar](https://www.npmjs.com/package/ng-hub-ui-calendar) - [ng-hub-ui-dropdown](https://www.npmjs.com/package/ng-hub-ui-dropdown) - [ng-hub-ui-ds](https://www.npmjs.com/package/ng-hub-ui-ds) - [ng-hub-ui-forms](https://www.npmjs.com/package/ng-hub-ui-forms) - [ng-hub-ui-history](https://www.npmjs.com/package/ng-hub-ui-history) - [ng-hub-ui-milestones](https://www.npmjs.com/package/ng-hub-ui-milestones) - [ng-hub-ui-modal](https://www.npmjs.com/package/ng-hub-ui-modal) - [ng-hub-ui-nav](https://www.npmjs.com/package/ng-hub-ui-nav) - [ng-hub-ui-paginable](https://www.npmjs.com/package/ng-hub-ui-paginable) - [ng-hub-ui-panels](https://www.npmjs.com/package/ng-hub-ui-panels) - [ng-hub-ui-portal](https://www.npmjs.com/package/ng-hub-ui-portal) - [ng-hub-ui-skeleton](https://www.npmjs.com/package/ng-hub-ui-skeleton) - [ng-hub-ui-sortable](https://www.npmjs.com/package/ng-hub-ui-sortable) - [ng-hub-ui-stepper](https://www.npmjs.com/package/ng-hub-ui-stepper) - [ng-hub-ui-utils](https://www.npmjs.com/package/ng-hub-ui-utils) ## Description A flexible and powerful board component for Angular applications, perfect for implementing Kanban-style boards, task management systems, or any drag-and-drop card-based interface. Similar to Trello boards, this component allows you to create interactive columns with draggable cards. ## Features - 🎯 **Standalone component** - Modern Angular approach with minimal setup - πŸ”„ **Native drag and drop** - Built on the in-ecosystem `ng-hub-ui-utils` core, with no third-party UI or CDK dependencies - 🎨 **Fully customizable drag visuals** - Custom templates for drag previews and drop placeholders - βš™οΈ **Configurable drag behavior** - Choose between ghost, hide, or collapse modes for dragged elements - πŸ“± **Responsive design** - Works seamlessly across desktop, tablet, and mobile devices - 🎭 **Highly customizable** - Custom templates for cards, headers, footers, and drag interactions - πŸ”§ **Bootstrap compatible** - Integrates perfectly with Bootstrap 5 design system - ⚑ **Virtual scrolling** - Supports infinite scroll with end-detection for performance - 🎨 **Custom styling** - CSS custom properties for easy theming and customization - πŸ”’ **Granular control** - Enable/disable functionality at board, column, or card level - 🏷️ **TypeScript support** - Full type safety with generic interfaces - β™Ώ **Accessibility ready** - Follows WAI-ARIA best practices for drag-and-drop - πŸͺΆ **Lightweight** - No third-party UI or CDK dependencies; relies only on the shared `ng-hub-ui-utils` core ## Installation ```bash # Install the component and its required peer dependency npm install ng-hub-ui-board ng-hub-ui-utils ``` Or using yarn: ```bash yarn add ng-hub-ui-board ng-hub-ui-utils ``` **Note:** `@angular/cdk` is not required. The board uses the shared `ng-hub-ui-utils` native drag-and-drop core (a mandatory peer dependency since `22.1.0`) β€” there are no third-party UI or CDK dependencies. ## Quick Start Here’s a quick example to get you started with `ng-hub-ui-board` using the standalone component approach. ### 1. Setup your board model ```ts import { signal } from '@angular/core'; import { Board } from 'ng-hub-ui-board'; export const board = signal<Board>({ title: 'Project Sprint', columns: [ { title: 'To Do', cards: [ { title: 'Login page', description: 'Build login form with validation' }, { title: 'Landing hero', description: 'Implement hero section' } ] }, { title: 'In Progress', cards: [{ title: 'Set up CI/CD', description: 'Add GitHub Actions' }] }, { title: 'Done', cards: [{ title: 'Project scaffold', description: 'Initial Angular setup' }] } ] }); ``` ### 2. Create your component ```ts import { Component } from '@angular/core'; import { HubBoardComponent, CardTemplateDirective, BoardColumnHeaderDirective, BoardColumnFooterDirective, BoardCard } from 'ng-hub-ui-board'; @Component({ selector: 'board-demo', standalone: true, imports: [HubBoardComponent, CardTemplateDirective, BoardColumnHeaderDirective, BoardColumnFooterDirective], templateUrl: './board-demo.component.html' }) export class BoardDemoComponent { board = board; handleCardClick(card: BoardCard) { console.log('Card clicked:', card); } handleCardMoved(event: any) { console.log('Card moved:', event); } } ``` ### 3. Use in your template ```html <hub-board [board]="board()" (onCardClick)="handleCardClick($event)" (onCardMoved)="handleCardMoved($event)"> <ng-template cardTpt let-card="item"> <strong>{{ card.title }}</strong> <p>{{ card.description }}</p> </ng-template> </hub-board> ``` This code block provides a minimal and functional example for both beginners and intermediate users. ## Usage The component can be used in two ways: ### 1. Standalone Component Import (Recommended) ```typescript import { Component } from '@angular/core'; import { HubBoardComponent, CardTemplateDirective, BoardColumnHeaderDirective, BoardColumnFooterDirective } from 'ng-hub-ui-board'; @Component({ selector: 'app-my-component', standalone: true, imports: [HubBoardComponent, CardTemplateDirective, BoardColumnHeaderDirective, BoardColumnFooterDirective], template: ` <hub-board [board]="board" (onCardClick)="handleCardClick($event)" (onCardMoved)="handleCardMoved($event)"> <!-- Templates go here --> </hub-board> ` }) export class MyComponent { // ... component logic } ``` ### 2. Module Import (Legacy) ```typescript import { NgModule } from '@angular/core'; import { BoardModule } from 'ng-hub-ui-board'; @NgModule({ imports: [BoardModule] // ... rest of the module configuration }) export class AppModule {} ``` ## Templates The component uses multiple templates for customization. If you're using the standalone approach, remember to import the corresponding directives for each template you plan to use. ### Standard Templates ### Card Template (CardTemplateDirective) Used to customize how each card is rendered within the columns. This template gives you complete control over the card's appearance and structure. ```html <ng-template cardTpt let-card="card"> <div class="custom-card"> <h3>{{ card.title }}</h3> <p>{{ card.description }}</p> <div class="card-metadata"> <span class="priority">{{ card.data?.priority }}</span> <span class="due-date">{{ card.data?.dueDate | date }}</span> </div> </div> </ng-template> ``` ### Column Header Template (BoardColumnHeaderDirective) Used to customize the header of each column. Perfect for adding column-specific actions, showing card counts, or adding filtering options. ```html <ng-template columnHeaderTpt let-column="column"> <div class="custom-header"> <h2>{{ column.title }}</h2> <span class="card-count">{{ column.cards.length }} items</span> <div class="column-actions"> <button (click)="addCard(column)">Add Card</button> <button (click)="filterColumn(column)">Filter</button> </div> </div> </ng-template> ``` ### Column Footer Template (BoardColumnFooterDirective) Used to add a footer to each column. Useful for summary information, quick actions, or column-specific controls. ```html <ng-template columnFooterTpt let-column="column"> <div class="custom-footer"> <div class="column-summary"> <span>Total: {{ column.cards.length }}</span> <span>Priority Items: {{ getPriorityItems(column) }}</span> </div> <button (click)="quickAddCard(column)">Quick Add</button> </div> </ng-template> ``` ### Drag-and-Drop Templates #### Card Drag Preview Template (CardDragPreviewDirective) Customize the visual element that follows the cursor when dragging cards. The template receives the dragged card and its source column as context. ```html <ng-template cardDragPreview let-card="card" let-column="column"> <div class="custom-drag-preview"> <div class="preview-header"> <span class="badge">{{ column.title }}</span> </div> <h4>{{ card.title }}</h4> <p class="preview-description">{{ card.description }}</p> </div> </ng-template> ``` **Context variables:** - `card`: The card being dragged - `column`: The source column of the card #### Card Placeholder Template (CardPlaceholderDirective) Customize the drop zone appearance when dragging cards between or within columns. ```html <ng-template cardPlaceholder> <div class="custom-card-placeholder"> <span class="placeholder-icon">πŸ“₯</span> <p>Drop card here</p> </div> </ng-template> ``` #### Column Drag Preview Template (ColumnDragPreviewDirective) Customize the visual element that follows the cursor when dragging columns. The template receives the dragged column as context. ```html <ng-template columnDragPreview let-column="column"> <div class="custom-column-preview"> <h3>{{ column.title }}</h3> <span class="card-count">{{ column.cards.length }} cards</span> </div> </ng-template> ``` **Context variable:** - `column`: The column being dragged #### Column Placeholder Template (ColumnPlaceholderDirective) Customize the drop zone appearance when reordering columns. ```html <ng-template columnPlaceholder> <div class="custom-column-placeholder"> <span class="placeholder-text">Drop column here</span> </div> </ng-template> ``` ## Events The `HubBoardComponent` emits several events to help you interact with user actions such as clicking cards, moving items, or reaching scroll limits. ### onCardClick Emitted when a card is clicked. ```html <hub-board [board]="board" (onCardClick)="handleCardClick($event)"> </hub-board> ``` **Type:** `EventEmitter<BoardCard>` **Example:** ```ts handleCardClick(card: BoardCard) { console.log('Card clicked:', card.title); } ``` --- ### onCardMoved Emitted when a card is moved either within the same column or between columns. ```html <hub-board [board]="board" (onCardMoved)="handleCardMoved($event)"> </hub-board> ``` **Type:** `EventEmitter<CardDragDropEvent>` **Example:** ```ts import { CardDragDropEvent } from 'ng-hub-ui-board'; handleCardMoved(event: CardDragDropEvent) { const card = event.item.data; const from = event.previousContainer.data; const to = event.container.data; console.log(`Moved "${card.title}" from "${from.title}" to "${to.title}"`); } ``` --- ### onColumnMoved Emitted when a column is reordered via drag and drop. ```html <hub-board [board]="board" (onColumnMoved)="handleColumnMoved($event)"> </hub-board> ``` **Type:** `EventEmitter<ColumnDragDropEvent>` **Example:** ```ts import { ColumnDragDropEvent } from 'ng-hub-ui-board'; handleColumnMoved(event: ColumnDragDropEvent) { console.log(`Column moved from ${event.previousIndex} to ${event.currentIndex}`); } ``` --- ### reachedEnd Emitted when a user scrolls to the end of a column. Useful for triggering lazy-loading of additional cards. ```html <div style="height: 512px;"> <hub-board [board]="board" (reachedEnd)="loadMoreCards($event)"></hub-board> </div> ``` **Type:** `EventEmitter<ReachedEndEvent<BoardColumn>>` **Event Structure:** ```typescript interface ReachedEndEvent<T = any> { index: number; // Index of the column that reached the end data: T; // The BoardColumn object itself } ``` **Example:** ```ts loadMoreCards(event: ReachedEndEvent) { const columnIndex = event.index; const column = event.data; // event.data is the BoardColumn object if (!column) { return; } console.log(`Loading more cards for column: ${column.title}`); // Simulate API call to load more cards setTimeout(() => { const newCards = this.generateCards(5); // Update the board with new cards this.board.update(currentBoard => ({ ...currentBoard, columns: currentBoard.columns?.map((col, index) => index === columnIndex ? { ...col, cards: [...col.cards, ...newCards] } : col ) || [] })); }, 1000); } ``` > ℹ️ **Important:** To enable scroll detection, the board must be placed inside a container with a fixed height constraint. ## Inputs The following inputs are available on the `HubBoardComponent`: | Input | Type | Description | Default | | ----------------------- | --------------- | ------------------------------------------------------------------------------------------------------ | ------------ | | `board` | `Signal<Board>` | The board object containing columns and cards | `undefined` | | `columnSortingDisabled` | `boolean` | Disables drag-and-drop sorting of columns | `false` | | `dragBehavior` | `DragBehavior` | Controls how dragged elements behave visually: `'ghost'` (semi-transparent), `'hide'`, or `'collapse'` | `'collapse'` | | `variant` | `string` | Semantic accent of the drag/drop placeholder. Built-in values (`'primary'` / `'success'` / `'danger'` / `'warning'` / `'info'`) use the exact design-system tints; any other string is also accepted β€” the board reads `--hub-sys-color-<variant>` from the host application | `'primary'` | ## Outputs These outputs are emitted by the component during user interaction: | Output | Type | Description | | --------------- | ----------------------------------- | ------------------------------------------------------------------------- | | `onCardClick` | `EventEmitter<BoardCard>` | Triggered when a card is clicked | | `onCardMoved` | `EventEmitter<CardDragDropEvent>` | Emitted when a card is moved (within or across columns) | | `onColumnMoved` | `EventEmitter<ColumnDragDropEvent>` | Emitted when a column is reordered via drag and drop | | `reachedEnd` | `EventEmitter<ReachedEndEvent>` | Triggered when the user scrolls to the bottom of a column (for lazy load) | ## Interfaces ### Board Main container interface that represents the entire board structure. Used to define the overall board configuration including its columns and general styling. ```typescript interface Board<T = any> { id?: number; title: string; description?: string; columns?: BoardColumn<T>[]; classlist?: string[]; style?: { [key: string]: any }; } ``` ### BoardColumn Represents a single column in the board. Used to configure individual columns, their cards, and column-specific behavior like drag-and-drop rules. ```typescript interface BoardColumn<T = any> { id?: number; boardId?: number; title: string; description?: string; cards: BoardCard<T>[]; style?: { [key: string]: any }; classlist?: string[] | string; disabled?: boolean; cardSortingDisabled?: boolean; } ``` ### BoardCard Represents individual cards within columns. Used to define card content and behavior, including custom data and styling. ```typescript interface BoardCard<T = any> { id?: number; columnId?: number; title: string; description?: string; data?: T; classlist?: string[]; style?: { [key: string]: any }; disabled?: boolean; } ``` ### CardDragDropEvent Event interface emitted when a card is moved. Provides all information about the drag-and-drop operation. ```typescript interface CardDragDropEvent<T = any> { previousIndex: number; currentIndex: number; container: BoardDropContainer<BoardColumn>; previousContainer: BoardDropContainer<BoardColumn>; item: BoardDragItem<BoardCard>; isPointerOverContainer: boolean; distance?: { x: number; y: number }; dropPoint?: { x: number; y: number }; } ``` ### ColumnDragDropEvent Event interface emitted when a column is moved. Provides all information about the column reordering operation. ```typescript interface ColumnDragDropEvent { previousIndex: number; currentIndex: number; container: BoardDropContainer<BoardColumn[]>; previousContainer: BoardDropContainer<BoardColumn[]>; item: BoardDragItem<BoardColumn>; isPointerOverContainer: boolean; distance?: { x: number; y: number }; dropPoint?: { x: number; y: number }; } ``` ### DragBehavior Type definition for controlling how dragged elements behave visually during drag operations. ```typescript type DragBehavior = 'ghost' | 'hide' | 'collapse'; ``` **Values:** - `'ghost'`: Element becomes semi-transparent (50% opacity) but remains visible - `'hide'`: Element is hidden but still occupies its space (invisible placeholder) - `'collapse'`: Element is completely hidden and its space is collapsed (default) ## 🧩 Styling `ng-hub-ui-board` is fully style-configurable through CSS custom properties. For a complete and up-to-date token catalog, see [CSS Variables Reference](./docs/css-variables-reference.md). ### πŸ”— Import styles ```scss @use 'ng-hub-ui-board/src/lib/styles/board.scss'; ``` ### 🎨 Semantic accent (`variant`) The drag/drop placeholder is driven by a single accent token. Pass the `variant` input to recolour the drop zone with a semantic accent: ```html <hub-board [board]="board()" variant="success"></hub-board> ``` Built-in variants (`primary` / `success` / `danger` / `warning` / `info`) use the exact design-system tints. Any other string is also accepted β€” the board reads `--hub-sys-color-<variant>` from the host application, so a custom accent palette interconnects with no changes to this library. Defaults to `primary`. Under the hood this re-bases the new `--hub-board-accent` token (and its subtle tint `--hub-board-accent-subtle`, derived via `color-mix`), through which the placeholder border and background colours resolve: | Token | Description | | ---------------------------- | -------------------------------------------------------------------------- | | `--hub-board-accent` | Semantic accent of the drag/drop placeholder (re-based per `variant`) | | `--hub-board-accent-subtle` | Subtle tint of the accent, used as the placeholder background | ### 🧡 `hub-board-theme()` Sass mixin Theme a `<hub-board>` in a single call. Every parameter is optional and defaults to `null`, so only the ones you pass are emitted as `--hub-board-*` overrides; the rest keep their defaults. Token-based, with no Bootstrap dependency. ```scss @use 'ng-hub-ui-board/styles/mixins/board-theme' as *; .sprint-board { @include hub-board-theme( $accent: var(--hub-sys-color-success), $column-bg: #f6f8fa, $card-border-radius: 0.75rem, $columns-gap: 1.25rem ); } ``` Available parameters cover the accent, container/column/card colours, borders and radius, columns gap, card padding and shadow. ### πŸŽ› Quick customization example (framework-agnostic) ```scss .hub-board { --hub-board-columns-gap: 1.25rem; --hub-board-column-bg: #f8f9fa; --hub-board-card-bg: #ffffff; --hub-board-card-border-color: #d0d7de; --hub-board-placeholder-border-color: #0d6efd; } ``` ### πŸ”Œ Bootstrap integration (optional) ```scss .hub-board { --hub-board-column-bg: var(--bs-light); --hub-board-card-bg: var(--bs-body-bg); --hub-board-card-border-color: var(--bs-border-color); --hub-board-placeholder-border-color: var(--bs-primary); } ``` ## Real-world Use Cases The `ng-hub-ui-board` component is versatile and has been used in a variety of real-world applications, such as: - **Project Management Tools** – Visualize task progress across stages (To Do, In Progress, Done). - **Support Ticket Boards** – Organize support tickets by urgency, team, or status. - **Recruitment Pipelines** – Track candidates through different phases of hiring. - **CRM Systems** – Manage leads and customers in pipeline-style workflows. - **Editorial Calendars** – Schedule and organize content by publication status. Each case benefits from customizable columns, card templates, and event outputs to integrate with your app logic. ## Troubleshooting Here are some common issues and how to resolve them: ### πŸ”„ Drag and drop not working - **Check dependencies**: Ensure the `ng-hub-ui-utils` peer dependency is installed (`npm install ng-hub-ui-utils`) - **Reactive data**: Verify your board data is reactive (using `signal()`, `Observable`, or proper change detection) - **Browser compatibility**: Ensure your target browsers support the HTML5 Drag and Drop API ### πŸ“ Scroll detection not triggering `reachedEnd` - **Height constraints**: The `<hub-board>` element or its parent must have a `max-height` or fixed height - **Overflow setting**: Ensure `overflow: auto` or `overflow-y: scroll` is applied to enable scrolling - **Content length**: Make sure there's enough content to actually trigger scrolling ### 🎨 Styles not applying - **Import path**: Confirm you've imported the SCSS base styles in your global `styles.scss`: ```scss @use 'ng-hub-ui-board/src/lib/styles/board.scss'; ``` - **CSS custom properties**: Check that your custom CSS variables follow the `--hub-*` naming convention - **Style specificity**: Ensure your custom styles have sufficient specificity to override defaults ### 🧩 Templates not rendering - **Import directives**: When using standalone components, import the template directives: ```typescript imports: [HubBoardComponent, CardTemplateDirective, BoardColumnHeaderDirective]; ``` - **Template syntax**: Verify you're using the correct template selectors (`cardTpt`, `columnHeaderTpt`, `columnFooterTpt`) ### πŸ› οΈ Runtime errors - **"Cannot read property 'cards' of undefined"**: Initialize your board signal properly: ```typescript board = signal<Board>({ title: 'My Board', columns: [] }); ``` - **Type errors**: Ensure your data matches the `Board`, `BoardColumn`, and `BoardCard` interfaces - **Signal updates**: Use `.set()` or `.update()` methods to modify signal values ### 🎯 Performance issues - **Large datasets**: Consider implementing virtual scrolling for columns with many cards - **Memory leaks**: Ensure proper cleanup of event listeners and subscriptions - **Change detection**: Use `OnPush` change detection strategy when possible If problems persist, open an issue at: https://github.com/carlos-morcillo/ng-hub-ui-board/issues ## Contributing Contributions are welcome! Here's how you can help: 1. Fork the repository 2. Create your feature branch: `git checkout -b feature/my-new-feature` 3. Commit your changes: `git commit -am 'Add some feature'` 4. Push to the branch: `git push origin feature/my-new-feature` 5. Submit a pull request ## Support the Project If you find this project helpful and would like to support its development, you can buy me a coffee: [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/carlosmorcillo) Your support is greatly appreciated and helps maintain and improve this project! ## License This project is licensed under the **Creative Commons Attribution 4.0 International License (CC BY 4.0)**. ### What this means: βœ… **You can:** - Use commercially and non-commercially - Modify, adapt, and create derivatives - Distribute and redistribute in any format - Use in private and public projects πŸ“‹ **You must:** - Give appropriate credit to the original authors - Provide a link to the license - Indicate if changes were made ### Example attribution: ``` Based on ng-hub-ui-board by [Carlos Morcillo](https://www.carlosmorcillo.com) Original: https://github.com/carlos-morcillo/ng-hub-ui-board License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/) ``` For full license details, see the [LICENSE](LICENSE) file. --- Made with ❀️ by [Carlos Morcillo FernΓ‘ndez](https://www.carlosmorcillo.com/)