Telerik blogs
KendoReactT2_1200x303

Let’s take a look at the KendoReact Window component, which allows you to resize, minimize, maximize and even drag windowed items on the web similar to desktop functionality.

A really nice thing about desktop applications is that you can easily resize, minimize, maximize and even drag them around. Have you ever wondered how something like this could be implemented on the web? It’s actually quite easy with KendoReact, as it offers a React Window component that provides the aforementioned functionality.

In this article, you will learn how to install KendoReact, add a list of reports and then implement the React Window component to display full report details in multiple windows and archive or unarchive reports. See the gif below.

KendoReact Window reports list. Four reports are listed. We open Mary. It pops up a window. Then we minimize it, so only its header shows, and it gets dragged to the side. Similar is done with Anna. Then Matthew is opened and maximized, taking up the whole window; then it is minimized and closed. Then William is opened, the 'archive report' button is clicked, and then its window closed; it's reopened and the button is now labeled 'unarchive report' and it is clicked.

If you want to quickly check the whole code and play with the demo, you can scroll to the bottom of this article, where you can find an interactive StackBlitz example.

Project Setup

Before we start, we need to set up a React project. To quickly scaffold a new project, you can use Create React App or Vite. For this demo, we are going to use Create React App. You can create a React project by running one of the below commands in your terminal.

npx create-react-app my-kendo-react-window
cd my-kendo-react-window
npm start

Here are Kendo packages that we also need to install.

  • @progress/kendo-react-dialogs
  • @progress/kendo-theme-default
  • @progress/kendo-licensing

You can copy the text below and paste it into your terminal. If you’re using yarn, just replace npm install with yarn add.

npm install @progress/kendo-react-dialogs @progress/kendo-theme-default @progress/kendo-licensing

The React Window component is a part of the KendoReact Dialogs package. We also need to install one of the Kendo UI themes. In this example, we will use the Default theme that was developed by the UX experts at Progress Telerik.

Note on the kendo-licensing package: KendoReact is a professionally developed UI library distributed under a commercial license. Using it requires either a commercial license key or an active trial license key, easily acquired following these steps.

After installing the packages, we need to import the theme styles in the App.js:

import '@progress/kendo-theme-default/dist/all.css';

That’s all for the initial setup.

Creating a List of Reports

We will start by creating a file with reports data.

src/components/Reports/reportsData.js

export const reportsData = [
  {
    id: 1,
    author: "Mary",
    text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam pulvinar congue sapien, quis pellentesque orci porta ac. Curabitur bibendum pulvinar mauris vitae elementum. Vivamus a turpis nec nunc ullamcorper facilisis vel non lorem.",
    created: "15/05/2021",
    archived: false,
  },
  {
    id: 2,
    author: "Anna",
    text: "Vestibulum risus sem, posuere nec dolor vitae, posuere auctor justo. In tincidunt dolor ullamcorper, pharetra arcu id, gravida nibh. Quisque pulvinar finibus velit in blandit. Vestibulum eget leo sed metus scelerisque fermentum ut id leo.",
    created: "15/05/2021",
    archived: false,
  },
  {
    id: 3,
    author: "Matthew",
    text: "Integer turpis arcu, faucibus sit amet aliquam nec, sollicitudin sed ante. Cras accumsan massa id ex mattis, non fringilla tellus ultricies. Aenean odio arcu, faucibus eu massa eget, interdum fringilla neque. Donec id tellus molestie, bibendum nunc nec, sodales risus. Sed nec purus quis quam lobortis condimentum.",
    created: "15/05/2021",
    archived: false,
  },
  {
    id: 4,
    author: "William",
    text: "Curabitur eu nibh erat. Duis rhoncus arcu ex, et vulputate ligula maximus ac. Morbi scelerisque nisi non dolor auctor, eu feugiat velit gravida. Phasellus faucibus purus libero, ac pellentesque elit finibus et. Duis a erat cursus, varius ex feugiat, interdum nunc.",
    created: "15/05/2021",
    archived: false,
  },
];

