juq-llm-kit
Version:
Customizable UI components for React Native (Expo) chat applications
337 lines (329 loc) • 12.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(require("react"));
const react_native_1 = require("react-native");
const lucide_react_1 = require("lucide-react");
const vector_icons_1 = require("@expo/vector-icons");
// Default projects
const defaultProjects = [
{ name: "Frontend Development" },
{ name: "Backend Architecture" }
];
// Default history items
const defaultHistoryItems = [
{ name: "Task creation and processing", date: new Date(2024, 2, 15) },
{ name: "API Integration", date: new Date(2024, 2, 14) },
{ name: "Database Schema Design", date: new Date(2024, 2, 14) },
{ name: "User Authentication Flow", date: new Date(2024, 2, 13) },
{ name: "Performance Optimization", date: new Date(2024, 2, 13) }
];
// Custom hook for web-specific styles
const useWebScrollbarStyle = (theme) => {
(0, react_1.useEffect)(() => {
if (react_native_1.Platform.OS === 'web' && typeof window !== 'undefined') {
const thumbColor = theme === 'light' ? '#6b7280' : '#FFFFFF';
const style = document.createElement('style');
style.textContent = `
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
background-color: transparent;
}
.custom-scrollbar::-webkit-scrollbar-track {
background-color: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: ${thumbColor};
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: ${thumbColor};
}
/* For Firefox */
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: ${thumbColor} transparent;
}
`;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}
}, [theme]);
};
// Add fixed positioning CSS for web platform
const useFixedPositioningForCollapsed = (isCollapsed) => {
(0, react_1.useEffect)(() => {
if (react_native_1.Platform.OS === 'web' && typeof window !== 'undefined') {
const style = document.createElement('style');
if (isCollapsed) {
style.textContent = `
.collapsed-sidebar {
position: fixed !important;
top: 0 !important;
left: 0 !important;
z-index: 9999 !important;
margin: 0 !important;
padding: 0 !important;
}
`;
document.head.appendChild(style);
}
return () => {
if (document.head.contains(style)) {
document.head.removeChild(style);
}
};
}
}, [isCollapsed]);
};
const Sidebar = ({ onCollapsedChange, initialCollapsed = false, projects = defaultProjects, historyItems = defaultHistoryItems, subscriptionTitle = "View plans", subscriptionText = "Unlimited access, team features,...", containerStyle, theme = 'dark' }) => {
const [isCollapsed, setIsCollapsed] = (0, react_1.useState)(initialCollapsed);
const animatedWidth = new react_native_1.Animated.Value(initialCollapsed ? 40 : 256);
const colors = getThemeColors(theme);
useWebScrollbarStyle(theme);
useFixedPositioningForCollapsed(isCollapsed);
const toggleCollapse = () => {
const newCollapsedState = !isCollapsed;
setIsCollapsed(newCollapsedState);
if (onCollapsedChange) {
onCollapsedChange(newCollapsedState);
}
react_native_1.Animated.timing(animatedWidth, {
toValue: newCollapsedState ? 40 : 256,
duration: 300,
useNativeDriver: false,
}).start();
};
return (<react_native_1.Animated.View style={[
styles.container,
{
width: animatedWidth,
backgroundColor: colors.containerBg,
},
isCollapsed && styles.collapsedContainer,
containerStyle
]}>
{/* Top icons */}
<react_native_1.View style={[
styles.topBar,
{ borderBottomColor: colors.borderColor },
isCollapsed && [styles.collapsedTopBar, { backgroundColor: colors.containerBg }]
]}>
<react_native_1.Pressable style={styles.iconButton} onPress={toggleCollapse}>
<react_native_1.View style={[styles.checkboxIcon, { borderColor: colors.iconColor }]}></react_native_1.View>
</react_native_1.Pressable>
{!isCollapsed ? (<react_native_1.View style={styles.iconGroup}>
<react_native_1.Pressable style={styles.iconButton}>
<lucide_react_1.Search size={20} color={colors.iconColor}/>
</react_native_1.Pressable>
<react_native_1.Pressable style={styles.iconButton}>
<lucide_react_1.Plus size={20} color={colors.iconColor}/>
</react_native_1.Pressable>
</react_native_1.View>) : (<react_native_1.Pressable style={[styles.iconButton, styles.collapsedPlusButton]}>
<lucide_react_1.Plus size={20} color={colors.iconColor}/>
</react_native_1.Pressable>)}
</react_native_1.View>
{/* Main content - only show when not collapsed */}
{!isCollapsed && (<>
<react_native_1.ScrollView style={styles.scrollContainer} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false}>
{/* Projects section */}
<react_native_1.View style={styles.section}>
<react_native_1.Text style={[styles.sectionTitle, { color: colors.secondaryText }]}>Projects</react_native_1.Text>
{projects.map((project, index) => (<ProjectItem key={`project-${index}`} name={project.name} onSelect={project.onSelect} colors={colors}/>))}
</react_native_1.View>
{/* History section */}
<react_native_1.View style={styles.sectionWithMargin}>
<react_native_1.Text style={[styles.sectionTitle, { color: colors.secondaryText }]}>History</react_native_1.Text>
{historyItems.map((item, index) => (<HistoryItem key={`history-${index}`} name={item.name} date={item.date} onSelect={item.onSelect} colors={colors}/>))}
</react_native_1.View>
</react_native_1.ScrollView>
{/* Bottom subscription link */}
<react_native_1.View style={[styles.bottomBar, { borderTopColor: colors.borderColor }]}>
<react_native_1.View style={[styles.subscriptionCard, { backgroundColor: colors.cardBg }]}>
<react_native_1.View style={styles.subscriptionContent}>
<react_native_1.View>
<react_native_1.Text style={[styles.subscriptionTitle, { color: colors.textColor }]}>{subscriptionTitle}</react_native_1.Text>
<react_native_1.Text style={[styles.subscriptionText, { color: colors.secondaryText }]}>{subscriptionText}</react_native_1.Text>
</react_native_1.View>
<lucide_react_1.Settings size={18} color={colors.secondaryText}/>
</react_native_1.View>
</react_native_1.View>
</react_native_1.View>
</>)}
</react_native_1.Animated.View>);
};
// Project item component with folder icon
const ProjectItem = ({ name, onSelect, colors }) => {
return (<react_native_1.Pressable style={styles.listItem} onPress={onSelect}>
<vector_icons_1.Ionicons name="folder-outline" size={20} color={colors.folderIconColor}/>
<react_native_1.Text style={[styles.listItemText, { color: colors.textColor }]}>{name}</react_native_1.Text>
</react_native_1.Pressable>);
};
// History item component with date
const HistoryItem = ({ name, date, onSelect, colors }) => {
const formattedDate = date.toLocaleDateString('en-US', {
month: 'numeric',
day: 'numeric',
year: '2-digit'
});
return (<react_native_1.Pressable style={styles.listItem} onPress={onSelect}>
<react_native_1.Text style={[styles.dateText, { color: colors.secondaryText }]}>{formattedDate}</react_native_1.Text>
<react_native_1.Text style={[styles.listItemText, { color: colors.textColor }]}>{name}</react_native_1.Text>
</react_native_1.Pressable>);
};
// Theme helper function
function getThemeColors(theme) {
if (theme === 'light') {
return {
containerBg: '#f9fafb',
cardBg: '#e5e7eb',
textColor: '#111827',
secondaryText: '#6b7280',
borderColor: '#e5e7eb',
iconColor: '#6b7280',
folderIconColor: '#6b7280'
};
}
return {
containerBg: '#111827',
cardBg: '#1f2937',
textColor: '#f9fafb',
secondaryText: '#9ca3af',
borderColor: '#1f2937',
iconColor: '#d1d5db',
folderIconColor: '#4b5563'
};
}
const styles = react_native_1.StyleSheet.create({
container: {
height: '100%',
flexDirection: 'column',
},
collapsedContainer: Object.assign({ position: 'absolute', top: 0, left: 0, height: 'auto', zIndex: 10000, backgroundColor: 'transparent', padding: 0, margin: 0 }, (react_native_1.Platform.OS === 'web' ? {
position: 'fixed',
} : {})),
topBar: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 12,
borderBottomWidth: 1,
},
collapsedTopBar: {
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-start',
borderBottomWidth: 0,
borderRadius: 4,
padding: 0,
margin: 0,
},
iconButton: {
width: 32,
height: 32,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 4,
},
collapsedPlusButton: {
marginTop: 8,
},
checkboxIcon: {
width: 20,
height: 20,
borderWidth: 2,
borderRadius: 4,
},
iconGroup: {
flexDirection: 'row',
gap: 12,
},
scrollContainer: {
flex: 1,
},
section: {
paddingVertical: 8,
},
sectionWithMargin: {
marginTop: 16,
},
sectionTitle: {
paddingHorizontal: 12,
paddingVertical: 8,
fontSize: 12,
fontWeight: '500',
},
dateText: {
fontSize: 11,
width: 45,
marginRight: 8,
},
listItem: {
paddingHorizontal: 12,
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
},
listItemText: {
marginLeft: 12,
fontSize: 14,
},
bottomBar: {
padding: 12,
borderTopWidth: 1,
},
subscriptionCard: {
borderRadius: 8,
padding: 12,
},
subscriptionContent: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
subscriptionTitle: {
fontSize: 14,
fontWeight: '500',
},
subscriptionText: {
fontSize: 12,
},
collapsedIconGroup: {
gap: 0,
},
});
exports.default = Sidebar;