CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-rc-tabs

React tabs UI component providing comprehensive, accessible, and customizable tabbed interfaces

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

editable-tabs.mddocs/

Editable Tabs

Interactive tab management with add/remove functionality and customizable controls. Editable tabs allow users to dynamically create and destroy tabs at runtime, providing a flexible interface for document editing, multi-view applications, and dynamic content management.

Capabilities

Editable Configuration

Comprehensive configuration for interactive tab management functionality.

/**
 * Configuration for editable tabs functionality
 * Enables dynamic tab creation and removal with customizable UI
 */
interface EditableConfig {
  /** 
   * Callback function handling both add and remove operations
   * @param type - Type of edit operation ('add' or 'remove')  
   * @param info - Context information about the operation
   */
  onEdit: (
    type: 'add' | 'remove',
    info: { 
      key?: string; 
      event: React.MouseEvent | React.KeyboardEvent 
    }
  ) => void;
  
  /** Whether to show the add button in the tab bar */
  showAdd?: boolean;
  
  /** Custom icon for remove buttons on closable tabs */
  removeIcon?: React.ReactNode;
  
  /** Custom icon for the add button */
  addIcon?: React.ReactNode;
}

/**
 * Edit operation context information
 */
interface EditInfo {
  /** Tab key for remove operations (undefined for add operations) */
  key?: string;
  /** The mouse or keyboard event that triggered the operation */
  event: React.MouseEvent | React.KeyboardEvent;
}

Usage Examples:

import Tabs from "rc-tabs";
import { PlusOutlined, CloseOutlined } from "@ant-design/icons";

function EditableTabs() {
  const [items, setItems] = useState([
    { key: '1', label: 'Tab 1', children: <div>Content 1</div>, closable: true },
    { key: '2', label: 'Tab 2', children: <div>Content 2</div>, closable: true },
  ]);
  const [activeKey, setActiveKey] = useState('1');
  
  const handleEdit = (type: 'add' | 'remove', info: { key?: string }) => {
    if (type === 'add') {
      const newKey = `tab-${Date.now()}`;
      const newTab = {
        key: newKey,
        label: `New Tab ${items.length + 1}`,
        children: <div>New tab content</div>,
        closable: true,
      };
      setItems([...items, newTab]);
      setActiveKey(newKey);
    } else if (type === 'remove' && info.key) {
      const newItems = items.filter(item => item.key !== info.key);
      setItems(newItems);
      
      // If removing active tab, switch to another tab
      if (activeKey === info.key && newItems.length > 0) {
        setActiveKey(newItems[0].key);
      }
    }
  };
  
  return (
    <Tabs
      activeKey={activeKey}
      onChange={setActiveKey}
      items={items}
      editable={{
        onEdit: handleEdit,
        showAdd: true,
        addIcon: <PlusOutlined />,
        removeIcon: <CloseOutlined />,
      }}
    />
  );
}

Add Tab Functionality

Dynamic tab creation with customizable add button and behavior.

/**
 * Add tab configuration and behavior
 */
interface AddTabConfig {
  /** Whether to display the add button in tab bar */
  showAdd?: boolean;
  /** Custom icon for the add button */
  addIcon?: React.ReactNode;
  /** Callback for add operations */
  onAdd?: (event: React.MouseEvent | React.KeyboardEvent) => void;
}

Add Tab Examples:

// Basic add functionality
function BasicAddTabs() {
  const [tabs, setTabs] = useState(initialTabs);
  
  const addTab = (type: 'add' | 'remove', info: { event: Event }) => {
    if (type === 'add') {
      const newTab = {
        key: `new-${Date.now()}`,
        label: 'New Tab',
        children: <div>Fresh content</div>,
        closable: true,
      };
      setTabs(prev => [...prev, newTab]);
    }
  };
  
  return (
    <Tabs
      items={tabs}
      editable={{
        onEdit: addTab,
        showAdd: true, // Shows + button in tab bar
      }}
    />
  );
}

