npm package: @okrlinkhub/ui-kit
Description: Dumb React components for displaying OKR data. No Convex queries - all data passed via props.
Author: LinkHub Team
Latest version: 0.1.1

# @okrlinkhub/ui-kit

[![npm version](https://img.shields.io/npm/v/@okrlinkhub/ui-kit.svg)](https://www.npmjs.com/package/@okrlinkhub/ui-kit)
[![npm license](https://img.shields.io/npm/l/@okrlinkhub/ui-kit.svg)](https://www.npmjs.com/package/@okrlinkhub/ui-kit)

Dumb React components for displaying OKR data. No Convex queries - all data passed via props.

This library is designed to work seamlessly with [`okrhub-convex`](https://github.com/okrlinkhub/okrhub-convex) for building complete OKR management applications.

## Features

- 🎯 **Complete OKR Components**: Objectives, Key Results, Risks, and Initiatives
- 📦 **Zero Dependencies**: No Convex queries - all data passed via props
- 🎨 **Fully Customizable**: Inline styles for maximum portability
- 📱 **TypeScript Support**: Full type definitions included
- 🔄 **Cascade Selection**: Built-in support for hierarchical data selection
- 📄 **Pagination & Search**: Built-in pagination and search functionality
- 🎭 **Modal Components**: Pre-built modals for editing OKR entities

## Requirements

- React 18+ or React 19+
- React DOM 18+ or React DOM 19+

## Installation

```bash
npm install @okrlinkhub/ui-kit
```

or with yarn:

```bash
yarn add @okrlinkhub/ui-kit
```

or with pnpm:

```bash
pnpm add @okrlinkhub/ui-kit
```

## Quick Start - One-Page OKR Dashboard

Create a complete OKR dashboard with cascade selection in minutes:

```tsx
import { useState } from 'react';
import {
  ObjectiveSection,
  KeyResultSection,
  RisksSection,
  InitiativesSection,
} from '@okrlinkhub/ui-kit';

function OKRDashboard({ objectives, keyResults, risks, initiatives, teamMembers }) {
  const [selectedObjectiveId, setSelectedObjectiveId] = useState();
  const [selectedKeyResultId, setSelectedKeyResultId] = useState();
  const [selectedRiskId, setSelectedRiskId] = useState();

  // Filter data based on cascade selection
  const filteredKRs = selectedObjectiveId 
    ? keyResults.filter(kr => kr.objectiveId === selectedObjectiveId)
    : keyResults;
  
  const filteredRisks = selectedKeyResultId
    ? risks.filter(r => r.keyResultId === selectedKeyResultId)
    : risks;

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px' }}>
      <ObjectiveSection
        objectives={objectives}
        selectedObjectiveId={selectedObjectiveId}
        onObjectiveSelect={(id) => {
          setSelectedObjectiveId(id);
          setSelectedKeyResultId(undefined);
          setSelectedRiskId(undefined);
        }}
        onCreate={() => {/* create logic */}}
      />
      
      <KeyResultSection
        keyResults={filteredKRs}
        selectedKeyResultId={selectedKeyResultId}
        objectiveTitle={objectives.find(o => o.id === selectedObjectiveId)?.title}
        onKeyResultSelect={(id) => {
          setSelectedKeyResultId(id);
          setSelectedRiskId(undefined);
        }}
        onCreate={() => {/* create logic */}}
      />
      
      <RisksSection
        risks={filteredRisks}
        selectedRiskId={selectedRiskId}
        onRiskSelect={setSelectedRiskId}
        onCreate={() => {/* create logic */}}
      />
      
      <InitiativesSection
        initiatives={initiatives.filter(i => i.riskId === selectedRiskId)}
        riskDescription={risks.find(r => r.id === selectedRiskId)?.description}
        teamMembers={teamMembers}
        onCreate={(description) => {/* create logic */}}
      />
    </div>
  );
}
```

## Development

### Visualizzare i componenti durante lo sviluppo

Per visualizzare e testare i componenti UI durante lo sviluppo, usa l'app di showcase inclusa:

**Primo avvio:**
```bash
npm run dev:showcase
```

Questo comando:
1. Entra nella cartella `dev`
2. Installa le dipendenze (se necessario)
3. Avvia il server di sviluppo Vite

**Avvii successivi:**
```bash
cd dev
npm run dev
```

L'app si aprirà automaticamente su **`http://localhost:3001`** e mostrerà tutti i componenti con dati di esempio.

**Caratteristiche:**
- ✅ **Hot reload**: Le modifiche ai componenti si riflettono immediatamente
- ✅ **Dati di esempio**: Puoi modificarli in `dev/src/App.tsx` per testare diversi scenari
- ✅ **Import diretto**: I componenti vengono importati direttamente da `src/` (non dal build), quindi vedi le modifiche in tempo reale

**Compilazione della libreria:**
```bash
npm run dev  # Watch mode per la compilazione
npm run build  # Build una tantum
```

## Section Components (Recommended)

These are the new section components designed for building complete OKR dashboards. They include built-in pagination, search, filtering, and selection.

### ObjectiveSection

Displays a list of objectives with selection and search.

```tsx
import { ObjectiveSection } from '@okrlinkhub/ui-kit';

<ObjectiveSection
  objectives={objectives}
  selectedObjectiveId={selectedId}
  onObjectiveSelect={(id) => setSelectedId(id)}
  onEdit={(objective) => openEditModal(objective)}
  onCreate={() => openCreateModal()}
  isLoading={isLoading}
/>
```

**Props:**
- `objectives` - Array of Objective objects
- `selectedObjectiveId` - Currently selected objective ID
- `onObjectiveSelect` - Callback when an objective is selected
- `onEdit` - Callback when edit button is clicked
- `onCreate` - Callback when create button is clicked
- `isLoading` - Show loading skeleton

### KeyResultSection

Displays key results filtered by objective with progress indicators.

```tsx
import { KeyResultSection } from '@okrlinkhub/ui-kit';

<KeyResultSection
  keyResults={keyResults}
  selectedKeyResultId={selectedKRId}
  objectiveTitle="Increase Revenue"
  onKeyResultSelect={(id) => setSelectedKRId(id)}
  onEdit={(kr) => openEditModal(kr)}
  onCreate={() => openCreateModal()}
/>
```

### RisksSection

Displays risks with priority, KPI indicators, and initiative count.

```tsx
import { RisksSection } from '@okrlinkhub/ui-kit';

<RisksSection
  risks={risks}
  selectedRiskId={selectedRiskId}
  onRiskSelect={(id) => setSelectedRiskId(id)}
  onPriorityChange={(risk, newPriority) => updatePriority(risk, newPriority)}
  onResolve={(risk) => resolveRisk(risk)}
  onKpiClick={(risk) => openKpiModal(risk)}
  onCreate={() => openCreateModal()}
/>
```

### InitiativesSection

Displays initiatives with inline creation, assignee selection, and check-in support.

```tsx
import { InitiativesSection } from '@okrlinkhub/ui-kit';

<InitiativesSection
  initiatives={initiatives}
  riskDescription="Sales slowdown risk"
  teamMembers={teamMembers}
  onCreate={(description) => createInitiative(description)}
  onEdit={(initiative) => openEditModal(initiative)}
  onPriorityChange={(init, newPriority) => updatePriority(init, newPriority)}
  onAssigneeChange={(init, assigneeId) => updateAssignee(init, assigneeId)}
  onCheckIn={(initiative) => openCheckInModal(initiative)}
  highlight={true}
/>
```

## Modal Components

Pre-built modal components for editing OKR entities.

### EditObjectiveModal

```tsx
import { EditObjectiveModal } from '@okrlinkhub/ui-kit';

<EditObjectiveModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  objective={objective}
  onSave={async (updates) => await saveObjective(updates)}
  onDelete={async () => await deleteObjective()}
/>
```

### EditKeyResultModal

```tsx
import { EditKeyResultModal } from '@okrlinkhub/ui-kit';

<EditKeyResultModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  keyResult={keyResult}
  objectives={objectives}
  indicators={indicators}
  onSave={async (updates) => await saveKeyResult(updates)}
  onDelete={async () => await deleteKeyResult()}
/>
```

### EditRiskModal

```tsx
import { EditRiskModal } from '@okrlinkhub/ui-kit';

<EditRiskModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  risk={risk}
  keyResults={keyResults}
  onSave={async (updates) => await saveRisk(updates)}
  onDelete={async () => await deleteRisk()}
/>
```

### EditInitiativeModal

```tsx
import { EditInitiativeModal } from '@okrlinkhub/ui-kit';

<EditInitiativeModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  initiative={initiative}
  risks={risks}
  teamMembers={teamMembers}
  onSave={async (updates) => await saveInitiative(updates)}
  onDelete={async () => await deleteInitiative()}
  onReopen={async () => await reopenInitiative()}
/>
```

## Shared Components

Reusable utility components.

### PaginationFooter

```tsx
import { PaginationFooter, usePagination } from '@okrlinkhub/ui-kit';

const pagination = usePagination(items, 10);

<PaginationFooter
  currentPage={pagination.currentPage}
  totalPages={pagination.totalPages}
  onPageChange={pagination.setCurrentPage}
  getVisiblePageNumbers={pagination.getVisiblePageNumbers}
/>
```

### PriorityBadge

```tsx
import { PriorityBadge } from '@okrlinkhub/ui-kit';

<PriorityBadge priority="high" showLabel size="sm" />
<PriorityBadge priority="medium" onClick={() => cyclePriority()} />
```

### EmptyState

```tsx
import { EmptyState } from '@okrlinkhub/ui-kit';

<EmptyState
  icon={<TargetIcon />}
  message="No objectives found"
  description="Create your first objective to get started"
  actionLabel="Create Objective"
  onAction={() => openCreateModal()}
/>
```

## Legacy Components

These components are still available for backward compatibility but Section components are recommended for new projects.

### ObjectiveCard

Displays an objective with optional metadata.

```tsx
import { ObjectiveCard } from '@okrlinkhub/ui-kit';

<ObjectiveCard
  objective={{
    id: "1",
    title: "Increase Revenue Q1",
    description: "Focus on expanding sales channels",
    teamId: "team-1",
    teamName: "Sales",
    keyResultsCount: 3,
  }}
  onClick={() => navigate(`/objectives/1`)}
/>
```

### KeyResultTile

Displays a key result with progress indicator.

```tsx
import { KeyResultTile } from '@okrlinkhub/ui-kit';

<KeyResultTile
  keyResult={{
    id: "kr-1",
    indicatorDescription: "Monthly Revenue",
    indicatorSymbol: "€",
    weight: 40,
    currentValue: 75000,
    targetValue: 100000,
    progress: 75,
  }}
/>
```

### RiskBadge

Displays a risk with priority indicator. Supports badge and card variants.

```tsx
import { RiskBadge } from '@okrlinkhub/ui-kit';

// Badge variant (compact)
<RiskBadge
  risk={{
    id: "risk-1",
    description: "Market downturn risk",
    priority: "high",
    isRed: true,
  }}
  variant="badge"
/>

// Card variant (full)
<RiskBadge
  risk={{
    id: "risk-1",
    description: "Revenue target at risk due to market conditions",
    priority: "high",
    isKpiTriggered: true,
    indicatorDescription: "Monthly Revenue",
    triggerValue: 50000,
    lastIndicatorValue: 45000,
  }}
  variant="card"
/>
```

### InitiativeRow

Displays an initiative in a list format.

```tsx
import { InitiativeRow } from '@okrlinkhub/ui-kit';

<InitiativeRow
  initiative={{
    id: "init-1",
    description: "Launch new marketing campaign",
    assigneeId: "user-1",
    assigneeName: "John Doe",
    priority: "high",
    status: "ON_TIME",
    isNew: true,
  }}
/>
```

## Types

All types are exported for TypeScript users:

```tsx
import type {
  // Entity types
  Objective,
  KeyResult,
  Risk,
  Initiative,
  Indicator,
  Milestone,
  TeamMember,
  
  // Enums
  Priority,
  InitiativeStatus,
  MilestoneStatus,
  
  // Component props
  ObjectiveSectionProps,
  KeyResultSectionProps,
  RisksSectionProps,
  InitiativesSectionProps,
  EditObjectiveModalProps,
  EditKeyResultModalProps,
  EditRiskModalProps,
  EditInitiativeModalProps,
  
  // Pagination
  PaginationState,
} from '@okrlinkhub/ui-kit';
```

## Integration with okrhub-convex

This library is designed to work with `okrhub-convex`. Here's how to map the data:

```tsx
import { useQuery } from 'convex/react';
import { api } from '@convex/_generated/api';
import { ObjectiveSection, KeyResultSection } from '@okrlinkhub/ui-kit';

function MyApp() {
  const objectives = useQuery(api.okrhub.objectives.list, { teamExternalId: 'team-1' });
  const keyResults = useQuery(api.okrhub.keyResults.list, { teamExternalId: 'team-1' });
  
  // Map okrhub-convex data to ui-kit format
  const mappedObjectives = objectives?.map(obj => ({
    id: obj.externalId,
    title: obj.title,
    description: obj.description,
    teamId: obj.teamExternalId,
    keyResultsCount: obj.keyResultsCount,
  })) ?? [];
  
  return (
    <ObjectiveSection
      objectives={mappedObjectives}
      onObjectiveSelect={(id) => {/* ... */}}
    />
  );
}
```

## Styling

Components use inline styles for maximum portability. You can customize appearance via:

- `className` - Add custom CSS classes
- `style` - Override inline styles
- `actions` - Render custom action buttons/menus

## Publishing

This package is published to npm under the `@okrlinkhub` scope.

### For Maintainers

To publish a new version:

```bash
# Build the package
npm run build

# Run type checking
npm run typecheck

# Publish (automatically increments version and publishes)
npm run release
```

The `release` script will:
1. Run `prepublishOnly` hook (builds the package)
2. Increment the patch version
3. Publish to npm with public access
4. Push git tags

For manual publishing:

```bash
npm run build
npm publish --access public
```

### Version Management

- Use `npm version patch` for bug fixes
- Use `npm version minor` for new features
- Use `npm version major` for breaking changes

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

MIT

## Links

- [npm package](https://www.npmjs.com/package/@okrlinkhub/ui-kit)
- [GitHub repository](https://github.com/okrlinkhub/linkhub-ui-kit)
- [Issue tracker](https://github.com/okrlinkhub/linkhub-ui-kit/issues)