1
0

design_canvas.jsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /**
  2. * DesignCanvas — 变体并排网格布局
  3. *
  4. * 用于展示2+个静态设计variations让用户对比选择。
  5. * 每个variation有label,可hover放大。
  6. *
  7. * 用法:
  8. * <DesignCanvas
  9. * title="Hero区设计探索"
  10. * subtitle="3个方向对比"
  11. * columns={3}
  12. * >
  13. * <Variation label="Minimal" description="极简克制版">
  14. * <div>...你的设计1...</div>
  15. * </Variation>
  16. * <Variation label="Editorial" description="杂志编辑风">
  17. * <div>...你的设计2...</div>
  18. * </Variation>
  19. * <Variation label="Brutalist" description="粗粝原始">
  20. * <div>...你的设计3...</div>
  21. * </Variation>
  22. * </DesignCanvas>
  23. *
  24. * 配合React+Babel使用。放在合适的script里,然后window.DesignCanvas/window.Variation可用。
  25. */
  26. const canvasStyles = {
  27. container: {
  28. minHeight: '100vh',
  29. background: '#F5F5F0',
  30. padding: '40px 60px',
  31. fontFamily: '-apple-system, "SF Pro Text", "PingFang SC", sans-serif',
  32. },
  33. header: {
  34. marginBottom: 48,
  35. maxWidth: 900,
  36. },
  37. title: {
  38. fontSize: 36,
  39. fontWeight: 600,
  40. marginBottom: 12,
  41. color: '#1A1A1A',
  42. letterSpacing: '-0.02em',
  43. },
  44. subtitle: {
  45. fontSize: 16,
  46. color: '#666',
  47. lineHeight: 1.5,
  48. },
  49. grid: {
  50. display: 'grid',
  51. gap: 32,
  52. },
  53. cell: {
  54. display: 'flex',
  55. flexDirection: 'column',
  56. gap: 12,
  57. },
  58. cellHeader: {
  59. display: 'flex',
  60. alignItems: 'baseline',
  61. gap: 12,
  62. paddingBottom: 8,
  63. borderBottom: '1px solid #E0E0DA',
  64. },
  65. label: {
  66. fontSize: 14,
  67. fontWeight: 600,
  68. color: '#1A1A1A',
  69. letterSpacing: '-0.01em',
  70. },
  71. description: {
  72. fontSize: 13,
  73. color: '#888',
  74. },
  75. frame: {
  76. background: '#fff',
  77. borderRadius: 4,
  78. border: '1px solid #E0E0DA',
  79. overflow: 'hidden',
  80. position: 'relative',
  81. transition: 'transform 0.2s ease, box-shadow 0.2s ease',
  82. cursor: 'pointer',
  83. },
  84. frameInner: {
  85. position: 'relative',
  86. width: '100%',
  87. },
  88. badge: {
  89. position: 'absolute',
  90. top: 12,
  91. left: 12,
  92. background: 'rgba(0, 0, 0, 0.7)',
  93. color: '#fff',
  94. padding: '3px 8px',
  95. borderRadius: 4,
  96. fontSize: 11,
  97. fontWeight: 500,
  98. letterSpacing: '0.5px',
  99. textTransform: 'uppercase',
  100. zIndex: 10,
  101. pointerEvents: 'none',
  102. },
  103. };
  104. function DesignCanvas({ title, subtitle, columns = 3, children }) {
  105. const [expanded, setExpanded] = React.useState(null);
  106. const gridStyle = {
  107. ...canvasStyles.grid,
  108. gridTemplateColumns: `repeat(${columns}, 1fr)`,
  109. };
  110. return (
  111. <div style={canvasStyles.container}>
  112. {(title || subtitle) && (
  113. <div style={canvasStyles.header}>
  114. {title && <h1 style={canvasStyles.title}>{title}</h1>}
  115. {subtitle && <p style={canvasStyles.subtitle}>{subtitle}</p>}
  116. </div>
  117. )}
  118. <div style={gridStyle}>
  119. {React.Children.map(children, (child, idx) =>
  120. React.isValidElement(child)
  121. ? React.cloneElement(child, {
  122. _index: idx,
  123. _expanded: expanded === idx,
  124. _onToggle: () => setExpanded(expanded === idx ? null : idx),
  125. })
  126. : child
  127. )}
  128. </div>
  129. {expanded !== null && (
  130. <div
  131. onClick={() => setExpanded(null)}
  132. style={{
  133. position: 'fixed',
  134. inset: 0,
  135. background: 'rgba(0, 0, 0, 0.75)',
  136. zIndex: 1000,
  137. display: 'flex',
  138. alignItems: 'center',
  139. justifyContent: 'center',
  140. padding: 40,
  141. cursor: 'zoom-out',
  142. }}
  143. >
  144. <div
  145. onClick={e => e.stopPropagation()}
  146. style={{
  147. background: '#fff',
  148. borderRadius: 8,
  149. overflow: 'hidden',
  150. maxWidth: '90vw',
  151. maxHeight: '90vh',
  152. position: 'relative',
  153. }}
  154. >
  155. {React.Children.toArray(children)[expanded]}
  156. </div>
  157. </div>
  158. )}
  159. </div>
  160. );
  161. }
  162. function Variation({ label, description, number, children, _index, _expanded, _onToggle, aspectRatio = '4 / 3' }) {
  163. const displayNumber = number || String(_index + 1).padStart(2, '0');
  164. return (
  165. <div style={canvasStyles.cell}>
  166. <div style={canvasStyles.cellHeader}>
  167. <span style={{ ...canvasStyles.label, color: '#999', fontFamily: 'ui-monospace, monospace', fontSize: 12 }}>
  168. {displayNumber}
  169. </span>
  170. <span style={canvasStyles.label}>{label}</span>
  171. {description && <span style={canvasStyles.description}>— {description}</span>}
  172. </div>
  173. <div
  174. onClick={_onToggle}
  175. style={{
  176. ...canvasStyles.frame,
  177. aspectRatio,
  178. }}
  179. onMouseEnter={e => {
  180. e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.08)';
  181. }}
  182. onMouseLeave={e => {
  183. e.currentTarget.style.boxShadow = 'none';
  184. }}
  185. >
  186. <div style={canvasStyles.frameInner}>
  187. {children}
  188. </div>
  189. </div>
  190. </div>
  191. );
  192. }
  193. if (typeof window !== 'undefined') {
  194. Object.assign(window, { DesignCanvas, Variation });
  195. }