Design system.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

207 lignes
7.3 KiB

  1. import * as React from 'react';
  2. import {augmentImageFile, getMimeTypeDescription} from 'packages/web-kitchensink-reactnext/src/utils/blob';
  3. import {formatFileSize, formatNumeral} from 'packages/web-kitchensink-reactnext/src/utils/numeral';
  4. import clsx from 'clsx';
  5. import {useFileMetadata, useFileUrl, useImageControls} from 'src/index';
  6. import {KeyValueTable} from 'categories/information/react';
  7. import {useClientSide} from 'packages/react-utils';
  8. import type {CommonPreviewProps} from '../../../../../categories/blob/react/src/components/FileSelectBox';
  9. import {Swatch} from 'categories/color/react';
  10. export type ImageFilePreviewDerivedElement = HTMLImageElement;
  11. export interface ImageFilePreviewProps<F extends Partial<File> = Partial<File>>
  12. extends Omit<React.HTMLProps<ImageFilePreviewDerivedElement>, 'src' | 'alt'>, CommonPreviewProps<F> {}
  13. export const ImageFilePreview = React.forwardRef<ImageFilePreviewDerivedElement, ImageFilePreviewProps>(({
  14. file,
  15. className,
  16. style,
  17. disabled = false,
  18. enhanced: enhancedProp = false,
  19. ...etcProps
  20. }, forwardedRef) => {
  21. const { fileWithUrl, loading: urlLoading } = useFileUrl({ file });
  22. const { fileWithMetadata, loading: metadataLoading, error } = useFileMetadata({
  23. file: fileWithUrl as File,
  24. augmentFunction: augmentImageFile,
  25. });
  26. const {
  27. fullScreen,
  28. handleAction,
  29. imageRef,
  30. filenameRef,
  31. } = useImageControls({
  32. forwardedRef,
  33. });
  34. const { clientSide } = useClientSide({ clientSide: enhancedProp });
  35. if (!(fileWithMetadata)) {
  36. return null;
  37. }
  38. const cannotDisplayPicture = Boolean(
  39. typeof fileWithMetadata.url !== 'string'
  40. && error
  41. );
  42. return (
  43. <div
  44. className={clsx(
  45. 'flex flex-col sm:grid sm:grid-cols-3 gap-8 w-full',
  46. className,
  47. )}
  48. style={style}
  49. >
  50. <div className="h-full relative">
  51. <div className="sm:absolute top-0 left-0 w-full sm:h-full z-[3]">
  52. {typeof fileWithMetadata.url === 'string' && (
  53. <img
  54. {...etcProps}
  55. ref={imageRef}
  56. className={clsx(
  57. 'block h-full max-w-full object-center bg-[#000000]',
  58. {
  59. 'object-contain fixed w-full top-0 left-0 z-[3]': fullScreen,
  60. 'object-cover w-full': !fullScreen,
  61. },
  62. )}
  63. src={fileWithMetadata.url}
  64. alt=""
  65. data-testid="preview"
  66. />
  67. )}
  68. {cannotDisplayPicture && (
  69. <div className="w-full h-full flex items-center justify-center text-center px-4 bg-[#000000] select-none">
  70. {error!.message}
  71. </div>
  72. )}
  73. </div>
  74. </div>
  75. <div
  76. className="col-span-2 flex-shrink-0 m-0 flex flex-col gap-4 justify-between"
  77. >
  78. <KeyValueTable
  79. hiddenKeys
  80. data-testid="infoBox"
  81. properties={[
  82. Boolean(fileWithMetadata.name) && {
  83. key: 'Name',
  84. className: 'font-bold',
  85. valueProps: {
  86. ref: filenameRef,
  87. title: fileWithMetadata.name,
  88. children: fileWithMetadata.name,
  89. },
  90. },
  91. (clientSide && Boolean(getMimeTypeDescription(fileWithMetadata.type, fileWithMetadata.name) || '(Loading)') || Boolean(fileWithMetadata.type)) && {
  92. key: 'Type',
  93. valueProps: {
  94. className: clsx(
  95. !getMimeTypeDescription(fileWithMetadata.type, fileWithMetadata.name) && 'opacity-50'
  96. ),
  97. children: getMimeTypeDescription(fileWithMetadata.type, fileWithMetadata.name) || '(Loading)',
  98. },
  99. },
  100. (clientSide && Boolean(formatFileSize(fileWithMetadata.size) || '(Loading)') || Boolean(fileWithMetadata.size)) && {
  101. key: 'Size',
  102. valueProps: {
  103. className: clsx(
  104. !formatFileSize(fileWithMetadata.size) && 'opacity-50'
  105. ),
  106. title: `${formatNumeral(fileWithMetadata.size ?? 0)} byte(s)`,
  107. children: formatFileSize(fileWithMetadata.size) || '(Loading)',
  108. },
  109. },
  110. typeof fileWithMetadata.metadata?.width === 'number'
  111. && typeof fileWithMetadata.metadata?.height === 'number'
  112. && {
  113. key: 'Pixel Dimensions',
  114. valueProps: {
  115. children: `${formatNumeral(fileWithMetadata.metadata.width)} × ${formatNumeral(fileWithMetadata.metadata.height)} pixel(s)`,
  116. },
  117. },
  118. Array.isArray(fileWithMetadata.metadata?.palette)
  119. && {
  120. key: 'Palette',
  121. valueProps: {
  122. className: '-ml-4 pl-4 pt-4 ',
  123. children: (
  124. <div className="flex flex-wrap gap-3">
  125. {fileWithMetadata.metadata?.palette.map((rgb, i) => (
  126. <React.Fragment
  127. key={rgb.join(' ')}
  128. >
  129. {i > 0 && ' '}
  130. <Swatch
  131. color={rgb}
  132. mode="rgb"
  133. />
  134. </React.Fragment>
  135. ))}
  136. </div>
  137. ),
  138. },
  139. },
  140. ]}
  141. />
  142. {clientSide && (
  143. <form
  144. onSubmit={handleAction}
  145. className="flex gap-4 justify-end"
  146. >
  147. <fieldset
  148. disabled={disabled || cannotDisplayPicture}
  149. className="contents"
  150. >
  151. <legend className="sr-only">
  152. Controls
  153. </legend>
  154. <button
  155. type="submit"
  156. name="action"
  157. value="toggleFullScreen"
  158. className={clsx(
  159. 'h-12 flex text-primary disabled:text-primary focus:text-secondary active:text-tertiary items-center justify-center leading-none gap-4 select-none',
  160. 'focus:outline-0',
  161. 'disabled:opacity-50 disabled:cursor-not-allowed',
  162. {
  163. 'fixed top-0 left-0 w-full h-full opacity-0 z-[3]': fullScreen,
  164. }
  165. )}
  166. >
  167. <span
  168. className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
  169. >
  170. Preview
  171. </span>
  172. </button>
  173. {' '}
  174. <button
  175. type="submit"
  176. name="action"
  177. value="download"
  178. className={clsx(
  179. 'h-12 flex text-primary disabled:text-primary focus:text-secondary active:text-tertiary items-center justify-center leading-none gap-4 select-none',
  180. 'focus:outline-0',
  181. 'disabled:opacity-50 disabled:cursor-not-allowed',
  182. )}
  183. >
  184. <span
  185. className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
  186. >
  187. Download
  188. </span>
  189. </button>
  190. </fieldset>
  191. </form>
  192. )}
  193. </div>
  194. </div>
  195. );
  196. });
  197. ImageFilePreview.displayName = 'ImageFilePreview';