PricingVendorIntroSkeleton.jsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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.allVendors.background,
  80. borderRadius: 8,
  81. border: `1px solid ${THEME_COLORS.allVendors.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. }) => {
  90. const placeholder = (
  91. <Card className="!rounded-2xl shadow-sm border-0"
  92. cover={
  93. <div
  94. className="relative h-32"
  95. style={SKELETON_STYLES.cover(isAllVendors ? THEME_COLORS.allVendors.primary : THEME_COLORS.specific.primary)}
  96. >
  97. <div className="relative z-10 h-full flex items-center justify-between p-4">
  98. <div className="flex-1 min-w-0 mr-4">
  99. <div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 mb-2">
  100. {createSkeletonRect({
  101. ...SKELETON_STYLES.title,
  102. width: isAllVendors ? SIZES.title.width.all : SIZES.title.width.specific,
  103. height: SIZES.title.height
  104. }, 'title')}
  105. {createSkeletonRect({
  106. ...SKELETON_STYLES.tag,
  107. width: SIZES.tag.width,
  108. height: SIZES.tag.height
  109. }, 'tag')}
  110. </div>
  111. <div className="space-y-2">
  112. {createSkeletonRect({
  113. ...SKELETON_STYLES.description,
  114. width: '100%',
  115. height: SIZES.description.height
  116. }, 'desc1')}
  117. {createSkeletonRect({
  118. ...SKELETON_STYLES.description,
  119. backgroundColor: 'rgba(255, 255, 255, 0.15)',
  120. width: '75%',
  121. height: SIZES.description.height
  122. }, 'desc2')}
  123. </div>
  124. </div>
  125. <div className="flex-shrink-0 w-16 h-16 rounded-2xl bg-white/90 shadow-md backdrop-blur-sm flex items-center justify-center">
  126. {createSkeletonRect({
  127. ...SKELETON_STYLES.avatar(isAllVendors),
  128. width: SIZES.avatar.width,
  129. height: SIZES.avatar.height
  130. }, 'avatar')}
  131. </div>
  132. </div>
  133. </div>
  134. }
  135. >
  136. <div className="flex items-center gap-2 w-full">
  137. <div className="flex-1">
  138. {createSkeletonRect({
  139. ...SKELETON_STYLES.searchInput,
  140. width: '100%',
  141. height: SIZES.searchInput.height
  142. }, 'search')}
  143. </div>
  144. {createSkeletonRect({
  145. ...SKELETON_STYLES.button,
  146. width: SIZES.button.width,
  147. height: SIZES.button.height
  148. }, 'button')}
  149. </div>
  150. </Card>
  151. );
  152. return (
  153. <Skeleton loading={true} active placeholder={placeholder}></Skeleton>
  154. );
  155. });
  156. PricingVendorIntroSkeleton.displayName = 'PricingVendorIntroSkeleton';
  157. export default PricingVendorIntroSkeleton;