We will use this data to render a list of clickable reports. Each item has a unique id that will be used as a key in the loop.

If you’re not sure the purpose of the key prop in loops in React, you can check out one of my previous articles, “A Beginner’s Guide to Loops in React JSX”, where I explain why the key prop is needed and how to write loops in React.

We also have author name, created date, and archived property, which indicates current status of a report. Next, let’s create a Reports component that will handle looping through the reports data we just defined.

src/components/Reports/Reports.js

import styles from "./Reports.module.css";
import { reportsData } from "./reportsData";
import ReportItem from "./ReportItem";
import { useState } from "react";

const Reports = () => {
  const [reports, setReports] = useState(reportsData);

  return (
    <div>
      <h1>Reports</h1>
      <div className={styles.reportsList}>
        {reports.map(report => {
          return (
            <ReportItem
              key={report.id}
              report={report}
            />
          );
        })}
      </div>
    </div>
  );
};

export default Reports;

We are using CSS modules to add some styles for the reports. We also have the ReportItem component, which is used for each report in the reportsData. Below you can find code for both.

src/components/Reports/Reports.module.css

/* Center the list and restrict its width */
.reportsList {
  max-width: 30rem;
  margin: 0 auto;
}

/* Add border between list items */
.reportItem + .reportItem {
  border-top: 1px solid #ddd;
}

/* A bit of styling for each report item in the reports list */
.reportItem {
  text-align: left;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0.75rem;
  width: 370px;
}

/* Show different background when user is hovering over an item */
.reportItem:hover {
  background-color: #eee;
}

/* Display a different background color on a report that is archived */
.archivedReport {
  background-color: #f3f4f6;
}

src/components/Reports/ReportItem.js

import { useState } from "react";
import styles from "./Reports.module.css";

const ReportItem = props => {
  const { report } = props;
  const { author, text, created, archived } = report
  return (
    <>
      <button
        onClick={() => {}}
        className={`${styles.reportItem} ${
          archived ? styles.archivedReport : ""
        }`}
      >
        <div>
          {author} - {created}
        </div>
        <span>{text.slice(0, 55).padEnd(58, "...")}</span>
      </button>
    </>
  );
};

export default ReportItem;

In the ReportItem component, we have a button that contains information about the author, when the report was created, and an excerpt of the description, padded by an ellipsis. At the moment, the button is not doing anything, as we passed a noop* function. Now, let’s finally add the React Window component.

* Noop stands for a “no-operation” function that basically does not do anything.

Adding React Window Component

The functionality we want to implement now is quite simple. When we click on any of the reports, we want to open a new dialog popup that will contain the full text of the report as well as a button to archive or unarchive the report. Let’s start by creating a new ReportWindow component.

src/components/Reports/ReportWindow.js

import { Window, WindowActionsBar } from "@progress/kendo-react-dialogs";

const ReportWindow = props => {
  const { report, setShowWindow, onChangeArchiveStatus } = props;
  const { id, author, text, archived } = report;

  return (
    <Window
      title={author}
      onClose={() => setShowWindow(false)}
      initialHeight={350}
    >
      <div>{text}</div>

      <WindowActionsBar layout="end">
        <button
          type="button"
          className="k-button k-primary"
          onClick={() => {
            onChangeArchiveStatus(id);
            setShowWindow(false);
          }}
        >
          {`${archived ? "Unarchive" : "Archive"} report`}
        </button>
      </WindowActionsBar>
    </Window>
  );
};

export default ReportWindow;

We have imported Window and WindowActionsBar components from the @progress/kendo-react-dialogs package. The former one is the dialog popup that provides functionality to minimize, maximize, resize and drag out of the box. The latter component is used to add any actionable buttons. In this case, we want to add one button that will either archive or unarchive a report.

The ReportWindow component will receive three props:

  • report – The report object from the reportsData array. It’s used to display report data.
  • setShowWindow – The function that is used to set the open state of the Window component. It expects a boolean value.
  • onChangeArchiveStatus – The function that is used to update the archived property of the report. It excepts a report id.

