Skip to main content
About Me is a wizard-style application that provides personal information in a classic Windows XP tour interface.

Features

  • Wizard Interface: Multi-step navigation with Back/Next buttons
  • Radio Button Selection: Choose which section to view
  • Multiple Sections: Skills, Projects, Contact information
  • XP Styling: Authentic Windows XP wizard appearance
  • Tour Image: Left panel with branded image
  • Navigation Controls: Back, Next, Cancel buttons

Component Structure

Location: src/WinXP/apps/AboutMe/index.jsx
function AboutMe({ onClose }) {
  const [activeSection, setActiveSection] = useState('initial');
  const [selectedOption, setSelectedOption] = useState(null);
  
  const sections = {
    skills: { /* ... */ },
    projects: { /* ... */ },
    contact: { /* ... */ },
  };
  
  // Navigation and rendering logic
}

Configuration

From apps/index.jsx:
AboutMe: {
  name: 'AboutMe',
  header: { icon: aboutMeIcon, title: 'aduncan.dev Tour' },
  component: AboutMe,
  defaultSize: { width: 550, height: 400 },
  defaultOffset: getCenter(550, 400),
  resizable: false,
  minimized: false,
  maximized: shouldMaximize(550, 400, false),
  multiInstance: false,
}

Section Structure

Define sections with content:
const sections = {
  skills: {
    id: 'skills',
    label: 'My Skills',
    title: 'My Skills',
    content: (
      <>
        <p>Here's a list of my skills:</p>
        <ul>
          <li>uhh...</li>
          <li>i dont really know yet</li>
        </ul>
      </>
    ),
  },
  projects: {
    id: 'projects',
    label: 'My Projects',
    title: 'My Projects',
    content: (
      <>
        <p>Check out some of my projects:</p>
        <ul>
          <li>
            <strong>Something or another:</strong> Maybe I'm working on something...
          </li>
        </ul>
      </>
    ),
  },
  contact: {
    id: 'contact',
    label: 'Contact Me',
    title: 'Contact Me',
    content: (
      <>
        <p>You can reach me through the following channels:</p>
        <ul>
          <li>Email: [email protected]</li>
          <li>Discord: skillzdev</li>
          <li>GitHub: github.com/skillz808</li>
        </ul>
      </>
    ),
  },
};

Option Selection

const handleOptionSelect = optionId => {
  setSelectedOption(optionId);
};

Next Button

const handleNext = () => {
  if (selectedOption) {
    setActiveSection(selectedOption);
  }
};

<DialogButton
  onClick={handleNext}
  disabled={!selectedOption || activeSection !== 'initial'}
>
  Next &gt;
</DialogButton>

Back Button

const handleBack = () => {
  if (activeSection !== 'initial') {
    setActiveSection('initial');
  }
};

<DialogButton
  onClick={handleBack}
  disabled={activeSection === 'initial'}
>
  &lt; Back
</DialogButton>

Initial Selection View

const renderRightPanelContent = () => {
  if (activeSection === 'initial') {
    return (
      <InitialSelectionView>
        <h2>Welcome to the About Me Section!</h2>
        <p>Please select an option below and click Next.</p>
        <OptionGroup>
          {Object.values(sections).map(option => (
            <RadioButtonLabel key={option.id}>
              <input
                type="radio"
                name="aboutMeOption"
                value={option.id}
                checked={selectedOption === option.id}
                onChange={() => handleOptionSelect(option.id)}
              />
              <span className="radio-custom"></span>
              {option.label}
            </RadioButtonLabel>
          ))}
        </OptionGroup>
      </InitialSelectionView>
    );
  }
  
  // Section detail view
};

Section Detail View

const currentSectionDetails = sections[activeSection];
if (currentSectionDetails) {
  return (
    <SectionDetailView>
      <HeaderBar>
        <h2>{currentSectionDetails.title}</h2>
      </HeaderBar>
      <ContentArea>{currentSectionDetails.content}</ContentArea>
    </SectionDetailView>
  );
}

Layout Structure

<AppWindow>
  <MainContentArea>
    <LeftPanel>
      <img src={placeholderLeftImage} alt="About me visual" />
    </LeftPanel>
    <RightPanel>{renderRightPanelContent()}</RightPanel>
  </MainContentArea>
  <BottomControls>
    <DialogButton onClick={handleBack} disabled={activeSection === 'initial'}>
      &lt; Back
    </DialogButton>
    <DialogButton
      onClick={handleNext}
      disabled={!selectedOption || activeSection !== 'initial'}
    >
      Next &gt;
    </DialogButton>
    <DialogButton onClick={onClose}>Cancel</DialogButton>
  </BottomControls>
</AppWindow>

Styling

XP Color Scheme

const AppWindow = styled.div`
  background-color: #ece9d8; // Classic XP background
  font-family: 'Tahoma', sans-serif;
  font-size: 11px;
  color: #000;
`;

Left Panel

