No description
|
Some checks failed
Publish / publish (push) Failing after 0s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| src | ||
| .gitignore | ||
| eslint.config.js | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsup.config.ts | ||
@lilith/ui-feedback
Feedback and overlay components for React applications. Includes modals, toasts, dropdowns, tooltips, tabs, popovers, skeleton loaders, and more with theme-aware styling and animations.
Features
- Modal - Dialog overlays with customizable size and animations
- Toast - Notification system with multiple types
- Dropdown - Customizable dropdown menus
- Tooltip - Hover tooltips with positioning
- Popover - Click-triggered content panels
- Tabs - Tab navigation with variants (standard and pill)
- Skeleton - Loading placeholders
- PromptDialog - Confirmation dialogs with context API
- BaseDrawer - Slide-out drawer component
- TranslatedText - Text with loading states for i18n
- Focus Management - Hooks for body scroll lock, escape handling, and focus trapping
Installation
npm install @lilith/ui-feedback
# or
pnpm add @lilith/ui-feedback
Peer Dependencies
npm install react react-dom styled-components
Usage
Modal
Dialog overlay with header and body.
import { Modal, ModalActions } from '@lilith/ui-feedback';
const [isOpen, setIsOpen] = useState(false);
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Confirm Action"
maxWidth="500px"
>
<p>Are you sure you want to proceed?</p>
<ModalActions>
<Button variant="secondary" onClick={() => setIsOpen(false)}>
Cancel
</Button>
<Button variant="primary" onClick={handleConfirm}>
Confirm
</Button>
</ModalActions>
</Modal>
Toast
Notification system with provider and hook.
import { ToastProvider, useToast } from '@lilith/ui-feedback';
// Wrap your app
<ToastProvider>
<App />
</ToastProvider>
// Use in components
function MyComponent() {
const { addToast } = useToast();
const handleSave = () => {
addToast({
type: 'success',
message: 'Changes saved successfully',
duration: 3000,
});
};
const handleError = () => {
addToast({
type: 'error',
message: 'Failed to save changes',
action: {
label: 'Retry',
onClick: () => retrySave(),
},
});
};
// Types: 'success', 'error', 'warning', 'info'
}
Dropdown
Customizable dropdown menu.
import { Dropdown, DropdownItem } from '@lilith/ui-feedback';
<Dropdown
trigger={<Button>Actions</Button>}
align="left"
>
<DropdownItem onClick={() => handleEdit()}>Edit</DropdownItem>
<DropdownItem onClick={() => handleDuplicate()}>Duplicate</DropdownItem>
<DropdownItem onClick={() => handleDelete()} variant="danger">
Delete
</DropdownItem>
</Dropdown>
Tooltip
Hover-triggered informational tooltips.
import { Tooltip } from '@lilith/ui-feedback';
<Tooltip content="This action cannot be undone" position="top">
<Button variant="danger">Delete</Button>
</Tooltip>
// With rich content
<Tooltip
content={
<div>
<strong>Keyboard Shortcut</strong>
<p>Press Cmd+S to save</p>
</div>
}
position="right"
>
<span>Save</span>
</Tooltip>
Popover
Click-triggered content panels.
import { Popover } from '@lilith/ui-feedback';
<Popover
trigger={<Button>Show Details</Button>}
content={
<div>
<h3>User Details</h3>
<p>Name: John Doe</p>
<p>Email: john@example.com</p>
</div>
}
position="bottom"
/>
Tabs
Tab navigation with multiple variants.
import { Tabs } from '@lilith/ui-feedback';
import type { Tab } from '@lilith/ui-feedback';
const tabs: Tab[] = [
{ id: 'overview', label: 'Overview', content: <Overview /> },
{ id: 'settings', label: 'Settings', content: <Settings /> },
{ id: 'history', label: 'History', content: <History />, disabled: true },
];
// Standard tabs
<Tabs
tabs={tabs}
activeTab={activeTab}
onChange={setActiveTab}
/>
// Pill tabs (for filtering, segmented controls)
import { PillTabs } from '@lilith/ui-feedback';
<PillTabs
tabs={[
{ id: 'all', label: 'All' },
{ id: 'active', label: 'Active' },
{ id: 'archived', label: 'Archived' },
]}
activeTab={filter}
onChange={setFilter}
/>
Skeleton
Loading placeholders.
import {
Skeleton,
TextSkeleton,
AvatarSkeleton,
CardSkeleton,
} from '@lilith/ui-feedback';
// Basic skeleton
<Skeleton width={200} height={20} />
// Text lines
<TextSkeleton lines={3} />
// Avatar placeholder
<AvatarSkeleton size={48} />
// Card placeholder
<CardSkeleton />
PromptDialog
Confirmation dialogs with context API.
import { PromptDialogProvider, usePromptDialog } from '@lilith/ui-feedback';
// Wrap your app
<PromptDialogProvider>
<App />
</PromptDialogProvider>
// Use in components
function DeleteButton() {
const { prompt } = usePromptDialog();
const handleDelete = async () => {
const confirmed = await prompt({
title: 'Delete Item',
message: 'Are you sure you want to delete this item?',
confirmLabel: 'Delete',
cancelLabel: 'Cancel',
variant: 'danger',
});
if (confirmed) {
await deleteItem();
}
};
return <Button onClick={handleDelete}>Delete</Button>;
}
BaseDrawer
Slide-out drawer component.
import { BaseDrawer } from '@lilith/ui-feedback';
<BaseDrawer
isOpen={isDrawerOpen}
onClose={() => setIsDrawerOpen(false)}
position="right"
width="400px"
title="Settings"
>
<SettingsForm />
</BaseDrawer>
Skeleton Loading Patterns
import {
ImageWithSkeleton,
SuspenseWithSkeleton,
PageSuspense,
} from '@lilith/ui-feedback';
// Image with loading skeleton
<ImageWithSkeleton
src="/user/avatar.jpg"
alt="User avatar"
width={100}
height={100}
/>
// Suspense wrapper with skeleton fallback
<SuspenseWithSkeleton skeleton={<CardSkeleton />}>
<LazyLoadedCard />
</SuspenseWithSkeleton>
// Page-level suspense
<PageSuspense>
<LazyLoadedPage />
</PageSuspense>
Focus Management Hooks
import {
useBodyScrollLock,
useModalEscape,
useModalFocusTrap,
} from '@lilith/ui-feedback';
function CustomModal({ isOpen, onClose, children }) {
const modalRef = useRef(null);
// Lock body scroll when open
useBodyScrollLock(isOpen);
// Close on Escape key
useModalEscape(onClose, isOpen);
// Trap focus within modal
useModalFocusTrap(modalRef, isOpen);
return (
<div ref={modalRef} role="dialog">
{children}
</div>
);
}
API Reference
Modal
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen |
boolean |
required | Visibility state |
onClose |
() => void |
required | Close handler |
title |
string |
required | Modal title |
children |
ReactNode |
required | Modal content |
maxWidth |
string |
'600px' |
Maximum width |
maxHeight |
string |
'90vh' |
Maximum height |
Toast (via useToast)
interface Toast {
type: 'success' | 'error' | 'warning' | 'info';
message: string;
duration?: number;
action?: {
label: string;
onClick: () => void;
};
}
Tooltip
| Prop | Type | Default | Description |
|---|---|---|---|
content |
ReactNode |
required | Tooltip content |
position |
'top' | 'bottom' | 'left' | 'right' |
'top' |
Position |
children |
ReactNode |
required | Trigger element |
delay |
number |
200 |
Show delay in ms |
Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
tabs |
Tab[] |
required | Tab definitions |
activeTab |
string |
required | Active tab ID |
onChange |
(id: string) => void |
required | Tab change handler |
BaseDrawer
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen |
boolean |
required | Visibility state |
onClose |
() => void |
required | Close handler |
position |
'left' | 'right' |
'right' |
Slide direction |
width |
string |
'400px' |
Drawer width |
title |
string |
- | Header title |
children |
ReactNode |
required | Drawer content |
Types
interface Tab {
id: string;
label: string;
content?: ReactNode;
disabled?: boolean;
}
interface PromptOptions {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: 'default' | 'danger';
}
interface SkeletonProps {
width?: number | string;
height?: number | string;
borderRadius?: string;
animation?: 'pulse' | 'wave';
}
Dependencies
@lilith/ui-primitives- Base components@lilith/ui-theme- Theme tokensframer-motion- Animationslucide-react- Icons
License
MIT