We return the Window component that contains the report text and WindowActionsBar component with the actionable button. The button will display an “Archive report” or “Unarchive report” message depending on the current archived status of the report.

You can find the full list of accepted props by the React Window component here and by the WindowActionsBar component here.

Now, let’s update ReportItem and Reports components, as we need to add the onChangeArchiveStatus function and new state to handle visibility of the ReportWindow component.

src/components/Reports/ReportItem.js

import { useState } from "react";
import ReportWindow from "./ReportWindow";
import styles from "./Reports.module.css";

const ReportItem = props => {
  const { report, onChangeArchiveStatus } = props;
  const { author, text, created, archived } = report;
  const [showWindow, setShowWindow] = useState(false);
  return (
    <>
      <button
        onClick={() => setShowWindow(true)}
        className={`${styles.reportItem} ${
          archived ? styles.archivedReport : ""
        }`}
      >
        <div>
          {author} - {created}
        </div>
        <span>{text.slice(0, 55).padEnd(58, "...")}</span>
      </button>
      {showWindow ? (
        <ReportWindow
          setShowWindow={setShowWindow}
          report={report}
          onChangeArchiveStatus={onChangeArchiveStatus}
        />
      ) : null}
    </>
  );
};

export default ReportItem;

As you can see, we have imported the ReportWindow component, which is rendered any time the showWindow state is set to true. You might now think, why do we have showWindow inside of the ReportItem component and not Reports. The way it is, we will have a new state for every single report item, and that’s exactly what we want, as we need to be able to open multiple windows at the same time. That’s why each report item has its own state for the React Window component.

Now, let’s update the Reports component, as that’s where the onChangeArchiveStatus function is coming from.

src/components/Reports/Reports.js

import styles from "./Reports.module.css";
import { reportsData } from "./reportsData";
import ReportItem from "./ReportItem";
import { useState } from "react";

const Reports = () => {
  const [reports, setReports] = useState(reportsData);

  const onChangeArchiveStatus = id => {
    const updatedReports = reports.map(report => {
      if (report.id === id) {
        return {
          ...report,
          archived: !report.archived,
        };
      }
      return report;
    });
    setReports(updatedReports);
  };

  return (
    <div>
      <h1>Reports</h1>
      <div className={styles.reportsList}>
        {reports.map(report => {
          return (
            <ReportItem
              key={report.id}
              report={report}
              onChangeArchiveStatus={onChangeArchiveStatus}
            />
          );
        })}
      </div>
    </div>
  );
};

export default Reports;

The onChangeArchiveStatus function loops through the reports and flips the archived state for the report that matches the id that was passed as an argument. That’s all we had to change.

Any time you click on a report, a React Window will be opened. What’s more, you can click on the Archive report button to change its archived status. This will immediately close the React Window, and you will see that the background of the report will change to indicate the archived state. If you would like to change it back, just open the report again and click on the “Unarchive report” button.

A really great thing about React Window implementation here is the fact that we can easily open multiple reports, resize and drag them, and see a few of them at once. Below you can try the interactive example in StackBlitz.

You can also find full code in this GitHub repo.

Wrap-up

We have successfully created a React project, installed KendoReact, and implemented a desktop-like functionality that allows us to have modals that can be minimized, maximized, resized and dragged.

KendoReact offers many useful components, and we have just covered the basics of using the React Window component. Therefore, make sure to check out its documentation to see what else is possible.


Thomas Findlay-2
About the Author

Thomas Findlay

Thomas Findlay is a 5-star rated mentor, full-stack developer, consultant, technical writer and the author of “React - The Road To Enterprise” and “Vue - The Road To Enterprise.” He works with many different technologies such as JavaScript, Vue, React, React Native, Node.js, Python, PHP and more. Thomas has worked with developers and teams from beginner to advanced and helped them build and scale their applications and products. Check out his Codementor page, and you can also find him on Twitter.

Related Posts

Comments

Comments are disabled in preview mode.