This is a submission for the KendoReact Free Components Challenge.
This project is a simple Microsoft Word–like clone built with React and KendoReact’s free components.
It demonstrates a basic text editor with functionalities such as:
[1] File Operations: New, Open, and Save (with a simulated save dialog).
[2] Text Formatting: Bold, Italic, and Underline commands.
[3] UI Components: A rich interface including Toolbars, ButtonGroups, Floating Action Button, Popover (for help), Skeleton (for loading state), Badge (to indicate unsaved changes), Chip/ChipList (for page indicator), and a few others.
[4] Animations & Typography: Used for enhanced UI/UX.
Components Used
The following free KendoReact components have been used:
A> Toolbar & ButtonGroup: For file operations and formatting commands.
B) Buttons & FloatingActionButton: To trigger actions.
C) Tooltip & Popover: For providing helpful hints.
D} Animation: To add visual transitions.
E) Skeleton & ProgressBar: To simulate loading and action feedback.
F} Chip & ChipList: To display document metadata (e.g., page indicator).
G] Dialogs: For save confirmation.
H] Typography: For text display.
I) AdaptiveModeContext: To ensure adaptive UI behaviors.
Project Structure
✅ App.tsx: Contains the complete React component that renders the Word clone. It uses a contenteditable div for text editing and integrates various KendoReact components.
☑️ README.md: Provides an overview of the project, installation instructions, and component details.
App.tsx
import React, { useRef, useState, useEffect } from 'react';
import { Button, ButtonGroup, FloatingActionButton, Chip, ChipList } from '@progress/kendo-react-buttons';
import { Toolbar } from '@progress/kendo-react-toolbar';
import { Dialog, DialogActionsBar } from '@progress/kendo-react-dialogs';
import { Tooltip } from '@progress/kendo-react-tooltip';
import { Animation } from '@progress/kendo-react-animation';
import { Typography } from '@progress/kendo-react-common';
import { Skeleton, ProgressBar } from '@progress/kendo-react-indicators';
import { AdaptiveModeContext } from '@progress/kendo-react-layout';
import { Popover } from '@progress/kendo-react-popover';
import '@progress/kendo-theme-default/dist/all.css';
const App = () => {
const editorRef = useRef<HTMLDivElement>(null);
const fabRef = useRef<HTMLButtonElement>(null);
// States for document title, unsaved changes, loading, save dialog, and help popover.
const [fileName, setFileName] = useState("Untitled Document");
const [unsavedChanges, setUnsavedChanges] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [showSaveDialog, setShowSaveDialog] = useState(false);
const [showHelpPopover, setShowHelpPopover] = useState(false);
// Simulate a loading state on startup.
useEffect(() => {
const timer = setTimeout(() => setIsLoading(false), 1000);
return () => clearTimeout(timer);
}, []);
// Execute document command for rich text editing.
const execCommand = (command: string, value?: any) => {
document.execCommand(command, false, value);
};
// Focus the editor and run the command.
const handleFormatting = (command: string) => {
if (editorRef.current) {
editorRef.current.focus();
execCommand(command);
setUnsavedChanges(unsavedChanges + 1);
}
};
// Handlers for file operations.
const handleNew = () => {
if (editorRef.current) {
editorRef.current.innerHTML = "<p>Start editing your document here...</p>";
}
setFileName("Untitled Document");
setUnsavedChanges(0);
};
const handleOpen = () => {
// For demo purposes, we simply alert.
alert("Open functionality is not implemented in this demo.");
};
const handleSave = () => {
// In a real app you might serialize the document here.
setShowSaveDialog(true);
setUnsavedChanges(0);
};
const closeSaveDialog = () => {
setShowSaveDialog(false);
};
// Toggle help popover visibility.
const toggleHelpPopover = () => {
setShowHelpPopover(!showHelpPopover);
};
// Track any text changes in the editor.
const handleEditorInput = () => {
setUnsavedChanges(prev => prev + 1);
};
return (
<AdaptiveModeContext.Provider value="desktop">
<div style={{ margin: '20px' }}>
{/* App title */}
<Typography variant="h4" style={{ marginBottom: '10px' }}>
Word Clone - {fileName}
</Typography>
{/* Main toolbar with file and formatting commands */}
<Toolbar>
<ButtonGroup>
<Button icon="file" onClick={handleNew}>New</Button>
<Button icon="folder-open" onClick={handleOpen}>Open</Button>
<Button icon="save" onClick={handleSave}>
{/* Badge to indicate unsaved changes */}
{unsavedChanges > 0 && (
<span style={{
background: 'red',
borderRadius: '50%',
color: 'white',
fontSize: '10px',
padding: '2px 6px',
position: 'absolute',
top: '5px',
right: '5px'
}}>
{unsavedChanges}
</span>
)}
Save
</Button>
</ButtonGroup>
<span style={{ marginLeft: '20px' }}></span>
<ButtonGroup>
<Tooltip position="top" anchorElement="target" content="Bold (Ctrl+B)">
<Button icon="bold" onClick={() => handleFormatting('bold')}>B</Button>
</Tooltip>
<Tooltip position="top" anchorElement="target" content="Italic (Ctrl+I)">
<Button icon="italic" onClick={() => handleFormatting('italic')}>I</Button>
</Tooltip>
<Tooltip position="top" anchorElement="target" content="Underline (Ctrl+U)">
<Button icon="underline" onClick={() => handleFormatting('underline')}>U</Button>
</Tooltip>
</ButtonGroup>
</Toolbar>
{/* Animated text editor area */}
<Animation>
{isLoading ? (
<Skeleton shape="rectangular" width="100%" height="300px" />
) : (
<div
ref={editorRef}
contentEditable
onInput={handleEditorInput}
style={{
border: '1px solid #ccc',
minHeight: '300px',
padding: '10px',
marginTop: '10px'
}}
>
<p>Start editing your document here...</p>
</div>
)}
</Animation>
{/* ChipList showing a page indicator */}
<div style={{ marginTop: '10px' }}>
<ChipList>
<Chip text="Page 1" />
</ChipList>
</div>
{/* Floating Action Button to toggle help popover */}
<FloatingActionButton
ref={fabRef}
icon="help"
style={{ position: 'fixed', bottom: '20px', right: '20px' }}
onClick={toggleHelpPopover}
/>
{/* Popover for help information */}
{showHelpPopover && fabRef.current && (
<Popover
anchor={fabRef.current}
align={{ vertical: 'top', horizontal: 'center' }}
collision={{ horizontal: 'flip', vertical: 'flip' }}
onClose={() => setShowHelpPopover(false)}
>
<div style={{ padding: '10px' }}>
<Typography variant="subtitle1">
<strong>Help</strong>
</Typography>
<Typography variant="body1">
Use the toolbar to create a new document, open or save your work.
Select text and click Bold, Italic, or Underline to format.
</Typography>
</div>
</Popover>
)}
{/* Save confirmation dialog */}
{showSaveDialog && (
<Dialog title="Save Document" onClose={closeSaveDialog}>
<div style={{ margin: '10px' }}>
<Typography variant="body1">
Your document has been saved successfully.
</Typography>
{/* Example of a ProgressBar used inside the dialog */}
<div style={{ marginTop: '10px' }}>
<ProgressBar value={100} animation={{ duration: 300 }} />
</div>
</div>
<DialogActionsBar>
<Button onClick={closeSaveDialog}>OK</Button>
</DialogActionsBar>
</Dialog>
)}
</div>
</AdaptiveModeContext.Provider>
);
};
export default App;
Enjoy experimenting with your Word clone, and fork to expand the functionality further!
Top comments (0)