Skip to main content

Overview

The Icons component manages the desktop icon grid with full selection support including single-click selection and drag-to-select rubber band functionality.

Component Structure

function Icons({
  icons,              // Array of icon data
  onMouseDown,        // Single icon click handler
  onDoubleClick,      // Double-click to open
  displayFocus,       // Whether to show focus state
  mouse,              // Mouse position from useMouse hook
  selecting,          // Rubber band selection state
  setSelectedIcons,   // Callback to update selection
}) {
  return (
    <IconsContainer>
      {icons.map(iconData => (
        <StyledIcon
          key={iconData.id}
          {...iconData}
          displayFocus={displayFocus}
          onMouseDown={onMouseDown}
          onDoubleClick={onDoubleClick}
        />
      ))}
    </IconsContainer>
  );
}
Source: src/WinXP/Icons/index.jsx:4-46

Icon Layout

Icons are arranged in a vertical column layout using flexbox:
const IconsContainer = styled.div`
  position: absolute;
  top: 40px;
  left: 40px;
  right: 20px;
  bottom: 30px;

  display: flex;
  flex-direction: column;  // Vertical layout
  flex-wrap: wrap;         // Wrap to next column
  align-content: flex-start;
  overflow-y: auto;
  overflow-x: hidden;
  gap: 15px;

  pointer-events: none;    // Let clicks pass through container
`;
Source: src/WinXP/Icons/index.jsx:96-112
The container has pointer-events: none to allow desktop clicks, while individual icons have pointer-events: auto.

Icon State

Each icon has the following structure:
{
  id: 0,                          // Unique identifier
  icon: ie,                       // Icon image source
  title: 'Internet Explorer',     // Display name
  component: InternetExplorer,    // Component to launch
  isFocus: false,                 // Selection state
  appName: 'Internet Explorer',   // App identifier
}
Source: src/WinXP/apps/index.jsx:136-143

Default Icons

export const defaultIconState = [
  {
    id: genId(),
    icon: ie,
    title: 'Internet Explorer',
    component: InternetExplorer,
    isFocus: false,
    appName: 'Internet Explorer',
  },
  {
    id: genId(),
    icon: mine,
    title: 'Minesweeper',
    component: WrappedMinesweeper,
    isFocus: false,
    appName: 'Minesweeper',
  },
  {
    id: genId(),
    icon: computerLarge,
    title: 'My Computer',
    component: MyComputer,
    isFocus: false,
    appName: 'My Computer',
  },
  // ... more icons
];
Source: src/WinXP/apps/index.jsx:135-232

Single Icon Selection

Clicking an icon focuses it and blurs others:
function onMouseDownIcon(id) {
  dispatch({ type: FOCUS_ICON, payload: id });
}
Source: src/WinXP/index.jsx:123-125

Reducer Logic

case FOCUS_ICON: {
  const icons = state.icons.map(icon => ({
    ...icon,
    isFocus: icon.id === action.payload,  // Only focused icon is true
  }));
  return {
    ...state,
    focusing: FOCUSING.ICON,
    icons,
  };
}
Source: src/WinXP/reducer.js:118-128

Double-Click to Open

Double-clicking an icon launches the application:
function onDoubleClickIcon(component) {
  const appSetting = Object.values(appSettings).find(
    setting => setting.component === component,
  );
  if (appSetting) {
    dispatch({ type: ADD_APP, payload: appSetting });
  }
}
Source: src/WinXP/index.jsx:126-133
Double-clicking the icon graphic or text will both launch the application.

Rubber Band Selection

Drag-to-select functionality with a visual selection box.

Initiating Selection

Clicking on the desktop background starts selection:
function onMouseDownDesktop(e) {
  if (e.target === e.currentTarget) {
    dispatch({ type: FOCUS_DESKTOP });
    dispatch({
      type: START_SELECT,
      payload: { x: mouse.docX, y: mouse.docY },
    });
  }
}
Source: src/WinXP/index.jsx:192-200

Selection State

case START_SELECT:
  return {
    ...state,
    focusing: FOCUSING.DESKTOP,
    icons: state.icons.map(icon => ({
      ...icon,
      isFocus: false,  // Clear all selections
    })),
    selecting: action.payload,  // Store start position
  };
Source: src/WinXP/reducer.js:149-158

Visual Selection Box

The DashedBox component renders the selection rectangle:
function DashedBox({ mouse, startPos }) {
  function getRect() {
    return {
      x: Math.min(startPos.x, mouse.docX),
      y: Math.min(startPos.y, mouse.docY),
      w: Math.abs(startPos.x - mouse.docX),
      h: Math.abs(startPos.y - mouse.docY),
    };
  }
  if (startPos) {
    const { x, y, w, h } = getRect();
    return (
      <div
        style={{
          transform: `translate(${x}px,${y}px)`,
          width: w,
          height: h,
          position: 'absolute',
          border: `1px dotted gray`,
        }}
      />
    );
  }
  return null;
}
Source: src/components/DashedBox/index.jsx

Collision Detection

Icons measure their positions and update selection based on overlap:
const [iconsRect, setIconsRect] = useState([]);

