UNPKG

@handit.ai/onboarding

Version:

Interactive onboarding components and service for AI agents

277 lines (262 loc) 8.67 kB
import React, { useState } from 'react'; import { Box, Card, IconButton, Typography, Button, Stack, Fade, TextField, InputAdornment, } from '@mui/material'; import { ChatCircle, PaperPlaneTilt, } from '@phosphor-icons/react'; const OnboardingAssistant = ({ visible = true, currentStep = 0, totalSteps = 5, onNext, onFinish, stepTitle = "Install HandIt", position = "bottom-right", chatPosition = "bottom-right" // Position for the floating chat when opened }) => { const [isMinimized, setIsMinimized] = useState(false); const [chatInput, setChatInput] = useState(''); // Handle chat message submission const handleChatSubmit = (e) => { e.preventDefault(); if (!chatInput.trim()) return; // Open the floating chat with the message window.dispatchEvent(new CustomEvent('openOnboardingChat', { detail: { mode: 'assistant', message: chatInput.trim(), position: chatPosition } })); // Clear the input setChatInput(''); }; if (!visible) return null; const stepsLeft = totalSteps - currentStep - 1; const isLastStep = currentStep === totalSteps - 1; // Calculate position styles based on position prop const getPositionStyles = () => { switch (position) { case 'bottom-center': return { bottom: 20, left: '50%', transform: 'translateX(-50%)', }; case 'bottom-left': return { bottom: 20, left: 20, }; case 'bottom-right': default: return { bottom: 20, right: 20, }; } }; return ( <Fade in={visible}> <Box sx={{ position: 'fixed', zIndex: 9998, ...getPositionStyles(), }} > {!isMinimized ? ( <Card sx={{ bgcolor: '#333333', color: 'white', borderRadius: '5px', overflow: 'hidden', minWidth: 500, maxWidth: 500, boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)', }} > {/* Content */} <Box sx={{ p: 2 }}> {/* Title */} <Typography variant="h6" sx={{ color: 'white', fontSize: '1rem', fontWeight: 500, mb: 1.5, textAlign: 'center' }} > {stepTitle} </Typography> {/* Progress Dots */} <Stack direction="row" spacing={1} sx={{ alignItems: 'center', justifyContent: 'center', mb: 2 }} > {Array.from({ length: totalSteps }).map((_, index) => ( <Box key={index} sx={{ width: 8, height: 8, borderRadius: '50%', bgcolor: index <= currentStep ? '#4A90E2' : '#666666', transition: 'background-color 0.3s ease', }} /> ))} <Typography variant="caption" sx={{ color: '#999', ml: 2, fontSize: '0.75rem' }} > {stepsLeft} steps left </Typography> </Stack> {/* Buttons and Chat Input on Same Line */} <Stack direction="row" spacing={2} alignItems="center" sx={{ borderTop: '1px solid rgba(255, 255, 255, 0.1)', pt: 2 }}> {/* Buttons */} {/* Chat Input */} <Box sx={{ flex: 1 }}> <form onSubmit={handleChatSubmit}> <TextField fullWidth value={chatInput} onChange={(e) => setChatInput(e.target.value)} placeholder="Ask me anything..." variant="outlined" size="small" sx={{ '& .MuiOutlinedInput-root': { bgcolor: 'rgba(255, 255, 255, 0.05)', borderRadius: '5px', '& fieldset': { borderColor: 'rgba(255, 255, 255, 0.2)' }, '&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.3)' }, '&.Mui-focused fieldset': { borderColor: '#4A90E2' }, }, '& .MuiOutlinedInput-input': { color: 'white', fontSize: '0.875rem', py: 0.5, '&::placeholder': { color: 'rgba(255, 255, 255, 0.5)', opacity: 1 } } }} InputProps={{ endAdornment: ( <InputAdornment position="end"> <IconButton type="submit" size="small" disabled={!chatInput.trim()} sx={{ color: chatInput.trim() ? '#4A90E2' : 'rgba(255, 255, 255, 0.3)', '&:hover': { bgcolor: 'rgba(74, 144, 226, 0.1)' }, '&.Mui-disabled': { color: 'rgba(255, 255, 255, 0.3)' } }} > <PaperPlaneTilt size={16} /> </IconButton> </InputAdornment> ) }} /> </form> </Box> <Stack direction="row" spacing={1.5}> <Button variant="text" onClick={isLastStep ? onFinish : onNext} sx={{ color: 'white', textTransform: 'none', fontSize: '0.8rem', bgcolor: 'rgba(255, 255, 255, 0.1)', px: 2.5, py: 0.4, minHeight: 'unset', '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.2)' } }} > Next </Button> <Button variant="text" onClick={onFinish} sx={{ color: 'white', textTransform: 'none', fontSize: '0.8rem', bgcolor: 'rgba(255, 255, 255, 0.1)', px: 2.5, py: 0.4, minHeight: 'unset', '&:hover': { bgcolor: 'rgba(255, 255, 255, 0.2)' } }} > Finish </Button> </Stack> </Stack> </Box> </Card> ) : ( // Minimized state <Card sx={{ bgcolor: 'primary.main', color: 'primary.contrastText', borderRadius: '50%', width: 56, height: 56, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', boxShadow: (theme) => theme.shadows[4], border: '2px solid', borderColor: 'background.paper', '&:hover': { boxShadow: (theme) => theme.shadows[8], transform: 'scale(1.05)', }, transition: 'all 0.2s ease' }} onClick={() => setIsMinimized(false)} > <ChatCircle size={24} weight="fill" /> </Card> )} </Box> </Fade> ); }; export default OnboardingAssistant;