PricingVendorIntroSkeleton.jsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { memo } from 'react';
  16. import { Card, Skeleton } from '@douyinfe/semi-ui';
  17. const THEME_COLORS = {
  18. allVendors: {
  19. primary: '37 99 235',
  20. background: 'rgba(59, 130, 246, 0.1)',
  21. border: 'rgba(59, 130, 246, 0.2)'
  22. },
  23. specific: {
  24. primary: '16 185 129',
  25. background: 'rgba(16, 185, 129, 0.1)',
  26. border: 'rgba(16, 185, 129, 0.2)'
  27. },
  28. neutral: {
  29. background: 'rgba(156, 163, 175, 0.1)',
  30. border: 'rgba(156, 163, 175, 0.2)'
  31. }
  32. };
  33. const SIZES = {
  34. title: { width: { all: 120, specific: 100 }, height: 24 },
  35. tag: { width: 80, height: 20 },
  36. description: { height: 14 },
  37. avatar: { width: 40, height: 40 },
  38. searchInput: { height: 32 },
  39. button: { width: 80, height: 32 }
  40. };
  41. const SKELETON_STYLES = {
  42. cover: (primaryColor) => ({
  43. '--palette-primary-darkerChannel': primaryColor,
  44. backgroundImage: `linear-gradient(0deg, rgba(var(--palette-primary-darkerChannel) / 80%), rgba(var(--palette-primary-darkerChannel) / 80%)), url('/cover-4.webp')`,
  45. backgroundSize: 'cover',
  46. backgroundPosition: 'center',
  47. backgroundRepeat: 'no-repeat'
  48. }),
  49. title: {
  50. backgroundColor: 'rgba(255, 255, 255, 0.25)',
  51. borderRadius: 8,
  52. backdropFilter: 'blur(4px)'
  53. },
  54. tag: {
  55. backgroundColor: 'rgba(255, 255, 255, 0.2)',
  56. borderRadius: 9999,
  57. backdropFilter: 'blur(4px)',
  58. border: '1px solid rgba(255,255,255,0.3)'
  59. },
  60. description: {
  61. backgroundColor: 'rgba(255, 255, 255, 0.2)',
  62. borderRadius: 4,
  63. backdropFilter: 'blur(4px)'
  64. },
  65. avatar: (isAllVendors) => {
  66. const colors = isAllVendors ? THEME_COLORS.allVendors : THEME_COLORS.specific;
  67. return {
  68. backgroundColor: colors.background,
  69. borderRadius: 12,
  70. border: `1px solid ${colors.border}`
  71. };
  72. },
  73. searchInput: {
  74. backgroundColor: THEME_COLORS.neutral.background,
  75. borderRadius: 8,
  76. border: `1px solid ${THEME_COLORS.neutral.border}`
  77. },
  78. button: {
  79. backgroundColor: THEME_COLORS.neutral.background,
  80. borderRadius: 8,
  81. border: `1px solid ${THEME_COLORS.neutral.border}`
  82. }
  83. };
  84. const createSkeletonRect = (style = {}, key = null) => (
  85. <div key={key} className="animate-pulse" style={style} />
  86. );
  87. const PricingVendorIntroSkeleton = memo(({
  88. isAllVendors = false,
  89. isMobile = false
  90. }) => {
  91. const placeholder = (
  92. <Card className="!rounded-2xl shadow-sm border-0"
  93. cover={
  94. <div
  95. className="relative h-32"
  96. style={SKELETON_STYLES.cover(isAllVendors ? THEME_COLORS.allVendors.primary : THEME_COLORS.specific.primary)}
  97. >
  98. <div className="relative z-10 h-full flex items-center justify-between p-4">
  99. <div className="flex-1 min-w-0 mr-4">
  100. <div className="flex flex-row flex-wrap items-center gap-2 sm:gap-3 mb-2">
  101. {createSkeletonRect({
  102. ...SKELETON_STYLES.title,
  103. width: isAllVendors ? SIZES.title.width.all : SIZES.title.width.specific,
  104. height: SIZES.title.height
  105. }, 'title')}
  106. {createSkeletonRect({
  107. ...SKELETON_STYLES.tag,
  108. width: SIZES.tag.width,
  109. height: SIZES.tag.height
  110. }, 'tag')}
  111. </div>
  112. <div className="space-y-2">
  113. {createSkeletonRect({
  114. ...SKELETON_STYLES.description,
  115. width: '100%',
  116. height: SIZES.description.height
  117. }, 'desc1')}
  118. {createSkeletonRect({
  119. ...SKELETON_STYLES.description,
  120. backgroundColor: 'rgba(255, 255, 255, 0.15)',
  121. width: '75%',
  122. height: SIZES.description.height
  123. }, 'desc2')}
  124. </div>
  125. </div>
  126. <div className="flex-shrink-0 w-16 h-16 rounded-2xl bg-white/90 shadow-md backdrop-blur-sm flex items-center justify-center">
  127. {createSkeletonRect({
  128. ...SKELETON_STYLES.avatar(isAllVendors),
  129. width: SIZES.avatar.width,
  130. height: SIZES.avatar.height
  131. }, 'avatar')}
  132. </div>
  133. </div>
  134. </div>
  135. }
  136. >
  137. <div className="flex items-center gap-2 w-full">
  138. <div className="flex-1">
  139. {createSkeletonRect({
  140. ...SKELETON_STYLES.searchInput,
  141. width: '100%',
  142. height: SIZES.searchInput.height
  143. }, 'search')}
  144. </div>
  145. {createSkeletonRect({
  146. ...SKELETON_STYLES.button,
  147. width: SIZES.button.width,
  148. height: SIZES.button.height
  149. }, 'copy-button')}
  150. {isMobile && createSkeletonRect({
  151. ...SKELETON_STYLES.button,
  152. width: SIZES.button.width,
  153. height: SIZES.button.height
  154. }, 'filter-button')}
  155. </div>
  156. </Card>
  157. );
  158. return (
  159. <Skeleton loading={true} active placeholder={placeholder}></Skeleton>
  160. );
  161. });
  162. PricingVendorIntroSkeleton.displayName = 'PricingVendorIntroSkeleton';
  163. export default PricingVendorIntroSkeleton;