Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions src/react/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# React Components for Angular2-HN Migration

This directory contains React components migrated from the Angular 9 Hacker News application as part of Phase 1 and Phase 2 of the migration plan.

## Prerequisites

To use these components, you'll need to install the following dependencies:

```bash
npm install react react-dom react-router-dom
npm install -D @types/react @types/react-dom
```

## Directory Structure

```
src/react/
├── contexts/
│ └── SettingsContext.tsx # React Context for settings state management
├── components/
│ ├── shared/
│ │ ├── Loader/ # Loading indicator component
│ │ └── ErrorMessage/ # Error message display component
│ ├── feeds/
│ │ └── Item/ # Story card component
│ └── core/
│ ├── Footer/ # Footer component
│ ├── Header/ # Navigation header component
│ └── Settings/ # Settings panel component
├── types/
│ └── index.ts # TypeScript interfaces
├── utils/
│ └── formatComment.ts # Comment formatting utility
├── index.ts # Main exports
├── tsconfig.json # TypeScript configuration for React
└── README.md # This file
```

## Components

### Phase 1: Shared/Utility Components

#### Loader
A pure presentational component that displays a loading animation.

```tsx
import { Loader } from './react';

<Loader />
```

#### ErrorMessage
A presentational component that displays error messages with a skull icon.

```tsx
import { ErrorMessage } from './react';

<ErrorMessage message="Something went wrong!" />
```

#### Item
A story card component that displays individual Hacker News items.

```tsx
import { Item } from './react';
import { Story } from './react/types';

const story: Story = { /* ... */ };
<Item item={story} />
```

### Phase 2: Core Layout Components

#### Footer
A static footer component with a GitHub link.

```tsx
import { Footer } from './react';

<Footer />
```

#### Header
A navigation header component with links and settings toggle.

```tsx
import { Header } from './react';

<Header />
```

#### Settings
A settings panel component for configuring app preferences.

```tsx
import { Settings } from './react';

<Settings />
```

## Context Provider

The `SettingsProvider` must wrap your application to provide settings state to all components:

```tsx
import { SettingsProvider } from './react';
import { BrowserRouter } from 'react-router-dom';

function App() {
return (
<BrowserRouter>
<SettingsProvider>
{/* Your app components */}
</SettingsProvider>
</BrowserRouter>
);
}
```

## Settings Context

The settings context provides the following state and methods:

- `settings`: Current settings object
- `showSettings`: boolean - Whether settings panel is visible
- `openLinkInNewTab`: boolean - Whether to open links in new tab
- `theme`: 'default' | 'night' | 'amoledblack' - Current theme
- `titleFontSize`: string - Font size for titles
- `listSpacing`: string - Spacing between list items

- `toggleSettings()`: Toggle settings panel visibility
- `toggleOpenLinksInNewTab()`: Toggle link behavior
- `setTheme(theme)`: Set the current theme
- `setFont(fontSize)`: Set the title font size
- `setSpacing(listSpacing)`: Set the list spacing

## Migration Notes

These components are designed to run alongside the existing Angular components during the incremental migration process. The CSS styles have been converted from SCSS to plain CSS to work independently.

The `SettingsService` from Angular has been converted to a React Context (`SettingsContext`) that provides the same functionality using React hooks.
22 changes: 22 additions & 0 deletions src/react/components/core/Footer/Footer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#footer {
position: relative;
padding: 10px;
height: 60px;
letter-spacing: 0.7px;
text-align: center;
}

#footer a {
font-weight: bold;
text-decoration: none;
}

#footer a:hover {
text-decoration: underline;
}

@media only screen and (max-width: 768px) {
#footer {
display: none;
}
}
17 changes: 17 additions & 0 deletions src/react/components/core/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import './Footer.css';

export const Footer: React.FC = () => {
return (
<div id="footer">
<p>
Show this project some love on{' '}
<a href="https://github.com/hdjirdeh/angular2-hn" target="_blank" rel="noopener noreferrer">
GitHub
</a>
</p>
</div>
);
};

export default Footer;
1 change: 1 addition & 0 deletions src/react/components/core/Footer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Footer, default } from './Footer';
164 changes: 164 additions & 0 deletions src/react/components/core/Header/Header.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#header {
color: #fff;
padding: 6px 0;
line-height: 18px;
vertical-align: middle;
position: relative;
z-index: 1;
width: 100%;
}

@media only screen and (max-width: 768px) {
#header {
height: 50px;
position: fixed;
top: 0;
}
}

#header a {
display: inline;
}

.home-link {
width: 50px;
height: 66px;
}

.logo-inner {
width: 32px;
position: absolute;
left: 17px;
top: 18px;
z-index: -1;
height: 32px;
border-radius: 50%;
}

@media only screen and (max-width: 768px) {
.logo-inner {
left: 16px;
top: 12px;
}
}

.logo {
width: 50px;
padding: 3px 8px 0;
}

@media only screen and (max-width: 768px) {
.logo {
width: 45px;
padding: 0 0 0 10px;
}
}

h1 {
font-weight: normal;
display: inline-block;
vertical-align: middle;
margin: 0;
font-size: 16px;
}

h1 a {
color: #fff;
text-decoration: none;
}

.name {
margin-right: 30px;
margin-bottom: 2px;
}

@media only screen and (max-width: 768px) {
.name {
display: none;
}
}

.header-text {
position: absolute;
width: inherit;
height: 20px;
left: 10px;
top: 27px;
z-index: -1;
}

@media only screen and (max-width: 768px) {
.header-text {
top: 22px;
}
}

.left {
position: absolute;
left: 60px;
font-size: 16px;
}

@media only screen and (max-width: 768px) {
.left {
width: 100%;
left: 0;
}
}

.header-nav {
display: inline-block;
margin-left: 20px;
}

@media only screen and (max-width: 768px) {
.header-nav {
margin-left: 60px;
}
}

.header-nav a {
color: hsla(0, 0%, 100%, 0.9);
text-decoration: none;
margin: 0 5px;
letter-spacing: 1.8px;
}

.header-nav a:hover {
color: #fff;
}

.header-nav a.active {
color: #fff;
}

.info {
position: absolute;
top: 0;
right: 20px;
height: 100%;
}

@media only screen and (max-width: 768px) {
.info {
right: 10px;
}
}

.info img {
opacity: 0.8;
width: 25px;
margin-top: 21.5px;
display: block;
}

.info img:hover {
opacity: 1;
cursor: pointer;
}

@media only screen and (max-width: 768px) {
.info img {
margin-top: 15px;
}
}
Loading