website refactor
This commit is contained in:
@@ -110,27 +110,35 @@ export function TeamsTemplate({ teams, searchQuery, onSearchChange, onTeamClick
|
|||||||
- Can use Next.js hooks (but not Next.js components like `Link`)
|
- Can use Next.js hooks (but not Next.js components like `Link`)
|
||||||
- Should be reusable within the app context
|
- Should be reusable within the app context
|
||||||
- **Strictly Forbidden**: Generic UI primitives (`Box`, `Surface`) and generic wrappers (`Layout`, `Container`)
|
- **Strictly Forbidden**: Generic UI primitives (`Box`, `Surface`) and generic wrappers (`Layout`, `Container`)
|
||||||
- **Strictly Forbidden**: The `className` prop (styling must be handled by UI components)
|
- **Strictly Forbidden**: Raw HTML tags (use `ui/` components instead)
|
||||||
|
- **Strictly Forbidden**: The `className` or `style` props
|
||||||
|
- **Strictly Forbidden**: Passing implementation details (Tailwind classes, CSS values) as props to UI components
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
```typescript
|
```typescript
|
||||||
// components/teams/TeamLeaderboardPreview.tsx
|
// components/teams/TeamLeaderboardPreview.tsx
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardHeader, TeamRow } from '@/ui';
|
||||||
|
|
||||||
export function TeamLeaderboardPreview({ teams, onTeamClick }: Props) {
|
export function TeamLeaderboardPreview({ teams, onTeamClick }: Props) {
|
||||||
// App-specific logic: medal colors, ranking, etc.
|
// App-specific logic: medal colors, ranking, etc.
|
||||||
const getMedalColor = (position: number) => { /* ... */ };
|
const getMedalColor = (position: number) => {
|
||||||
|
if (position === 0) return 'gold';
|
||||||
|
if (position === 1) return 'silver';
|
||||||
|
return 'none';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card variant="elevated">
|
||||||
<CardHeader>Top Teams</CardHeader>
|
<CardHeader title="Top Teams" />
|
||||||
{teams.map((team, index) => (
|
{teams.map((team, index) => (
|
||||||
<TeamRow
|
<TeamRow
|
||||||
key={team.id}
|
key={team.id}
|
||||||
team={team}
|
name={team.name}
|
||||||
position={index}
|
rank={index + 1}
|
||||||
onClick={() => onTeamClick(team.id)}
|
onClick={() => onTeamClick(team.id)}
|
||||||
medalColor={getMedalColor(index)}
|
achievement={getMedalColor(index)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Card>
|
</Card>
|
||||||
@@ -153,14 +161,22 @@ export function TeamLeaderboardPreview({ teams, onTeamClick }: Props) {
|
|||||||
- Only receive props and render
|
- Only receive props and render
|
||||||
- Maximum reusability
|
- Maximum reusability
|
||||||
- **Strict Layering**: Generic primitives (`Box`, `Surface`, `Stack`, `Grid`) are internal to this layer.
|
- **Strict Layering**: Generic primitives (`Box`, `Surface`, `Stack`, `Grid`) are internal to this layer.
|
||||||
|
- **Encapsulation**: Must NOT expose `className`, `style`, or Tailwind-specific props to consumers.
|
||||||
|
- **Semantic APIs**: Use semantic props (e.g., `variant="primary"`, `size="large"`) instead of implementation details.
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
```typescript
|
```typescript
|
||||||
// ui/Button.tsx
|
// ui/Button.tsx
|
||||||
export function Button({ children, variant, onClick }: ButtonProps) {
|
// GOOD: Semantic API
|
||||||
|
export function Button({ children, variant = 'primary', onClick }: ButtonProps) {
|
||||||
|
const classes = {
|
||||||
|
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||||
|
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
|
||||||
|
}[variant];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`btn btn-${variant}`}
|
className={`px-4 py-2 rounded-md transition-colors ${classes}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -168,10 +184,8 @@ export function Button({ children, variant, onClick }: ButtonProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ui/Card.tsx
|
// BAD: Exposing implementation details
|
||||||
export function Card({ children, className }: CardProps) {
|
// export function Button({ className, backgroundColor, padding }: Props) { ... }
|
||||||
return <div className={`card ${className}`}>{children}</div>;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## UI Layering & Primitives
|
## UI Layering & Primitives
|
||||||
@@ -191,6 +205,31 @@ To prevent "div wrapper" abuse and maintain architectural integrity, we enforce
|
|||||||
|
|
||||||
**Rule of thumb**: If you need a styled container or layout in a component, use `Panel`, `Card`, `Layout`, or `Container`. If you need a new type of semantic layout, create it in `ui/` using primitives. Direct use of primitives (`Box`, `Surface`, `Stack`, `Grid`) or raw HTML tags in `components/` is forbidden.
|
**Rule of thumb**: If you need a styled container or layout in a component, use `Panel`, `Card`, `Layout`, or `Container`. If you need a new type of semantic layout, create it in `ui/` using primitives. Direct use of primitives (`Box`, `Surface`, `Stack`, `Grid`) or raw HTML tags in `components/` is forbidden.
|
||||||
|
|
||||||
|
## Clean Component APIs (Anti-Pattern: Prop Pollution)
|
||||||
|
|
||||||
|
We strictly forbid "Prop Pollution" where implementation details leak into component APIs. This is unmaintainable and unreadable.
|
||||||
|
|
||||||
|
### The Problem
|
||||||
|
When a component exposes props like `className`, `style`, `mt={4}`, or `bg="blue-500"`, it:
|
||||||
|
1. **Breaks Encapsulation**: The consumer needs to know about the internal styling system (Tailwind, CSS).
|
||||||
|
2. **Increases Fragility**: Changing the internal implementation (e.g., switching from Tailwind to CSS Modules) requires updating all consumers.
|
||||||
|
3. **Reduces Readability**: Component usage becomes cluttered with styling logic instead of business intent.
|
||||||
|
|
||||||
|
### The Solution: Semantic Props
|
||||||
|
Components must only expose props that describe **what** the component is or **how** it should behave semantically, not how it should look in terms of CSS.
|
||||||
|
|
||||||
|
| Bad Prop (Implementation) | Good Prop (Semantic) |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `className="mt-4 flex items-center"` | `spacing="large"` or `layout="horizontal"` |
|
||||||
|
| `color="#FF0000"` or `color="red-500"` | `intent="danger"` or `variant="error"` |
|
||||||
|
| `style={{ fontWeight: 'bold' }}` | `emphasis="high"` |
|
||||||
|
| `isFullWidth={true}` | `size="full"` |
|
||||||
|
|
||||||
|
### Enforcement
|
||||||
|
- **`ui/` components**: May use Tailwind/CSS internally but **MUST NOT** expose these details in their props.
|
||||||
|
- **`components/` components**: **MUST NOT** use `className`, `style`, or any prop that accepts raw styling values. They must only use the semantic APIs provided by `ui/`.
|
||||||
|
- **`templates/`**: Same as `components/`.
|
||||||
|
|
||||||
## The Display Object Layer
|
## The Display Object Layer
|
||||||
|
|
||||||
**Purpose**: Reusable formatting and presentation logic
|
**Purpose**: Reusable formatting and presentation logic
|
||||||
@@ -266,8 +305,9 @@ ui/ (generic primitives)
|
|||||||
|
|
||||||
## Key Benefits
|
## Key Benefits
|
||||||
|
|
||||||
1. **Clear boundaries**: Each layer has a single responsibility
|
1. **Clear boundaries**: Each layer has a single responsibility.
|
||||||
2. **Testability**: Pure functions at UI/Display layer
|
2. **Encapsulation**: Implementation details (HTML, CSS, Tailwind) are hidden within the `ui/` layer.
|
||||||
3. **Reusability**: UI elements can be used anywhere
|
3. **Semantic APIs**: Components are easier to read and use because they speak the language of the domain, not CSS.
|
||||||
4. **Maintainability**: Changes in one layer don't ripple
|
4. **Maintainability**: Changes to styling or internal implementation don't ripple through the entire codebase.
|
||||||
5. **Type safety**: Clear contracts between layers
|
5. **Testability**: Pure functions at UI/Display layer.
|
||||||
|
6. **Type safety**: Clear, restricted contracts between layers prevent "prop soup".
|
||||||
|
|||||||
Reference in New Issue
Block a user