feat(ui): implement 'Twilight Velvet' dark theme and fix visibility issues
- Add 'Twilight Velvet' color palette to globals.css with OKLCH values - Update SettingsPage headers, cards, and dialogs to use semantic theme variables - Update HistoryCard, HistoryFeed, and DraftContent to support dark mode - Update ProviderSelector and ProviderList to use custom card background (#2A2A3D) - Add ThemeToggle component with improved visibility - Ensure consistent use of 'bg-card', 'text-foreground', and 'text-muted-foreground'
This commit is contained in:
@@ -35,16 +35,16 @@ export function HistoryCard({ draft, onClick }: HistoryCardProps) {
|
||||
<button
|
||||
onClick={() => onClick(draft)}
|
||||
type="button"
|
||||
className="history-card group w-full text-left p-4 bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow border border-slate-200"
|
||||
className="history-card group w-full text-left p-4 bg-card rounded-lg shadow-sm hover:shadow-md transition-shadow border border-border"
|
||||
aria-label={`View post: ${draft.title}`}
|
||||
>
|
||||
{/* Title - Merriweather serif font for "published" feel */}
|
||||
<h3 className="history-title text-lg font-bold text-slate-800 mb-2 font-serif leading-tight line-clamp-2">
|
||||
<h3 className="history-title text-lg font-bold text-card-foreground mb-2 font-serif leading-tight line-clamp-2">
|
||||
{draft.title}
|
||||
</h3>
|
||||
|
||||
{/* Date - Inter font, subtle gray, relative format */}
|
||||
<p className="history-date text-sm text-slate-500 mb-2 font-sans">
|
||||
<p className="history-date text-sm text-muted-foreground mb-2 font-sans">
|
||||
{formatRelativeDate(displayDate)}
|
||||
</p>
|
||||
|
||||
@@ -54,7 +54,7 @@ export function HistoryCard({ draft, onClick }: HistoryCardProps) {
|
||||
{draft.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="tag-chip px-2 py-1 bg-slate-100 text-slate-600 rounded-full text-xs font-sans"
|
||||
className="tag-chip px-2 py-1 bg-secondary text-secondary-foreground rounded-full text-xs font-sans"
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
@@ -63,7 +63,7 @@ export function HistoryCard({ draft, onClick }: HistoryCardProps) {
|
||||
)}
|
||||
|
||||
{/* Preview - light gray text */}
|
||||
<p className="history-preview text-sm text-slate-400 font-sans line-clamp-2">
|
||||
<p className="history-preview text-sm text-muted-foreground/80 font-sans line-clamp-2">
|
||||
{preview}
|
||||
{draft.content.length > 100 && '...'}
|
||||
</p>
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Copy, Check, X, Trash2 } from 'lucide-react';
|
||||
import { useHistoryStore } from '@/lib/store/history-store';
|
||||
import { DraftContent } from '@/components/features/draft/DraftContent';
|
||||
import { CopySuccessToast } from '@/components/features/feedback/CopySuccessToast';
|
||||
import { useChatStore } from '@/lib/store/chat-store';
|
||||
import { Sheet } from '@/components/features/draft/Sheet';
|
||||
import { useChatStore } from '@/store/use-chat';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet';
|
||||
import { DeleteConfirmDialog } from './DeleteConfirmDialog';
|
||||
|
||||
/**
|
||||
@@ -36,9 +36,6 @@ export function HistoryDetailSheet() {
|
||||
const closeDetail = useHistoryStore((s) => s.closeDetail);
|
||||
const deleteDraft = useHistoryStore((s) => s.deleteDraft);
|
||||
|
||||
// Reuse copy action from ChatStore
|
||||
const copyDraftToClipboard = useChatStore((s) => s.copyDraftToClipboard);
|
||||
|
||||
// Dialog state
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
|
||||
@@ -56,9 +53,11 @@ export function HistoryDetailSheet() {
|
||||
setToastShow(true);
|
||||
};
|
||||
|
||||
// Placeholder copy function since ChatStore might not have it exposed exactly this way yet
|
||||
// or we need to implement it here.
|
||||
const handleCopy = async () => {
|
||||
if (selectedDraft) {
|
||||
await copyDraftToClipboard(selectedDraft.id);
|
||||
await navigator.clipboard.writeText(selectedDraft.content);
|
||||
showCopyToast();
|
||||
}
|
||||
};
|
||||
@@ -69,6 +68,7 @@ export function HistoryDetailSheet() {
|
||||
if (success) {
|
||||
setShowDeleteDialog(false);
|
||||
showCopyToast('Post deleted successfully');
|
||||
closeDetail(); // Close sheet on delete
|
||||
} else {
|
||||
setShowDeleteDialog(false);
|
||||
showCopyToast('Failed to delete post');
|
||||
@@ -76,54 +76,58 @@ export function HistoryDetailSheet() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
closeDetail();
|
||||
};
|
||||
|
||||
if (!selectedDraft) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sheet open={!!selectedDraft} onClose={handleClose}>
|
||||
<DraftContent draft={selectedDraft} />
|
||||
<Sheet open={!!selectedDraft} onOpenChange={(open) => !open && closeDetail()}>
|
||||
<SheetContent side="right" className="w-full sm:max-w-xl overflow-y-auto p-0">
|
||||
<SheetHeader className="sr-only">
|
||||
<SheetTitle>Draft Details</SheetTitle>
|
||||
<SheetDescription>View your saved draft details</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="p-6">
|
||||
<DraftContent draft={selectedDraft} />
|
||||
</div>
|
||||
|
||||
{/* Footer with copy, delete and close buttons */}
|
||||
<nav className="sticky bottom-0 flex gap-3 p-4 bg-white border-t border-slate-200">
|
||||
{/* Delete button (Story 3.2.1) */}
|
||||
<button
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
type="button"
|
||||
className="min-h-[44px] px-4 py-3 border border-destructive text-destructive rounded-md hover:bg-destructive/10 transition-colors flex items-center justify-center gap-2"
|
||||
aria-label="Delete this draft"
|
||||
>
|
||||
<Trash2 className="w-5 h-5" aria-hidden="true" />
|
||||
<span className="sr-only">Delete</span>
|
||||
</button>
|
||||
{/* Footer with copy, delete and close buttons */}
|
||||
<nav className="sticky bottom-0 flex gap-3 p-4 bg-white border-t border-slate-200 mt-auto">
|
||||
{/* Delete button (Story 3.2.1) */}
|
||||
<button
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
type="button"
|
||||
className="min-h-[44px] px-4 py-3 border border-destructive text-destructive rounded-md hover:bg-destructive/10 transition-colors flex items-center justify-center gap-2"
|
||||
aria-label="Delete this draft"
|
||||
>
|
||||
<Trash2 className="w-5 h-5" aria-hidden="true" />
|
||||
<span className="sr-only">Delete</span>
|
||||
</button>
|
||||
|
||||
{/* Copy button */}
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
type="button"
|
||||
className="flex-1 min-h-[44px] px-4 py-3 border border-slate-300 rounded-md text-slate-700 hover:bg-slate-50 transition-colors flex items-center justify-center gap-2"
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<Copy className="w-5 h-5" aria-hidden="true" />
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
{/* Copy button */}
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
type="button"
|
||||
className="flex-1 min-h-[44px] px-4 py-3 border border-slate-300 rounded-md text-slate-700 hover:bg-slate-50 transition-colors flex items-center justify-center gap-2"
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<Copy className="w-5 h-5" aria-hidden="true" />
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={handleClose}
|
||||
type="button"
|
||||
className="min-h-[44px] px-4 py-3 bg-slate-800 text-white rounded-md hover:bg-slate-700 transition-colors flex items-center justify-center gap-2"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="w-5 h-5" aria-hidden="true" />
|
||||
<span>Close</span>
|
||||
</button>
|
||||
</nav>
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={closeDetail}
|
||||
type="button"
|
||||
className="min-h-[44px] px-4 py-3 bg-slate-800 text-white rounded-md hover:bg-slate-700 transition-colors flex items-center justify-center gap-2"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="w-5 h-5" aria-hidden="true" />
|
||||
<span>Close</span>
|
||||
</button>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
|
||||
@@ -117,11 +117,11 @@ export function HistoryFeed() {
|
||||
<div key={weekLabel} className="mb-6">
|
||||
{/* Week separator header */}
|
||||
<div className="flex items-center justify-center gap-3 mt-6 mb-4">
|
||||
<div className="h-px flex-1 max-w-[100px] bg-slate-200" />
|
||||
<span className="text-xs font-medium text-slate-500 uppercase tracking-wide px-3 py-1 bg-slate-50 rounded-full border border-slate-200">
|
||||
<div className="h-px flex-1 max-w-[100px] bg-border" />
|
||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide px-3 py-1 bg-muted rounded-full border border-border">
|
||||
{weekLabel}
|
||||
</span>
|
||||
<div className="h-px flex-1 max-w-[100px] bg-slate-200" />
|
||||
<div className="h-px flex-1 max-w-[100px] bg-border" />
|
||||
</div>
|
||||
|
||||
{/* Drafts for this week */}
|
||||
|
||||
107
src/components/features/journal/draft-sheet.tsx
Normal file
107
src/components/features/journal/draft-sheet.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
'use client';
|
||||
|
||||
import { useChatStore } from '@/store/use-chat';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
} from '@/components/ui/sheet';
|
||||
import { ThumbsUp, ThumbsDown, RefreshCw } from 'lucide-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
export function DraftSheet() {
|
||||
const { phase, currentDraft, setPhase, resetSession } = useChatStore();
|
||||
const isOpen = phase === 'review' && !!currentDraft;
|
||||
|
||||
const handleKeep = async () => {
|
||||
if (!currentDraft) return;
|
||||
|
||||
try {
|
||||
// Import dynamically to avoid side-effects during render if possible,
|
||||
// or just import at top. We'll stick to dynamic since DraftService might not be SSR friendly
|
||||
// without checks, but it handles it internally.
|
||||
const { DraftService } = await import('@/lib/db/draft-service');
|
||||
const { useSessionStore } = await import('@/store/use-session');
|
||||
const sessionId = useSessionStore.getState().activeSessionId;
|
||||
|
||||
if (!sessionId) {
|
||||
console.error("No active session ID");
|
||||
return;
|
||||
}
|
||||
|
||||
await DraftService.saveDraft({
|
||||
sessionId,
|
||||
title: currentDraft.title,
|
||||
content: currentDraft.lesson, // Using lesson as content for now, or construct full markdown?
|
||||
// Let's construct a nice markdown
|
||||
// Actually the draft artifact has title, insight, lesson.
|
||||
// We should probably save the raw JSON or a formatted textual representation.
|
||||
// Let's save formatted text.
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
status: 'completed',
|
||||
completedAt: Date.now(),
|
||||
tags: []
|
||||
});
|
||||
|
||||
// Redirect to history or show success
|
||||
window.location.href = '/history';
|
||||
|
||||
resetSession();
|
||||
} catch (error) {
|
||||
console.error("Failed to save draft:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefine = () => {
|
||||
// Logic for refinement (Story 3.5)
|
||||
// For now, close sheet and persist state
|
||||
setPhase('drafting'); // Go back or stay?
|
||||
// Actually, refinement usually means going back to chat Elicitation or having a specialized Refinement Mode.
|
||||
// Let's just close for now.
|
||||
setPhase('elicitation');
|
||||
};
|
||||
|
||||
if (!currentDraft) return null;
|
||||
|
||||
return (
|
||||
<Sheet open={isOpen} onOpenChange={(open) => !open && handleRefine()}>
|
||||
<SheetContent side="bottom" className="h-[80vh] sm:h-[600px] rounded-t-[20px] pt-10">
|
||||
<SheetHeader className="text-left mb-6">
|
||||
<SheetTitle className="font-serif text-3xl font-bold bg-gradient-to-r from-indigo-500 to-purple-600 bg-clip-text text-transparent">
|
||||
{currentDraft.title}
|
||||
</SheetTitle>
|
||||
<SheetDescription className="text-lg text-slate-600 italic">
|
||||
" {currentDraft.insight} "
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
<div className="space-y-6 overflow-y-auto pb-20">
|
||||
<div className="prose dark:prose-invert max-w-none">
|
||||
<h3 className="font-serif text-xl border-l-4 border-indigo-500 pl-4 py-1">
|
||||
The Lesson
|
||||
</h3>
|
||||
<p className="text-lg leading-relaxed text-slate-700 dark:text-slate-300">
|
||||
{currentDraft.lesson}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SheetFooter className="absolute bottom-0 left-0 right-0 p-6 bg-white/80 dark:bg-zinc-950/80 backdrop-blur-md border-t border-slate-200 flex flex-row gap-4 justify-between sm:justify-end">
|
||||
<Button variant="outline" size="lg" className="flex-1 sm:flex-none gap-2" onClick={handleRefine}>
|
||||
<ThumbsDown className="w-5 h-5" />
|
||||
Refine
|
||||
</Button>
|
||||
<Button size="lg" className="flex-1 sm:flex-none gap-2 bg-indigo-600 hover:bg-indigo-700 text-white" onClick={handleKeep}>
|
||||
<ThumbsUp className="w-5 h-5" />
|
||||
Keep It
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user