// Custom add button with confirmation
function ConfirmAddTabs() {
  const [tabs, setTabs] = useState(initialTabs);
  
  const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
    if (type === 'add') {
      const name = prompt('Enter tab name:');
      if (name) {
        const newTab = {
          key: `tab-${Date.now()}`,
          label: name,
          children: <div>{name} content</div>,
          closable: true,
        };
        setTabs(prev => [...prev, newTab]);
      }
    }
  };
  
  return (
    <Tabs
      items={tabs}
      editable={{
        onEdit: handleEdit,
        showAdd: true,
        addIcon: <span>+ Add</span>, // Custom add button text
      }}
    />
  );
}

Remove Tab Functionality

Dynamic tab removal with confirmation and cleanup options.

/**
 * Remove tab configuration and behavior
 */
interface RemoveTabConfig {
  /** Custom icon for remove buttons */
  removeIcon?: React.ReactNode;
  /** Callback for remove operations */
  onRemove?: (key: string, event: React.MouseEvent | React.KeyboardEvent) => void;
}

Remove Tab Examples:

// Basic remove functionality
function BasicRemoveTabs() {
  const [tabs, setTabs] = useState(initialTabs);
  const [activeKey, setActiveKey] = useState('1');
  
  const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
    if (type === 'remove' && info.key) {
      setTabs(prev => prev.filter(tab => tab.key !== info.key));
      
      // Handle active tab removal
      if (activeKey === info.key) {
        const remainingTabs = tabs.filter(tab => tab.key !== info.key);
        setActiveKey(remainingTabs.length > 0 ? remainingTabs[0].key : '');
      }
    }
  };
  
  return (
    <Tabs
      activeKey={activeKey}
      onChange={setActiveKey}
      items={tabs.map(tab => ({ ...tab, closable: true }))}
      editable={{
        onEdit: handleEdit,
        removeIcon: <CloseOutlined style={{ fontSize: 12 }} />,
      }}
    />
  );
}

// Remove with confirmation
function ConfirmRemoveTabs() {
  const [tabs, setTabs] = useState(initialTabs);
  
  const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
    if (type === 'remove' && info.key) {
      const tabToRemove = tabs.find(tab => tab.key === info.key);
      const confirmed = window.confirm(
        `Are you sure you want to close "${tabToRemove?.label}"?`
      );
      
      if (confirmed) {
        setTabs(prev => prev.filter(tab => tab.key !== info.key));
      }
    }
  };
  
  return (
    <Tabs
      items={tabs}
      editable={{
        onEdit: handleEdit,
        removeIcon: <span>×</span>,
      }}
    />
  );
}

Advanced Editable Patterns

Complex scenarios with validation, limits, and custom behaviors.

/**
 * Advanced editable tab patterns and constraints
 */
interface AdvancedEditableConfig {
  /** Maximum number of tabs allowed */
  maxTabs?: number;
  /** Minimum number of tabs required */
  minTabs?: number;
  /** Validation function for tab operations */
  validateOperation?: (type: 'add' | 'remove', context: any) => boolean;
  /** Custom behavior for different tab types */
  tabTypeHandlers?: Record<string, EditHandler>;
}

Advanced Examples:

// Limited tabs with validation
function LimitedEditableTabs() {
  const [tabs, setTabs] = useState(initialTabs);
  const maxTabs = 5;
  const minTabs = 1;
  
  const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
    if (type === 'add') {
      if (tabs.length >= maxTabs) {
        alert(`Maximum ${maxTabs} tabs allowed`);
        return;
      }
      
      const newTab = {
        key: `tab-${Date.now()}`,
        label: `Tab ${tabs.length + 1}`,
        children: <div>Content {tabs.length + 1}</div>,
        closable: tabs.length + 1 > minTabs, // Don't allow closing if at minimum
      };
      setTabs(prev => [...prev, newTab]);
      
    } else if (type === 'remove' && info.key) {
      if (tabs.length <= minTabs) {
        alert(`Minimum ${minTabs} tab required`);
        return;
      }
      
      setTabs(prev => prev.filter(tab => tab.key !== info.key));
    }
  };
  
  return (
    <Tabs
      items={tabs}
      editable={{
        onEdit: handleEdit,
        showAdd: tabs.length < maxTabs,
      }}
    />
  );
}

