Welcome to the first part of my four-part series on creating a responsive dialog component in React. In this series, I'll explore different approaches to achieve smooth animation transitions while maintaining the dialog's fluid dimensions. In this initial part, I'll set up the basic dialog component with minimise and expand functionality.
Please note that accessibility and responsive design are not included as part of the considerations in this series. The primary focus is on creating a reusable dialog component with smooth animation transitions.
This series is part of a proof of concept I've been working on, aimed at discussing and refining techniques for animating UI components. I invite feedback and insights from fellow developers to validate my approach or suggest improvements.
Let's start by creating a highly reusable dialog component that supports minimising and expanding. I'll use the compositional pattern to ensure the dialog can adapt to changing content.
File Structure:
src/ components/ FluidDialog/ Dialog.js DialogContext.js DialogHeader.js DialogBody.js DialogFooter.js DialogContainer.js index.js App.js index.js
First, I'll create a context to manage the state of our dialog component.
Key Points:
// src/components/FluidDialog/DialogContext.js import { createContext, useContext, useId, useState } from 'react'; const DialogContext = createContext(); export function DialogProvider({ rootRef, isExpandedByDefault, children, maxWidth, }) { const dialogId = useId(); const [isExpanded, setIsExpanded] = useState(isExpandedByDefault); return ( <DialogContext.Provider value={{ dialogId, rootRef, isExpanded, setIsExpanded, maxWidth }} > {children} </DialogContext.Provider> ); } export function useDialog() { return useContext(DialogContext); }
Next, I'll create the main dialog component that uses the context to handle expansion and minimisation.
Key Points:
// src/components/FluidDialog/Dialog.js import { useRef } from 'react'; import { styled } from 'styled-components'; import { DialogProvider } from './DialogContext'; export default function Dialog({ id, isExpandedByDefault = true, maxWidth = 400, children, }) { const rootRef = useRef(null); return ( <DialogProvider dialogId={id} rootRef={rootRef} isExpandedByDefault={isExpandedByDefault} > <DialogComponent role="dialog" aria-labelledby={`${id}_label`} aria-describedby={`${id}_desc`} ref={rootRef} maxWidth={maxWidth} > {children} </DialogComponent> </DialogProvider> ); } const DialogComponent = styled.section` max-width: ${({ maxWidth }) => (maxWidth ? `${maxWidth}px` : undefined)}; position: absolute; right: 16px; bottom: 16px; border: 1px solid #ccc; border-radius: 6px; box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); overflow: hidden; `;
I'll create additional components for the dialog header, body, footer, and container to ensure modularity and reusability.
Key Points:
// src/components/FluidDialog/DialogHeader.js import { styled } from 'styled-components'; import { IconButton } from '../IconButton'; import { useDialog } from './DialogContext'; export default function DialogHeader({ children, expandedTitle }) { const { dialogId, isExpanded, setIsExpanded } = useDialog(); return ( <DialogHeaderComponent id={`${dialogId}_label`}> <ExpandedState isVisible={isExpanded}> <Title>{expandedTitle ?? children}</Title> <IconButtons> <IconButton icon="chevron-down" onClick={() => setIsExpanded(false)} /> </IconButtons> </ExpandedState> <MinimizedState isVisible={!isExpanded} onClick={() => setIsExpanded(true)} > <Title>{children}</Title> <IconButtons> <IconButton icon="chevron-up" /> </IconButtons> </MinimizedState> </DialogHeaderComponent> ); } const DialogHeaderComponent = styled.div``; const ExpandedState = styled.header` transition: opacity 0.3s; opacity: ${({ isVisible }) => (isVisible ? 1 : 0)}; pointer-events: ${({ isVisible }) => (isVisible ? 'all' : 'none')}; position: absolute; top: 0; left: 0; width: 100%; background: #f3f3f3; display: flex; flex-direction: row; `; const MinimizedState = styled.header` transition: opacity 0.3s; opacity: ${({ isVisible }) => (isVisible ? 1 : 0)}; pointer-events: ${({ isVisible }) => (isVisible ? 'all' : 'none')}; background: #f3f3f3; display: flex; flex-direction: row; cursor: pointer; `; const Title = styled.span` flex-grow: 1; text-align: left; display: flex; align-items: center; padding: 0 16px; `; const IconButtons = styled.div``;
// src/components/FluidDialog/DialogContainer.js import { styled } from 'styled-components'; import { useDialog } from './DialogContext'; export default function DialogContainer({ children }) { const { isExpanded } = useDialog(); return ( <DialogContainerComponent isVisible={isExpanded}> {children} </DialogContainerComponent> ); } const DialogContainerComponent = styled.div` display: ${({ isVisible }) => (isVisible ? undefined : 'none')}; `;
// src/components/FluidDialog/DialogBody.js import { styled } from 'styled-components'; import DialogContainer from './DialogContainer'; import { useDialog } from './DialogContext'; export default function DialogBody({ children }) { const { dialogId } = useDialog(); return ( <DialogBodyComponent> <DialogContainer> <DialogBodyContent id={`${dialogId}_desc`}> {children} </DialogBodyContent> </DialogContainer> </DialogBodyComponent> ); } const DialogBodyComponent = styled.div``; const DialogBodyContent = styled.div` padding: 8px 16px; `;
// src/components/FluidDialog/DialogFooter.js import { styled } from 'styled-components'; import DialogContainer from './DialogContainer'; export default function DialogFooter({ children }) { return ( <DialogFooterComponent> <DialogContainer> <DialogFooterContent>{children}</DialogFooterContent> </DialogContainer> </DialogFooterComponent> ); } const DialogFooterComponent = styled.div` background: #f3f3f3; `; const DialogFooterContent = styled.div` padding: 8px 16px; `;
Finally, I'll import and use the dialog component in the main app.
Key Points:
// src/App.js import React from 'react'; import Dialog from './components/FluidDialog/Dialog'; import DialogHeader from './components/FluidDialog/DialogHeader'; import DialogBody from './components/FluidDialog/DialogBody'; import DialogFooter from './components/FluidDialog/DialogFooter'; function App() { return ( <div className="App"> <Dialog> <DialogHeader>My dialog/DialogHeader> <DialogBody>This is the content of the dialog.</DialogBody> <DialogFooter>This is the footer of the dialog.</DialogFooter> </Dialog> </div> ); } export default App;
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
You can access the whole source code on CodeSandbox.
You can also see a live preview of the implementation:
In this first part, I've set up a basic dialog box in React with minimise and expand functionality. This foundational component will serve as the basis for further enhancements in the upcoming articles. The dialog component is designed to hug its content and adapt to changes, making it highly reusable and flexible.
Stay tuned for Part 2, where I'll delve into adding animations to the dialog transitions, exploring different options to achieve smooth effects.
I invite feedback and comments from fellow developers to help refine and improve this approach. Your insights are invaluable in making this proof of concept more robust and effective.
The above is the detailed content of Creating a Smooth Transitioning Dialog Component in React (Part ). For more information, please follow other related articles on the PHP Chinese website!