歡迎來到我的關於在 React 中創建響應式對話框組件的四部分系列的第一部分。在本系列中,我將探索不同的方法來實現平滑的動畫過渡,同時保持對話框的流體尺寸。在最初的部分中,我將設定具有最小化和展開功能的基本對話方塊元件。
請注意,可訪問性和響應式設計不屬於本系列考慮因素的一部分。主要重點是創建具有平滑動畫過渡的可重複使用對話方塊元件。
本系列是我一直致力於的概念驗證的一部分,旨在討論和完善 UI 元件動畫技術。我邀請其他開發人員提供回饋和見解來驗證我的方法或提出改進建議。
讓我們先建立一個支援最小化和擴展的高度可重複使用的對話方塊元件。我將使用組合模式來確保對話方塊能夠適應不斷變化的內容。
檔案結構:
src/ components/ FluidDialog/ Dialog.js DialogContext.js DialogHeader.js DialogBody.js DialogFooter.js DialogContainer.js index.js App.js index.js
首先,我將建立一個上下文來管理對話方塊元件的狀態。
重點:
// 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); }
接下來,我將建立使用上下文來處理擴充和最小化的主對話框元件。
重點:
// 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; `;
我將為對話方塊標題、正文、頁尾和容器建立其他元件,以確保模組化和可重複使用性。
重點:
// 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; `;
最後,我將在主應用程式中匯入並使用對話方塊元件。
重點:
// 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') );
您可以在 CodeSandbox 上存取完整的原始程式碼。
您也可以查看實作的即時預覽:
在第一部分中,我在 React 中設定了一個基本對話框,具有最小化和展開功能。這個基礎組件將作為後續文章中進一步增強的基礎。對話方塊元件旨在擁抱其內容並適應變化,使其具有高度可重複使用性和靈活性。
請繼續關注第 2 部分,我將深入研究向對話框過渡添加動畫,探索不同的選項來實現平滑的效果。
我邀請其他開發人員提供回饋和意見,以幫助完善和改進這種方法。您的見解對於使概念驗證更加穩健和有效非常寶貴。
以上是在 React 中建立平滑過渡的對話方塊元件(部分)的詳細內容。更多資訊請關注PHP中文網其他相關文章!