const LeftPanel = styled.div`
  width: 170px;
  background-color: #f0f0f0;
  border-right: 1px solid #aca899;
  
  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
`;

Custom Radio Buttons

const RadioButtonLabel = styled.label`
  display: flex;
  align-items: center;
  cursor: pointer;
  
  input[type='radio'] {
    opacity: 0;
    width: 0;
    height: 0;
  }
  
  .radio-custom {
    width: 13px;
    height: 13px;
    border: 1px solid #3f8f2e;
    border-radius: 50%;
    margin-right: 8px;
    background-color: #fff;
    box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.1);
  }
  
  input[type='radio']:checked + .radio-custom::after {
    content: '';
    width: 7px;
    height: 7px;
    background-color: #3f8f2e;
    border-radius: 50%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
`;

Dialog Buttons

const DialogButton = styled.button`
  font-family: 'Tahoma', sans-serif;
  font-size: 11px;
  min-width: 75px;
  height: 23px;
  border: 1px solid #000;
  background-color: #f0f0f0;
  box-shadow: inset 1px 1px 0px #ffffff, inset -1px -1px 0px #808080;
  cursor: pointer;
  
  &:hover {
    border-color: #0078d7;
  }
  
  &:active {
    background-color: #e0e0e0;
    box-shadow: inset -1px -1px 0px #ffffff, inset 1px 1px 0px #808080;
    padding-top: 1px;
  }
  
  &:disabled {
    color: #808080;
    cursor: default;
    border-color: #a0a0a0;
  }
  
  &:focus {
    outline: 1px dotted #000000;
    outline-offset: -4px;
  }
`;

Customization

Adding New Sections

const sections = {
  // Existing sections...
  education: {
    id: 'education',
    label: 'My Education',
    title: 'Education Background',
    content: (
      <>
        <p>My educational background:</p>
        <ul>
          <li>University Name - Degree (Year)</li>
          <li>Certifications and courses</li>
        </ul>
      </>
    ),
  },
  experience: {
    id: 'experience',
    label: 'Work Experience',
    title: 'Professional Experience',
    content: (
      <>
        <p>My work experience:</p>
        <ul>
          <li><strong>Company Name:</strong> Role (Year - Year)</li>
          <li>Key achievements and responsibilities</li>
        </ul>
      </>
    ),
  },
};

Changing Tour Image

Replace the left panel image:
import customTourImage from 'assets/custom-tour-image.png';

<LeftPanel>
  <img src={customTourImage} alt="About me visual" />
</LeftPanel>

Dynamic Content

Load content from props or API:
function AboutMe({ onClose, userData }) {
  const sections = useMemo(() => ({
    skills: {
      id: 'skills',
      label: 'My Skills',
      title: 'My Skills',
      content: (
        <>
          <p>Here's a list of my skills:</p>
          <ul>
            {userData.skills.map(skill => (
              <li key={skill}>{skill}</li>
            ))}
          </ul>
        </>
      ),
    },
    // Other sections...
  }), [userData]);
  
  // Rest of component...
}

Usage Example

import { AboutMe } from 'src/WinXP/apps';

function Desktop() {
  const userData = {
    skills: ['React', 'TypeScript', 'Node.js'],
    projects: [
      { name: 'Web XP', description: 'Windows XP in the browser' }
    ],
    contact: {
      email: '[email protected]',
      github: 'username',
    },
  };
  
  return (
    <Window title="aduncan.dev Tour">
      <AboutMe 
        onClose={handleClose}
        userData={userData}
      />
    </Window>
  );
}

Accessibility

  • Keyboard navigation through radio buttons
  • Tab order follows visual layout
  • Focus indicators on all interactive elements
  • Disabled state clearly visible
  • Radio buttons have proper labels
  • Buttons have descriptive text
  1. Initial View: User sees welcome message and radio button options
  2. Select Option: User clicks a radio button to select a section
  3. Next Button: Enabled when option is selected, navigates to detail view
  4. Detail View: Shows selected section content with title and body
  5. Back Button: Returns to initial view to select different section
  6. Cancel Button: Closes the application at any time

Button States

Back Button

  • Disabled: On initial view (nowhere to go back to)
  • Enabled: On any detail view

Next Button

  • Disabled: When no option selected OR already on detail view
  • Enabled: When option selected on initial view

Cancel Button

  • Always Enabled: Can close at any time

Fixed Size Window

About Me has a fixed size:
resizable: false,
defaultSize: { width: 550, height: 400 },
This ensures:
  • Consistent layout across all screens
  • Optimized content display
  • Predictable button positioning
  • Tour image displays correctly

Future Enhancements

  • Add more sections (Portfolio, Resume, etc.)
  • Include clickable links in contact section
  • Add images to project descriptions
  • Implement progress indicator
  • Add breadcrumb navigation
  • Include timeline for experience section
  • Add download resume button
  • Integrate with portfolio website

Build docs developers (and LLMs) love