// Each icon measures itself on mount
function measure(rect) {
  if (iconsRect.find(r => r.id === rect.id)) return;
  setIconsRect(prevIconsRect => [...prevIconsRect, rect]);
}

useEffect(() => {
  if (!selecting) return;
  const sx = Math.min(selecting.x, mouse.docX);
  const sy = Math.min(selecting.y, mouse.docY);
  const sw = Math.abs(selecting.x - mouse.docX);
  const sh = Math.abs(selecting.y - mouse.docY);
  
  // Find icons intersecting selection box
  const selectedIds = iconsRect
    .filter(rect => {
      const { x, y, w, h } = rect;
      return x - sx < sw && sx - x < w && y - sy < sh && sy - y < h;
    })
    .map(icon => icon.id);
  
  setSelectedIcons(selectedIds);
}, [iconsRect, setSelectedIcons, selecting, mouse.docX, mouse.docY]);
Source: src/WinXP/Icons/index.jsx:13-31

Ending Selection

function onMouseUpDesktop() {
  if (state.selecting) {
    dispatch({ type: END_SELECT });
  }
}

case END_SELECT:
  return {
    ...state,
    selecting: null,  // Clear selection box
  };
Source: src/WinXP/index.jsx:201-205, src/WinXP/reducer.js:159-163

Multi-Selection

Multiple icons can be selected via rubber band:
case SELECT_ICONS: {
  const icons = state.icons.map(icon => ({
    ...icon,
    isFocus: action.payload.includes(icon.id),  // Check if in array
  }));
  return {
    ...state,
    icons,
    focusing: FOCUSING.ICON,
  };
}
Source: src/WinXP/reducer.js:129-139

Icon Styling

Basic Structure

function Icon({ title, icon, className, onMouseDown, onDoubleClick }) {
  return (
    <div
      className={className}
      onMouseDown={onMouseDown}
      onDoubleClick={onDoubleClick}
    >
      <div className={`${className}__img__container`}>
        <img src={icon} alt={title} className={`${className}__img`} />
      </div>
      <div className={`${className}__text__container`}>
        <div className={`${className}__text`}>{title}</div>
      </div>
    </div>
  );
}
Source: src/WinXP/Icons/index.jsx:79-93

Focus State

const StyledIcon = styled(Icon)`
  width: 70px;
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  pointer-events: auto;

  &__text {
    padding: 0 3px 2px;
    background-color: ${({ isFocus, displayFocus }) =>
      isFocus && displayFocus ? '#0b61ff' : 'transparent'};
    text-align: center;
    flex-shrink: 1;
  }
  
  &__img__container {
    width: 30px;
    height: 30px;
    filter: ${({ isFocus, displayFocus }) =>
      isFocus && displayFocus ? 'drop-shadow(0 0 blue)' : ''};
  }
  
  &__img {
    width: 30px;
    height: 30px;
    opacity: ${({ isFocus, displayFocus }) =>
      isFocus && displayFocus ? 0.5 : 1};
  }
`;
Source: src/WinXP/Icons/index.jsx:114-162
The displayFocus prop controls whether focus styling is shown. It’s true only when focusing === FOCUSING.ICON.

Clearing Icon Selection

Clicking the desktop, windows, or taskbar clears icon selection:
case FOCUS_DESKTOP:
  return {
    ...state,
    focusing: FOCUSING.DESKTOP,
    icons: state.icons.map(icon => ({
      ...icon,
      isFocus: false,  // Clear all selections
    })),
  };
Source: src/WinXP/reducer.js:140-148

Icon Position Measurement

Icons measure their DOM position for selection detection:
const ref = useRef(null);

useEffect(() => {
  const target = ref.current;
  if (!target) return;
  const { left, top, width, height } = ref.current.getBoundingClientRect();
  const posX = left + window.scrollX;
  const posY = top + window.scrollY;
  if (typeof measure === 'function') {
    measure({ id, x: posX, y: posY, w: width, h: height });
  }
}, [id, measure]);
Source: src/WinXP/Icons/index.jsx:69-78

Usage Example

import { Icons } from './WinXP/Icons';
import { DashedBox } from './components';
import useMouse from 'react-use/lib/useMouse';

function Desktop() {
  const ref = useRef(null);
  const mouse = useMouse(ref);
  const [state, dispatch] = useReducer(reducer, initState);

  const onIconsSelected = useCallback(
    iconIds => dispatch({ type: SELECT_ICONS, payload: iconIds }),
    [],
  );

  return (
    <Container ref={ref}>
      <Icons
        icons={state.icons}
        onMouseDown={id => dispatch({ type: FOCUS_ICON, payload: id })}
        onDoubleClick={component => {
          const app = appSettings[component];
          dispatch({ type: ADD_APP, payload: app });
        }}
        displayFocus={state.focusing === FOCUSING.ICON}
        mouse={mouse}
        selecting={state.selecting}
        setSelectedIcons={onIconsSelected}
      />
      <DashedBox startPos={state.selecting} mouse={mouse} />
    </Container>
  );
}

Next Steps

Desktop Overview

Return to desktop architecture

Windows

Learn about window management

Taskbar

Explore the taskbar and start menu

Build docs developers (and LLMs) love