blumbaben
v1.0.2
Published
A lightweight TypeScript React hook to show a toolbar when an input is focused.
Maintainers
Readme
blumbaben
A lightweight TypeScript React hook library for adding formatting toolbars to text inputs and textareas. Show contextual formatting options when users focus on input fields.
🔭 Explore the live Storybook showcase at blumbaben.vercel.app.
✨ Features
- 🎯 Global State Management - Single toolbar instance shared across all inputs
- 📱 Smart Positioning - Automatic toolbar positioning with customizable placement
- 🎨 Flexible Styling - Bring your own UI components and styles
- ⚡ TypeScript Support - Full type safety and excellent DX
- 🪶 Lightweight - Minimal bundle size with no external dependencies
- 🔧 Configurable - Customizable behavior and positioning
- ♿ Accessible - Built with accessibility in mind
📦 Installation
npm install blumbabenbun add blumbabenpnpm add blumbaben🚀 Quick Start
Basic Usage with Hook
import React, { useState } from 'react';
import { useFormattingToolbar } from 'blumbaben';
function MyComponent() {
const [content, setContent] = useState('');
const { getInputProps, isVisible, applyFormat } = useFormattingToolbar();
return (
<div>
<textarea
{...getInputProps()}
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Focus me to see the toolbar!"
/>
{isVisible && (
<div className="toolbar">
<button onClick={() => applyFormat((text) => text.toUpperCase())}>UPPERCASE</button>
<button onClick={() => applyFormat((text) => `**${text}**`)}>Bold</button>
</div>
)}
</div>
);
}Using the FormattingToolbar Component
import React, { useState } from 'react';
import { useFormattingToolbar, FormattingToolbar } from 'blumbaben';
function MyComponent() {
const [content, setContent] = useState('');
const { getInputProps } = useFormattingToolbar();
return (
<div>
<textarea
{...getInputProps()}
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Focus me to see the toolbar!"
/>
<FormattingToolbar>
{(applyFormat) => (
<div className="flex gap-2 p-2 bg-white border rounded shadow">
<button
onClick={() => applyFormat((text) => text.toUpperCase())}
className="px-2 py-1 bg-blue-500 text-white rounded"
>
UPPERCASE
</button>
<button
onClick={() => applyFormat((text) => `**${text}**`)}
className="px-2 py-1 bg-green-500 text-white rounded"
>
Bold
</button>
<button
onClick={() => applyFormat((text) => text.replace(/\n/g, ' '))}
className="px-2 py-1 bg-red-500 text-white rounded"
>
Remove Line Breaks
</button>
</div>
)}
</FormattingToolbar>
</div>
);
}Using the Higher-Order Component
import React, { useState } from 'react';
import { withFormattingToolbar, FormattingToolbar } from 'blumbaben';
// Enhance your existing textarea component
const MyTextarea = ({ value, onChange, ...props }) => <textarea value={value} onChange={onChange} {...props} />;
const TextareaWithToolbar = withFormattingToolbar(MyTextarea);
function App() {
const [content, setContent] = useState('');
return (
<div>
<TextareaWithToolbar
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Focus me to see the toolbar!"
/>
<FormattingToolbar>
{(applyFormat) => (
<div className="toolbar-buttons">
<button onClick={() => applyFormat((text) => text.toUpperCase())}>UPPERCASE</button>
<button onClick={() => applyFormat((text) => text.toLowerCase())}>lowercase</button>
</div>
)}
</FormattingToolbar>
</div>
);
}🔧 Configuration
Toolbar Configuration Options
interface ToolbarConfig {
// Custom positioning function
getPosition?: (element: TextInputElement) => ToolbarPosition;
// Delay before hiding toolbar after blur (ms)
hideDelay?: number; // default: 500
// Prevent toolbar from closing when clicked
preventCloseOnClick?: boolean; // default: true
}Custom Positioning
const { getInputProps, isVisible, applyFormat } = useFormattingToolbar({
getPosition: (element) => {
const rect = element.getBoundingClientRect();
return {
x: rect.left,
y: rect.top - 50, // Position above the element
};
},
hideDelay: 300,
preventCloseOnClick: true,
});Styling the Toolbar
<FormattingToolbar
className="my-custom-toolbar"
style={{
backgroundColor: 'white',
border: '1px solid #ccc',
borderRadius: '4px',
padding: '8px'
}}
>
{(applyFormat) => (
// Your toolbar content
)}
</FormattingToolbar>📚 API Reference
Hooks
useFormattingToolbar(config?: ToolbarConfig)
Main hook for managing toolbar functionality.
Returns:
getInputProps()- Props to spread on input/textarea elementsgetToolbarProps()- Props for toolbar container (includes positioning)applyFormat(formatter)- Apply formatting to active elementshowToolbar(element)- Manually show toolbarhideToolbar()- Manually hide toolbarisVisible- Whether toolbar is visibletoolbarState- Current toolbar state
useFormattingToolbarState()
Lightweight hook for toolbar-only components that don't handle input events.
Returns:
applyFormat(formatter)- Apply formatting to active elementisVisible- Whether toolbar is visibletoolbarState- Current toolbar state
Components
FormattingToolbar
Renders the toolbar when an input is focused.
Props:
children- Render function receivingapplyFormatcallbackas?- Container element type (default: 'div')className?- CSS class nameconfig?- Toolbar configurationstyle?- Inline styles
withFormattingToolbar(Component, config?)
Higher-order component that adds toolbar functionality to input components.
Types
type FormatterFunction = (text: string) => string;
type TextInputElement = HTMLInputElement | HTMLTextAreaElement;
type ToolbarPosition = {
x: number;
y: number;
};
type ToolbarState = {
activeElement: TextInputElement | null;
isVisible: boolean;
position: ToolbarPosition | null;
};💡 Common Formatting Functions
Here are some useful formatting functions you can use:
// Text transformations
const toUpperCase = (text: string) => text.toUpperCase();
const toLowerCase = (text: string) => text.toLowerCase();
const toTitleCase = (text: string) =>
text.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
// Markdown formatting
const makeBold = (text: string) => `**${text}**`;
const makeItalic = (text: string) => `*${text}*`;
const makeCode = (text: string) => `\`${text}\``;
// Text cleaning
const removeLineBreaks = (text: string) => text.replace(/\n/g, ' ');
const trimWhitespace = (text: string) => text.trim();
const removeExtraSpaces = (text: string) => text.replace(/\s+/g, ' ');
// Usage
<button onClick={() => applyFormat(makeBold)}>Bold</button>;🎨 Styling Examples
With Tailwind CSS
<FormattingToolbar className="bg-white border border-gray-200 rounded-lg shadow-lg p-2">
{(applyFormat) => (
<div className="flex gap-1">
<button
onClick={() => applyFormat(toUpperCase)}
className="px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600"
>
ABC
</button>
<button
onClick={() => applyFormat(toLowerCase)}
className="px-3 py-1 text-sm bg-green-500 text-white rounded hover:bg-green-600"
>
abc
</button>
</div>
)}
</FormattingToolbar>With CSS Modules
/* styles.module.css */
.toolbar {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 8px;
}
.toolbarButton {
padding: 4px 12px;
margin-right: 4px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.toolbarButton:hover {
background-color: #f1f5f9;
}import styles from './styles.module.css';
<FormattingToolbar className={styles.toolbar}>
{(applyFormat) => (
<div>
<button className={styles.toolbarButton} onClick={() => applyFormat(makeBold)}>
Bold
</button>
</div>
)}
</FormattingToolbar>;🔍 Advanced Usage
Multiple Input Fields
The library automatically manages a single toolbar across multiple inputs:
function MultiInputForm() {
const { getInputProps } = useFormattingToolbar();
const [field1, setField1] = useState('');
const [field2, setField2] = useState('');
return (
<div>
<input
{...getInputProps()}
value={field1}
onChange={(e) => setField1(e.target.value)}
placeholder="First field"
/>
<textarea
{...getInputProps()}
value={field2}
onChange={(e) => setField2(e.target.value)}
placeholder="Second field"
/>
<FormattingToolbar>
{(applyFormat) => (
<div>
<button onClick={() => applyFormat(toUpperCase)}>UPPERCASE</button>
</div>
)}
</FormattingToolbar>
</div>
);
}Custom Formatter with Selection
const wrapWithQuotes = (text: string) => `"${text}"`;
const addPrefix = (text: string) => `• ${text}`;
// The library automatically handles whether text is selected or not
<button onClick={() => applyFormat(wrapWithQuotes)}>Add Quotes</button>;Conditional Toolbar Content
<FormattingToolbar>
{(applyFormat) => {
const { activeElement } = useFormattingToolbarState().toolbarState;
const isTextarea = activeElement?.tagName === 'TEXTAREA';
return (
<div>
<button onClick={() => applyFormat(toUpperCase)}>UPPERCASE</button>
{isTextarea && <button onClick={() => applyFormat(removeLineBreaks)}>Remove Line Breaks</button>}
</div>
);
}}
</FormattingToolbar>🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built with TypeScript and React
- Inspired by modern text editing interfaces
- Designed for developer experience and flexibility
- Asmāʾ for the project name
