@ostronaut/components
v0.2.0-alpha.2
Published
Themable practice UI component library for Ostronaut products
Readme
@ostronaut/components
Themable practice UI component library for Ostronaut products.
All components use CSS custom properties (see docs/CSS_VARS.md) so you can
fully restyle them by overriding a small set of variables — no component source changes required.
Install
npm install @ostronaut/componentsReact 18+ and react-dom are peer dependencies.
Import
// 1. Import the CSS bundle (once, at your app root)
import '@ostronaut/components/dist/styles.css';
// 2. Import components
import { EvalPanel, CoachPanel, AIPanel } from '@ostronaut/components';
import type { EvaluationState } from '@ostronaut/components';Theme Switching
All tokens live on :root. Override any subset on a wrapper element to scope a custom theme
to a tenant, page section, or feature flag:
/* Minimal rebrand: change accent and surface colours */
[data-theme="acme"] {
--osk-color-accent: #0066cc;
--osk-color-accent-hover: #0077ee;
--osk-color-surface: #ffffff;
--osk-color-surface-elevated: #f5f5f7;
--osk-color-text-primary: #1d1d1f;
--osk-color-text-secondary: #6e6e73;
--osk-color-border: rgba(0, 0, 0, 0.1);
}<div data-theme="acme">
<EvalPanel evaluation={eval} />
</div>See docs/CSS_VARS.md for the full variable reference.
Component Reference
| Component | Props summary |
|-----------|---------------|
| PracticeShell | workspace: ReactNode — left workspace pane (~65%); aiPanel: ReactNode — right AI pane (~35%); header?: ReactNode — optional top bar; className?: string |
| AIPanel | coachMessage: string\|null; coachTone: TutorResponse["tone"]\|null; coachConceptTag?: string\|null; coachSuggestedAction?: string\|null; isCoachLoading?: boolean; evaluation: EvaluationState\|null |
| CoachPanel | message: string\|null; tone: TutorResponse["tone"]\|null; conceptTag?: string\|null; suggestedAction?: string\|null; isLoading?: boolean; turnEvaluations?: TurnEvaluation[] |
| EvalPanel | evaluation: EvaluationState\|null — shows live criterion scores with animated progress bars |
| SessionScoreBar | evaluation: EvaluationState\|null — sticky bar above the input showing per-criterion mini bars |
| InlineEvalAnnotation | evaluation: InlineCriterionScore[]; coaching: InlineCoaching\|null — score pills rendered below a user message |
| PracticeShell | workspace; aiPanel; header?; className? — cursor-split fixed layout |
| PracticeTimelineView | mode: PracticeSessionMode; nodes: PracticeDecisionNode[]; currentNodeId: string\|null; currentBranch: string; analysis?: PracticeReplayAnalysis\|null; isRewinding?: boolean; onClose?: () => void; onRewind: (nodeId: string) => void |
| PracticeReplayLoadingScreen | transcript: string; stage: ReplayWorkspaceStage; isLoading: boolean; error?: string\|null; preflight?: PracticeReplayPreflight\|null; learnerName?: string\|null; draftHints?: PracticeReplayHints; showContextField?: boolean; onDraftHintsChange?; onConfirmIdentity?; onBack; onRetry |
| PracticeSummaryScreen | See component file — authored separately |
| DiscoveryPanel | scanningState: ScanningState; selectedMomentId: string\|null; onMomentSelect: (id: string) => void; onPracticeFromMoment: (dp: PracticeReplayDecisionPoint) => void |
| ScanningTranscript | transcript: string; scanningState: ScanningState; selectedMomentId: string\|null; onMomentSelect: (id: string) => void; scrollToMomentRef?: MutableRefObject<((id: string) => void) \| null> |
| TranscriptArticle | transcript: string; analysis: PracticeReplayAnalysis; practiceNodes: PracticeDecisionNode[]; currentBranch: string\|null; selectedMomentId: string\|null; onMomentSelect: (id: string) => void; transcriptContainerRef?: RefObject<HTMLDivElement\|null> |
| TranscriptSelectionToolbar | containerRef: RefObject<HTMLElement\|null>; onAnalyze: (text: string, type: SelectionAnalysisType) => void; disabled?: boolean |
| MomentReviewPanel | decisionPoint: PracticeReplayDecisionPoint\|null; momentNumber: number; turn: PracticeReplayAnalysisTurn\|null; threadCount: number; branches: Array<{branch, label}>; isPracticing: boolean; scoreComparison?: CriterionComparison[]; onPracticeFromHere; onOpenBranch |
| SelectionAnalysisPanel | result: SelectionAnalysisResult; onDismiss: () => void |
| IdentityHintsPopover | learnerName: string; learnerRole: string; counterpartName: string; counterpartRole: string; isRefreshing: boolean; onRefresh: (hints: PracticeReplayHints) => Promise<void> |
| PostSessionRecommendations | latestScores: Record<string, number>; sessionId?: string; className?: string; onFetchRecommendations?: (scores, sessionId?) => Promise<CourseRecommendation[]>; renderCard?: (rec: CourseRecommendation) => ReactNode |
CSS Variables
Full variable reference: docs/CSS_VARS.md
Quick summary of major categories:
- Surfaces —
--osk-color-surface*(6 vars) - Text —
--osk-color-text-*(5 vars) - Accent —
--osk-color-accent*(3 vars) - Semantic — success / warning / error / info (5 vars each)
- Brand palette — indigo / emerald / rose / amber (3 vars each)
- Borders —
--osk-color-border*(3 vars) - Score fill —
--osk-color-score-high/mid/low - Coach tone —
--osk-color-coach-*(9 vars) - Score bar —
--osk-score-bar-*(3 vars) - Spacing —
--osk-space-*(18 steps) - Radius —
--osk-radius-*(7 steps) - Typography — font families, sizes (10), weights (4), line-heights (5), letter-spacing (6)
- Shadows —
--osk-shadow-*(7 vars) - Transitions —
--osk-transition-*(4 vars) - Layout — panel widths, transcript max-width (6 vars)
- Z-index —
--osk-z-*(4 levels)
