27## Technology Stack
28
29- **Frontend**: HTML, CSS, TypeScript with React
30- **Styling**: TailwindCSS
31- **Backend**: Hono framework for API routing
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { useRef, useState } from "https://esm.sh/react@18.2.0";
3import { defineAction, defineLoader, render } from "./render.ts";
4
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { useState } from "https://esm.sh/react@18.2.0";
3import { defineAction, defineLoader, Form, render, useActionData, useLoaderData } from "./render.ts";
4
9 <script src="https://cdn.tailwindcss.com"></script>
10
11 <!-- Load React, ReactDOM, and Babel -->
12 <script
13 crossorigin
14 src="https://unpkg.com/react@18/umd/react.production.min.js"
15 ></script>
16 <script
17 crossorigin
18 src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
19 ></script>
20 <script
45 // Weight Tracker Component
46 const WeightTracker = () => {
47 const [weightData, setWeightData] = React.useState([]);
48 const [stats, setStats] = React.useState(null);
49 const [isLoading, setIsLoading] = React.useState(false);
50 const [error, setError] = React.useState(null);
51 const [fileName, setFileName] = React.useState('');
52
53 const handleFileUpload = (event) => {
492
493 // Render the App
494 const root = ReactDOM.createRoot(document.getElementById('root'));
495 root.render(<WeightTracker />);
496 </script>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>React Hono Val Town Starter</title>
7 <script src="https://cdn.tailwindcss.com"></script>
8 <link rel="icon" href="/public/favicon.svg" sizes="any" type="image/svg+xml">
1// components/FileUpload.tsx - Advanced file upload with drag-and-drop and progress
2import React, { useCallback, useRef, useState } from "react";
3import { useFileProcessor } from "../hooks/useFileProcessor";
4import { ProcessedDocument } from "../lib/types";
14}
15
16export const FileUpload: React.FC<FileUploadProps> = ({
17 onFileProcessed,
18 onTextProcessed,
72
73 // Drag and drop handlers
74 const handleDragEnter = useCallback((e: React.DragEvent) => {
75 e.preventDefault();
76 e.stopPropagation();
78 }, []);
79
80 const handleDragLeave = useCallback((e: React.DragEvent) => {
81 e.preventDefault();
82 e.stopPropagation();
84 }, []);
85
86 const handleDragOver = useCallback((e: React.DragEvent) => {
87 e.preventDefault();
88 e.stopPropagation();
89 }, []);
90
91 const handleDrop = useCallback((e: React.DragEvent) => {
92 e.preventDefault();
93 e.stopPropagation();
119 }, [maxFiles, validateFile, handleFileProcessing]);
120
121 const handleFileInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
122 const files = e.target.files ? Array.from(e.target.files) : [];
123 handleFileSelection(files);
125
126 // Paste handling for text area
127 const handleTextPaste = useCallback((e: React.ClipboardEvent) => {
128 const pastedText = e.clipboardData.getData("text");
129 if (pastedText && !textInput) {
1// components/ui/ProgressBar.tsx - Interactive progress bar with seeking capability
2import React, { useRef, useState } from "react";
3
4interface ProgressBarProps {
10 variant?: "default" | "audio" | "processing";
11 className?: string;
12 children?: React.ReactNode;
13}
14
15export const ProgressBar: React.FC<ProgressBarProps> = ({
16 progress,
17 onSeek,
41 const clampedProgress = Math.max(0, Math.min(1, progress));
42
43 const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
44 if (disabled || !onSeek || !progressRef.current) return;
45
50 };
51
52 const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
53 if (disabled || !onSeek) return;
54
75 };
76
77 const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
78 if (!showTooltip || !progressRef.current) return;
79
137}
138
139export const AudioProgressBar: React.FC<AudioProgressBarProps> = ({
140 currentTime,
141 duration,
175}
176
177export const MultiProgressBar: React.FC<MultiProgressBarProps> = ({
178 tracks,
179 height = "md",
222}
223
224export const CircularProgress: React.FC<CircularProgressProps> = ({
225 progress,
226 size = 100,
1// components/ui/Modal.tsx - Reusable modal component with animations
2import React, { useEffect, useRef } from "react";
3
4interface ModalProps {
7 title?: string;
8 size?: "sm" | "md" | "lg" | "xl" | "full";
9 children: React.ReactNode;
10 closeOnOverlayClick?: boolean;
11 showCloseButton?: boolean;
13}
14
15export const Modal: React.FC<ModalProps> = ({
16 isOpen = true,
17 onClose,
66 };
67
68 const handleOverlayClick = (event: React.MouseEvent) => {
69 if (closeOnOverlayClick && event.target === overlayRef.current) {
70 onClose();
143}
144
145export const ConfirmModal: React.FC<ConfirmModalProps> = ({
146 isOpen,
147 onClose,
187// Modal Hook
188export function useModal() {
189 const [isOpen, setIsOpen] = React.useState(false);
190
191 const openModal = () => setIsOpen(true);
1// components/ui/Toast.tsx - Toast notification system
2import React, { useEffect, useState } from "react";
3import { ToastMessage } from "../../lib/types";
4
8}
9
10export const Toast: React.FC<ToastProps> = ({
11 type,
12 message,
86}
87
88export const ToastManager: React.FC<ToastManagerProps> = ({
89 toasts,
90 onRemoveToast,
1// components/ui/Button.tsx - Reusable button component with variants
2import React from "react";
3
4interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
5 variant?: "primary" | "secondary" | "success" | "warning" | "danger" | "ghost";
6 size?: "sm" | "md" | "lg";
7 loading?: boolean;
8 icon?: React.ReactNode;
9 children: React.ReactNode;
10}
11
12export const Button: React.FC<ButtonProps> = ({
13 variant = "primary",
14 size = "md",