// Type-specific tab handling
function TypedEditableTabs() {
  const [tabs, setTabs] = useState([
    { key: '1', type: 'document', label: 'Document 1', closable: true },
    { key: '2', type: 'settings', label: 'Settings', closable: false },
  ]);
  
  const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
    if (type === 'add') {
      const tabType = 'document'; // Could be dynamic
      const newTab = {
        key: `${tabType}-${Date.now()}`,
        type: tabType,
        label: `New ${tabType}`,
        children: createContentByType(tabType),
        closable: tabType !== 'settings', // Settings tabs can't be closed
      };
      setTabs(prev => [...prev, newTab]);
      
    } else if (type === 'remove' && info.key) {
      const tab = tabs.find(t => t.key === info.key);
      
      if (tab?.type === 'settings') {
        alert('Settings tabs cannot be closed');
        return;
      }
      
      // Save document before closing
      if (tab?.type === 'document') {
        const shouldSave = window.confirm('Save document before closing?');
        if (shouldSave) {
          saveDocument(tab.key);
        }
      }
      
      setTabs(prev => prev.filter(tab => tab.key !== info.key));
    }
  };
  
  return (
    <Tabs
      items={tabs}
      editable={{
        onEdit: handleEdit,
        showAdd: true,
        addIcon: <span>+ New Document</span>,
      }}
    />
  );
}

Keyboard Support

Keyboard shortcuts and accessibility for editable tabs.

/**
 * Keyboard interactions for editable tabs
 * - Ctrl/Cmd + T: Add new tab (if showAdd is true)
 * - Ctrl/Cmd + W: Close current tab (if closable)
 * - Alt + Click: Alternative close action
 */
interface EditableKeyboardSupport {
  addShortcut: 'Ctrl+T' | 'Cmd+T';
  closeShortcut: 'Ctrl+W' | 'Cmd+W';
  altCloseClick: 'Alt+Click';
}

Keyboard Integration Example:

function KeyboardEditableTabs() {
  const [tabs, setTabs] = useState(initialTabs);
  const [activeKey, setActiveKey] = useState('1');
  
  useEffect(() => {
    const handleKeyboard = (e: KeyboardEvent) => {
      if ((e.ctrlKey || e.metaKey) && e.key === 't') {
        e.preventDefault();
        addNewTab();
      } else if ((e.ctrlKey || e.metaKey) && e.key === 'w') {
        e.preventDefault();
        closeCurrentTab();
      }
    };
    
    document.addEventListener('keydown', handleKeyboard);
    return () => document.removeEventListener('keydown', handleKeyboard);
  }, [activeKey, tabs]);
  
  const addNewTab = () => {
    const newTab = {
      key: `tab-${Date.now()}`,
      label: 'New Tab',
      children: <div>New content</div>,
      closable: true,
    };
    setTabs(prev => [...prev, newTab]);
    setActiveKey(newTab.key);
  };
  
  const closeCurrentTab = () => {
    const currentTab = tabs.find(tab => tab.key === activeKey);
    if (currentTab?.closable) {
      handleEdit('remove', { key: activeKey, event: new KeyboardEvent('keydown') });
    }
  };
  
  const handleEdit = (type: 'add' | 'remove', info: EditInfo) => {
    // Standard edit handling...
  };
  
  return (
    <Tabs
      activeKey={activeKey}
      onChange={setActiveKey}
      items={tabs}
      editable={{
        onEdit: handleEdit,
        showAdd: true,
      }}
    />
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-rc-tabs

docs

advanced-features.md

animation-transitions.md

core-interface.md

editable-tabs.md

index.md

tab-configuration.md

tile.json