PluginsView.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import React from 'react';
  2. import { useLanguage } from '../../contexts/LanguageContext';
  3. import { Search, Plus, MoreHorizontal, Puzzle } from 'lucide-react';
  4. import { motion, AnimatePresence } from 'framer-motion';
  5. // Mock data for plugins
  6. interface PluginMock {
  7. id: string;
  8. name: string;
  9. description: string;
  10. status: 'installed' | 'not-installed' | 'update-available';
  11. developer: string;
  12. iconEmoji: string;
  13. iconBgClass: string;
  14. }
  15. const mockPlugins: PluginMock[] = [
  16. {
  17. id: '1',
  18. name: 'plugin1Name',
  19. description: 'plugin1Desc',
  20. status: 'installed',
  21. developer: 'Official',
  22. iconEmoji: '🛠️',
  23. iconBgClass: 'bg-blue-50'
  24. },
  25. {
  26. id: '2',
  27. name: 'plugin2Name',
  28. description: 'plugin2Desc',
  29. status: 'update-available',
  30. developer: 'Official',
  31. iconEmoji: '📄',
  32. iconBgClass: 'bg-indigo-50'
  33. },
  34. {
  35. id: '3',
  36. name: 'plugin3Name',
  37. description: 'plugin3Desc',
  38. status: 'installed',
  39. developer: 'Community',
  40. iconEmoji: '🦊',
  41. iconBgClass: 'bg-orange-50'
  42. },
  43. {
  44. id: '4',
  45. name: 'plugin4Name',
  46. description: 'plugin4Desc',
  47. status: 'not-installed',
  48. developer: 'Official',
  49. iconEmoji: '🌐',
  50. iconBgClass: 'bg-emerald-50'
  51. },
  52. {
  53. id: '5',
  54. name: 'plugin5Name',
  55. description: 'plugin5Desc',
  56. status: 'not-installed',
  57. developer: 'Community',
  58. iconEmoji: '🗄️',
  59. iconBgClass: 'bg-slate-100'
  60. },
  61. {
  62. id: '6',
  63. name: 'plugin6Name',
  64. description: 'plugin6Desc',
  65. status: 'installed',
  66. developer: 'Official',
  67. iconEmoji: '💬',
  68. iconBgClass: 'bg-sky-50'
  69. }
  70. ];
  71. export const PluginsView: React.FC = () => {
  72. const { t } = useLanguage();
  73. return (
  74. <div className="flex flex-col h-full bg-[#f4f7fb] overflow-hidden">
  75. {/* Header Area */}
  76. <div className="px-8 pt-8 pb-6 flex items-start justify-between shrink-0">
  77. <div>
  78. <h1 className="text-[22px] font-bold text-slate-900 leading-tight flex items-center gap-2">
  79. <Puzzle className="text-blue-600" size={24} />
  80. {t('pluginTitle')}
  81. </h1>
  82. <p className="text-[14px] text-slate-500 mt-1">{t('pluginDesc')}</p>
  83. </div>
  84. <div className="flex items-center gap-4">
  85. <div className="relative w-64">
  86. <Search className="absolute text-slate-400 left-3 top-1/2 -translate-y-1/2" size={16} />
  87. <input
  88. type="text"
  89. placeholder={t('searchPlugin')}
  90. className="w-full h-10 pl-10 pr-4 bg-white border border-slate-200 rounded-lg focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all text-sm font-medium"
  91. />
  92. </div>
  93. </div>
  94. </div>
  95. {/* Content Area */}
  96. <div className="px-8 pb-8 flex-1 overflow-y-auto">
  97. <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 max-w-[1600px] mx-auto">
  98. <AnimatePresence>
  99. {mockPlugins.map((plugin) => (
  100. <motion.div
  101. key={plugin.id}
  102. layout
  103. initial={{ opacity: 0, y: 10 }}
  104. animate={{ opacity: 1, y: 0 }}
  105. className="bg-white rounded-2xl p-6 shadow-sm border border-slate-100 hover:shadow-md transition-all group flex flex-col h-[220px]"
  106. >
  107. {/* Top layer */}
  108. <div className="flex items-center justify-between mb-4">
  109. <div className={`w-12 h-12 flex items-center justify-center rounded-xl ${plugin.iconBgClass} text-2xl shadow-sm border border-black/5`}>
  110. {plugin.iconEmoji}
  111. </div>
  112. <div className="flex items-center gap-3">
  113. {/* Status Badge */}
  114. {plugin.status === 'installed' && (
  115. <div className="px-2.5 py-1 text-[12px] font-semibold text-emerald-600 bg-emerald-50 rounded-full border border-emerald-100 flex flex-row items-center justify-center">
  116. {t('installedPlugin')}
  117. </div>
  118. )}
  119. {plugin.status === 'update-available' && (
  120. <div className="px-2.5 py-1 text-[12px] font-semibold text-orange-600 bg-orange-50 rounded-full border border-orange-100 flex flex-row items-center justify-center">
  121. {t('updatePlugin')}
  122. </div>
  123. )}
  124. {/* Options button */}
  125. <button className="text-slate-400 hover:text-slate-600 transition-colors">
  126. <MoreHorizontal size={20} />
  127. </button>
  128. </div>
  129. </div>
  130. {/* Middle layer */}
  131. <div className="flex-1">
  132. <h3 className="font-bold text-slate-800 text-[17px] mb-2 leading-tight flex items-center gap-2">
  133. {t(plugin.name as any)}
  134. {plugin.developer === 'Official' && (
  135. <span className="bg-blue-100 text-blue-700 text-[10px] uppercase font-bold px-1.5 py-0.5 rounded-sm">{t('pluginOfficial')}</span>
  136. )}
  137. </h3>
  138. <p className="text-[13px] text-slate-500 leading-relaxed line-clamp-2">
  139. {t(plugin.description as any)}
  140. </p>
  141. </div>
  142. {/* Bottom layer */}
  143. <div className="mt-4 pt-4 border-t border-slate-50 flex items-center justify-between">
  144. <span className="text-[12px] font-medium text-slate-400">
  145. {t('pluginBy')}{plugin.developer === 'Official' ? t('pluginOfficial') : t('pluginCommunity')}
  146. </span>
  147. {plugin.status === 'not-installed' ? (
  148. <button className="flex items-center justify-center gap-1.5 px-4 py-1.5 text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors shadow-sm">
  149. <Plus size={14} className="text-white" />
  150. <span className="text-[13px] font-bold">{t('installPlugin')}</span>
  151. </button>
  152. ) : plugin.status === 'update-available' ? (
  153. <button className="flex items-center justify-center gap-1.5 px-3 py-1.5 text-orange-600 bg-orange-50 hover:bg-orange-100 rounded-lg transition-colors border border-orange-200">
  154. <span className="text-[13px] font-bold">{t('updatePlugin')}</span>
  155. </button>
  156. ) : (
  157. <button className="flex items-center justify-center gap-1.5 px-3 py-1.5 text-slate-600 bg-slate-50 hover:bg-slate-100 rounded-lg transition-colors border border-slate-200">
  158. <span className="text-[13px] font-bold">{t('pluginConfig')}</span>
  159. </button>
  160. )}
  161. </div>
  162. </motion.div>
  163. ))}
  164. </AnimatePresence>
  165. </div>
  166. </div>
  167. </div>
  168. );
  169. };