@okrlinkhub/ui-kit
v0.1.1
Published
Dumb React components for displaying OKR data. No Convex queries - all data passed via props.
Maintainers
Readme
@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 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
npm install @okrlinkhub/ui-kitor with yarn:
yarn add @okrlinkhub/ui-kitor with pnpm:
pnpm add @okrlinkhub/ui-kitQuick Start - One-Page OKR Dashboard
Create a complete OKR dashboard with cascade selection in minutes:
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:
npm run dev:showcaseQuesto comando:
- Entra nella cartella
dev - Installa le dipendenze (se necessario)
- Avvia il server di sviluppo Vite
Avvii successivi:
cd dev
npm run devL'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.tsxper 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:
npm run dev # Watch mode per la compilazione
npm run build # Build una tantumSection 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.
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 objectsselectedObjectiveId- Currently selected objective IDonObjectiveSelect- Callback when an objective is selectedonEdit- Callback when edit button is clickedonCreate- Callback when create button is clickedisLoading- Show loading skeleton
KeyResultSection
Displays key results filtered by objective with progress indicators.
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.
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.
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
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
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
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
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
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
import { PriorityBadge } from '@okrlinkhub/ui-kit';
<PriorityBadge priority="high" showLabel size="sm" />
<PriorityBadge priority="medium" onClick={() => cyclePriority()} />EmptyState
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.
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.
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.
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.
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:
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:
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 classesstyle- Override inline stylesactions- Render custom action buttons/menus
Publishing
This package is published to npm under the @okrlinkhub scope.
For Maintainers
To publish a new version:
# Build the package
npm run build
# Run type checking
npm run typecheck
# Publish (automatically increments version and publishes)
npm run releaseThe release script will:
- Run
prepublishOnlyhook (builds the package) - Increment the patch version
- Publish to npm with public access
- Push git tags
For manual publishing:
npm run build
npm publish --access publicVersion Management
- Use
npm version patchfor bug fixes - Use
npm version minorfor new features - Use
npm version majorfor breaking changes
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT
