A React compatibility layer for Preact
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Animation and transition components for managing component lifecycle animations and CSS transitions.
Container component that manages entering and leaving transitions for its children.
/**
* Container for managing child component transitions
*/
class TransitionGroup extends Component {
/**
* TransitionGroup props
*/
props: {
/** Component type to render as (default: 'span') */
component?: string | ComponentType;
/** Additional props passed to component */
[key: string]: any;
};
}
/**
* CSS-based transition group with automatic class management
*/
class CSSTransitionGroup extends Component {
/**
* CSSTransitionGroup props
*/
props: {
/** Base name for CSS classes */
transitionName: string | TransitionClasses;
/** Enter transition duration in ms */
transitionEnterTimeout: number;
/** Leave transition duration in ms */
transitionLeaveTimeout: number;
/** Whether to apply transition on initial mount */
transitionAppear?: boolean;
/** Appear transition duration in ms */
transitionAppearTimeout?: number;
/** Component type to render as (default: 'span') */
component?: string | ComponentType;
/** Additional props passed to component */
[key: string]: any;
};
}
/**
* CSS class names for different transition states
*/
interface TransitionClasses {
enter: string;
enterActive: string;
leave: string;
leaveActive: string;
appear?: string;
appearActive?: string;
}Usage Examples:
import { CSSTransitionGroup } from 'preact-compat';
// or
import { CSSTransitionGroup } from 'preact-compat/lib/ReactTransitionGroup';
class TodoList extends Component {
state = {
todos: [
{ id: 1, text: 'Learn React' },
{ id: 2, text: 'Build an app' }
]
};
addTodo = () => {
const newTodo = {
id: Date.now(),
text: `Todo ${this.state.todos.length + 1}`
};
this.setState({
todos: [...this.state.todos, newTodo]
});
};
removeTodo = (id) => {
this.setState({
todos: this.state.todos.filter(todo => todo.id !== id)
});
};
render() {
return (
<div>
<button onClick={this.addTodo}>Add Todo</button>
<CSSTransitionGroup
transitionName="todo"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
component="ul"
>
{this.state.todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => this.removeTodo(todo.id)}>
Remove
</button>
</li>
))}
</CSSTransitionGroup>
</div>
);
}
}/* CSS for the example above */
/* Enter transition */
.todo-enter {
opacity: 0;
transform: translateX(-100%);
}
.todo-enter-active {
opacity: 1;
transform: translateX(0);
transition: all 300ms ease-in;
}
/* Leave transition */
.todo-leave {
opacity: 1;
transform: translateX(0);
}
.todo-leave-active {
opacity: 0;
transform: translateX(100%);
transition: all 300ms ease-out;
}
/* Appear transition (optional) */
.todo-appear {
opacity: 0;
transform: scale(0.8);
}
.todo-appear-active {
opacity: 1;
transform: scale(1);
transition: all 300ms ease-in-out;
}import { CSSTransitionGroup } from 'preact-compat';
const customTransitions = {
enter: 'slide-in',
enterActive: 'slide-in-active',
leave: 'slide-out',
leaveActive: 'slide-out-active',
appear: 'fade-in',
appearActive: 'fade-in-active'
};
function AnimatedList({ items }) {
return (
<CSSTransitionGroup
transitionName={customTransitions}
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
transitionAppear={true}
transitionAppearTimeout={500}
component="div"
className="animated-container"
>
{items.map(item => (
<div key={item.id} className="animated-item">
{item.content}
</div>
))}
</CSSTransitionGroup>
);
}import { CSSTransitionGroup } from 'preact-compat';
class Modal extends Component {
render() {
const { isOpen, onClose, children } = this.props;
return (
<CSSTransitionGroup
transitionName="modal"
transitionEnterTimeout={200}
transitionLeaveTimeout={200}
component="div"
>
{isOpen && (
<div key="modal" className="modal-backdrop" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<button className="modal-close" onClick={onClose}>×</button>
{children}
</div>
</div>
)}
</CSSTransitionGroup>
);
}
}
// CSS for modal transitions
/*
.modal-enter {
opacity: 0;
}
.modal-enter-active {
opacity: 1;
transition: opacity 200ms ease-in;
}
.modal-leave {
opacity: 1;
}
.modal-leave-active {
opacity: 0;
transition: opacity 200ms ease-out;
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 4px;
position: relative;
max-width: 500px;
width: 90%;
}
*/import { CSSTransitionGroup } from 'preact-compat';
class App extends Component {
state = { currentRoute: 'home' };
navigate = (route) => {
this.setState({ currentRoute: route });
};
renderRoute = () => {
const { currentRoute } = this.state;
switch (currentRoute) {
case 'home':
return <HomePage key="home" />;
case 'about':
return <AboutPage key="about" />;
case 'contact':
return <ContactPage key="contact" />;
default:
return <HomePage key="home" />;
}
};
render() {
return (
<div>
<nav>
<button onClick={() => this.navigate('home')}>Home</button>
<button onClick={() => this.navigate('about')}>About</button>
<button onClick={() => this.navigate('contact')}>Contact</button>
</nav>
<CSSTransitionGroup
transitionName="page"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
component="main"
className="page-container"
>
{this.renderRoute()}
</CSSTransitionGroup>
</div>
);
}
}import { TransitionGroup } from 'preact-compat';
class CustomTransitionGroup extends Component {
render() {
return (
<TransitionGroup component="div" className="transition-container">
{this.props.items.map(item => (
<CustomTransitionChild key={item.id} item={item} />
))}
</TransitionGroup>
);
}
}
// Custom child component that handles its own transitions
class CustomTransitionChild extends Component {
componentWillEnter(done) {
// Custom enter animation
const el = this.refs.element;
el.style.opacity = '0';
el.style.transform = 'translateY(-20px)';
requestAnimationFrame(() => {
el.style.transition = 'all 300ms ease';
el.style.opacity = '1';
el.style.transform = 'translateY(0)';
setTimeout(done, 300);
});
}
componentWillLeave(done) {
// Custom leave animation
const el = this.refs.element;
el.style.transition = 'all 300ms ease';
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
setTimeout(done, 300);
}
render() {
return (
<div ref="element">
{this.props.item.content}
</div>
);
}
}// Main imports
import { TransitionGroup, CSSTransitionGroup } from 'preact-compat';
// Library imports
import { TransitionGroup, CSSTransitionGroup } from 'preact-compat/lib/ReactTransitionGroup';
// CommonJS
const { TransitionGroup, CSSTransitionGroup } = require('preact-compat/lib/ReactTransitionGroup');interface TransitionGroupProps {
component?: string | ComponentType;
[key: string]: any;
}
interface CSSTransitionGroupProps {
transitionName: string | TransitionClasses;
transitionEnterTimeout: number;
transitionLeaveTimeout: number;
transitionAppear?: boolean;
transitionAppearTimeout?: number;
component?: string | ComponentType;
[key: string]: any;
}
interface TransitionClasses {
enter: string;
enterActive: string;
leave: string;
leaveActive: string;
appear?: string;
appearActive?: string;
}
interface TransitionChildComponent {
componentWillEnter?(done: () => void): void;
componentDidEnter?(): void;
componentWillLeave?(done: () => void): void;
componentDidLeave?(): void;
componentWillAppear?(done: () => void): void;
componentDidAppear?(): void;
}Install with Tessl CLI
npx tessl i tessl/npm-preact-compat