@snack-uikit/attachment 0.0.1-preview-8b6f4393.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/README.md +51 -0
  4. package/dist/components/Attachment/Attachment.d.ts +2 -0
  5. package/dist/components/Attachment/Attachment.js +28 -0
  6. package/dist/components/Attachment/components/Emblem/Emblem.d.ts +5 -0
  7. package/dist/components/Attachment/components/Emblem/Emblem.js +20 -0
  8. package/dist/components/Attachment/components/Emblem/index.d.ts +1 -0
  9. package/dist/components/Attachment/components/Emblem/index.js +1 -0
  10. package/dist/components/Attachment/components/Emblem/styled.module.css +15 -0
  11. package/dist/components/Attachment/components/index.d.ts +1 -0
  12. package/dist/components/Attachment/components/index.js +1 -0
  13. package/dist/components/Attachment/index.d.ts +1 -0
  14. package/dist/components/Attachment/index.js +1 -0
  15. package/dist/components/Attachment/styles.module.css +15 -0
  16. package/dist/components/AttachmentSquare/AttachmentSquare.d.ts +2 -0
  17. package/dist/components/AttachmentSquare/AttachmentSquare.js +27 -0
  18. package/dist/components/AttachmentSquare/components/HoverContent/HoverContent.d.ts +4 -0
  19. package/dist/components/AttachmentSquare/components/HoverContent/HoverContent.js +11 -0
  20. package/dist/components/AttachmentSquare/components/HoverContent/index.d.ts +1 -0
  21. package/dist/components/AttachmentSquare/components/HoverContent/index.js +1 -0
  22. package/dist/components/AttachmentSquare/components/LoadingContent/LoadingContent.d.ts +1 -0
  23. package/dist/components/AttachmentSquare/components/LoadingContent/LoadingContent.js +8 -0
  24. package/dist/components/AttachmentSquare/components/LoadingContent/index.d.ts +1 -0
  25. package/dist/components/AttachmentSquare/components/LoadingContent/index.js +1 -0
  26. package/dist/components/AttachmentSquare/components/MainContent/MainContent.d.ts +6 -0
  27. package/dist/components/AttachmentSquare/components/MainContent/MainContent.js +19 -0
  28. package/dist/components/AttachmentSquare/components/MainContent/index.d.ts +1 -0
  29. package/dist/components/AttachmentSquare/components/MainContent/index.js +1 -0
  30. package/dist/components/AttachmentSquare/components/index.d.ts +3 -0
  31. package/dist/components/AttachmentSquare/components/index.js +3 -0
  32. package/dist/components/AttachmentSquare/components/styles.module.css +12 -0
  33. package/dist/components/AttachmentSquare/index.d.ts +1 -0
  34. package/dist/components/AttachmentSquare/index.js +1 -0
  35. package/dist/components/AttachmentSquare/styles.module.css +16 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.js +2 -0
  38. package/dist/contexts.d.ts +5 -0
  39. package/dist/contexts.js +5 -0
  40. package/dist/helperComponents/Actions/Actions.d.ts +6 -0
  41. package/dist/helperComponents/Actions/Actions.js +20 -0
  42. package/dist/helperComponents/Actions/index.d.ts +1 -0
  43. package/dist/helperComponents/Actions/index.js +1 -0
  44. package/dist/helperComponents/Actions/styles.module.css +5 -0
  45. package/dist/helperComponents/Text/Text.d.ts +5 -0
  46. package/dist/helperComponents/Text/Text.js +10 -0
  47. package/dist/helperComponents/Text/index.d.ts +1 -0
  48. package/dist/helperComponents/Text/index.js +1 -0
  49. package/dist/helperComponents/Text/styles.module.css +16 -0
  50. package/dist/helperComponents/TextBlock/TextBlock.d.ts +5 -0
  51. package/dist/helperComponents/TextBlock/TextBlock.js +9 -0
  52. package/dist/helperComponents/TextBlock/index.d.ts +1 -0
  53. package/dist/helperComponents/TextBlock/index.js +1 -0
  54. package/dist/helperComponents/TextBlock/styles.module.css +68 -0
  55. package/dist/helperComponents/index.d.ts +2 -0
  56. package/dist/helperComponents/index.js +2 -0
  57. package/dist/helpers.d.ts +3 -0
  58. package/dist/helpers.js +18 -0
  59. package/dist/hooks.d.ts +4 -0
  60. package/dist/hooks.js +26 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +1 -0
  63. package/dist/types.d.ts +43 -0
  64. package/dist/types.js +1 -0
  65. package/package.json +45 -0
  66. package/src/components/Attachment/Attachment.tsx +55 -0
  67. package/src/components/Attachment/components/Emblem/Emblem.tsx +47 -0
  68. package/src/components/Attachment/components/Emblem/index.ts +1 -0
  69. package/src/components/Attachment/components/Emblem/styled.module.scss +16 -0
  70. package/src/components/Attachment/components/index.ts +1 -0
  71. package/src/components/Attachment/index.ts +1 -0
  72. package/src/components/Attachment/styles.module.scss +20 -0
  73. package/src/components/AttachmentSquare/AttachmentSquare.tsx +56 -0
  74. package/src/components/AttachmentSquare/components/HoverContent/HoverContent.tsx +24 -0
  75. package/src/components/AttachmentSquare/components/HoverContent/index.ts +1 -0
  76. package/src/components/AttachmentSquare/components/LoadingContent/LoadingContent.tsx +14 -0
  77. package/src/components/AttachmentSquare/components/LoadingContent/index.ts +1 -0
  78. package/src/components/AttachmentSquare/components/MainContent/MainContent.tsx +44 -0
  79. package/src/components/AttachmentSquare/components/MainContent/index.ts +1 -0
  80. package/src/components/AttachmentSquare/components/index.ts +3 -0
  81. package/src/components/AttachmentSquare/components/styles.module.scss +17 -0
  82. package/src/components/AttachmentSquare/index.ts +1 -0
  83. package/src/components/AttachmentSquare/styles.module.scss +25 -0
  84. package/src/components/index.ts +2 -0
  85. package/src/contexts.ts +14 -0
  86. package/src/helperComponents/Actions/Actions.tsx +48 -0
  87. package/src/helperComponents/Actions/index.ts +1 -0
  88. package/src/helperComponents/Actions/styles.module.scss +8 -0
  89. package/src/helperComponents/Text/Text.tsx +22 -0
  90. package/src/helperComponents/Text/index.ts +1 -0
  91. package/src/helperComponents/Text/styles.module.scss +24 -0
  92. package/src/helperComponents/TextBlock/TextBlock.tsx +22 -0
  93. package/src/helperComponents/TextBlock/index.ts +1 -0
  94. package/src/helperComponents/TextBlock/styles.module.scss +63 -0
  95. package/src/helperComponents/index.ts +2 -0
  96. package/src/helpers.ts +21 -0
  97. package/src/hooks.ts +33 -0
  98. package/src/index.ts +3 -0
  99. package/src/types.ts +47 -0
@@ -0,0 +1 @@
1
+ export * from './Text';
@@ -0,0 +1,16 @@
1
+ .wrapper{
2
+ width:var(--size-attachment-container-square, 80px);
3
+ height:var(--size-attachment-container-square, 80px);
4
+ }
5
+ .wrapper .main{
6
+ display:flex;
7
+ }
8
+ .wrapper .hover{
9
+ display:none;
10
+ }
11
+ .wrapper:hover .hover, .wrapper:focus-visible .hover{
12
+ display:flex;
13
+ }
14
+ .wrapper:hover .main, .wrapper:focus-visible .main{
15
+ display:none;
16
+ }
@@ -0,0 +1,5 @@
1
+ import { AttachmentProps } from '../../types';
2
+ export type TextBlockProps = Pick<AttachmentProps, 'title' | 'description' | 'error' | 'className'> & {
3
+ align?: 'center' | 'left';
4
+ };
5
+ export declare function TextBlock({ title, description, error, className, align }: TextBlockProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import cn from 'classnames';
3
+ import { useAttachmentContext } from '../../contexts';
4
+ import { Text } from '../Text';
5
+ import styles from './styles.module.css';
6
+ export function TextBlock({ title, description, error, className, align = 'left' }) {
7
+ const { size } = useAttachmentContext();
8
+ return (_jsxs("div", { className: cn(styles.content, className), "data-size": size, "data-align": align, children: [_jsx(Text, { className: styles.title, text: title }), _jsx(Text, { className: styles.description, text: description }), _jsx(Text, { className: styles.error, text: error })] }));
9
+ }
@@ -0,0 +1 @@
1
+ export * from './TextBlock';
@@ -0,0 +1 @@
1
+ export * from './TextBlock';
@@ -0,0 +1,68 @@
1
+ .content{
2
+ overflow:hidden;
3
+ display:flex;
4
+ flex:1;
5
+ flex-direction:column;
6
+ align-items:flex-start;
7
+ justify-content:flex-start;
8
+ }
9
+ .content[data-align=left]{
10
+ text-align:left;
11
+ }
12
+ .content[data-align=center]{
13
+ max-width:100%;
14
+ text-align:center;
15
+ }
16
+ .content[data-size=s] .title{
17
+ font-family:var(--sans-label-m-font-family, SB Sans Interface);
18
+ font-weight:var(--sans-label-m-font-weight, Semibold);
19
+ line-height:var(--sans-label-m-line-height, 16px);
20
+ font-size:var(--sans-label-m-font-size, 12px);
21
+ letter-spacing:var(--sans-label-m-letter-spacing, -0.1px);
22
+ paragraph-spacing:var(--sans-label-m-paragraph-spacing, 6.6px);
23
+ }
24
+ .content[data-size=s] .description,
25
+ .content[data-size=s] .error{
26
+ font-family:var(--sans-body-s-font-family, SB Sans Interface);
27
+ font-weight:var(--sans-body-s-font-weight, Regular);
28
+ line-height:var(--sans-body-s-line-height, 16px);
29
+ font-size:var(--sans-body-s-font-size, 12px);
30
+ letter-spacing:var(--sans-body-s-letter-spacing, 0.1px);
31
+ paragraph-spacing:var(--sans-body-s-paragraph-spacing, 6.6px);
32
+ }
33
+ .content[data-size=m] .title{
34
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
35
+ font-weight:var(--sans-label-l-font-weight, Semibold);
36
+ line-height:var(--sans-label-l-line-height, 20px);
37
+ font-size:var(--sans-label-l-font-size, 14px);
38
+ letter-spacing:var(--sans-label-l-letter-spacing, -0.1px);
39
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
40
+ }
41
+ .content[data-size=m] .description,
42
+ .content[data-size=m] .error{
43
+ font-family:var(--sans-body-m-font-family, SB Sans Interface);
44
+ font-weight:var(--sans-body-m-font-weight, Regular);
45
+ line-height:var(--sans-body-m-line-height, 20px);
46
+ font-size:var(--sans-body-m-font-size, 14px);
47
+ letter-spacing:var(--sans-body-m-letter-spacing, 0.1px);
48
+ paragraph-spacing:var(--sans-body-m-paragraph-spacing, 7.7px);
49
+ }
50
+
51
+ .title,
52
+ .description,
53
+ .error{
54
+ overflow:hidden;
55
+ width:100%;
56
+ }
57
+
58
+ .title{
59
+ color:var(--sys-neutral-text-main, #33333b);
60
+ }
61
+
62
+ .description{
63
+ color:var(--sys-neutral-text-light, #868892);
64
+ }
65
+
66
+ .error{
67
+ color:var(--sys-red-text-support, #ab423e);
68
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Actions';
2
+ export * from './TextBlock';
@@ -0,0 +1,2 @@
1
+ export * from './Actions';
2
+ export * from './TextBlock';
@@ -0,0 +1,3 @@
1
+ export declare function getBaseFileName(fileName?: string): string | undefined;
2
+ export declare function getFileExtension(fileName?: string): string | undefined;
3
+ export declare function isFileImage(file: File): RegExpMatchArray | null;
@@ -0,0 +1,18 @@
1
+ export function getBaseFileName(fileName) {
2
+ if (!fileName) {
3
+ return fileName;
4
+ }
5
+ const parts = fileName.split('.');
6
+ parts.pop();
7
+ // Для отображения названия файлов без имени (например, .bashrc, .gitignore)
8
+ if (parts.length === 1 && parts[0] === '')
9
+ return fileName;
10
+ return parts.join('.');
11
+ }
12
+ export function getFileExtension(fileName) {
13
+ var _a;
14
+ return (_a = fileName === null || fileName === void 0 ? void 0 : fileName.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLocaleUpperCase();
15
+ }
16
+ export function isFileImage(file) {
17
+ return file.type.match(/image\/(png|jpg|jpeg|gif|webp|bmp|svg)/i);
18
+ }
@@ -0,0 +1,4 @@
1
+ export declare function useImage(file?: File): {
2
+ imageData: string | undefined;
3
+ loading: boolean;
4
+ };
package/dist/hooks.js ADDED
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { isFileImage } from './helpers';
3
+ export function useImage(file) {
4
+ const [imageData, setImageData] = useState(undefined);
5
+ const [loading, setIsLoading] = useState(false);
6
+ useEffect(() => {
7
+ if (file && isFileImage(file)) {
8
+ const reader = new FileReader();
9
+ reader.onloadstart = () => {
10
+ setIsLoading(true);
11
+ };
12
+ reader.onloadend = () => {
13
+ setIsLoading(false);
14
+ const { result } = reader;
15
+ if (result) {
16
+ setImageData(result.toString());
17
+ }
18
+ };
19
+ reader.readAsDataURL(file);
20
+ }
21
+ else {
22
+ setImageData(undefined);
23
+ }
24
+ }, [file]);
25
+ return { imageData, loading };
26
+ }
@@ -0,0 +1,2 @@
1
+ export * from './components';
2
+ export type { AttachmentSquareProps, AttachmentProps } from './types';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './components';
@@ -0,0 +1,43 @@
1
+ import { CardProps } from '@snack-uikit/card';
2
+ import { IconPredefinedProps } from '@snack-uikit/icon-predefined';
3
+ import { TruncateStringProps } from '@snack-uikit/truncate-string';
4
+ import { WithSupportProps } from '@snack-uikit/utils';
5
+ export type Size = 's' | 'm';
6
+ export type AttachmentCommonProps = WithSupportProps<{
7
+ /** Файл */
8
+ file?: File;
9
+ /** Колбек на клик по кнопке скачивания */
10
+ onDownload?(file?: File | undefined): void;
11
+ /** Колбек на клик по кнопке удаления */
12
+ onDelete?(file?: File | undefined): void;
13
+ /**
14
+ * Заголовок
15
+ * @default file.name
16
+ */
17
+ title?: string;
18
+ /**
19
+ * Описание
20
+ * @default file.type
21
+ */
22
+ description?: string;
23
+ /** Сообщение об ошибке */
24
+ error?: string;
25
+ /**
26
+ * Вариант обрезания строки:
27
+ * <br> - `End` - с конца;
28
+ * <br> - `Middle` - по середине
29
+ * */
30
+ truncateVariant?: TruncateStringProps['variant'];
31
+ /** Управление состоянием загрузки */
32
+ loading?: boolean;
33
+ /**
34
+ * Объект сопоставления расширения файла и отображаемой иконки
35
+ * @defaultIcon FileSVG
36
+ */
37
+ extension2Icon?: Record<string, IconPredefinedProps['icon']>;
38
+ }> & Pick<CardProps, 'checked' | 'disabled' | 'multipleSelection' | 'onClick' | 'className'>;
39
+ export type AttachmentSquareProps = AttachmentCommonProps;
40
+ export type AttachmentProps = AttachmentCommonProps & {
41
+ /** Размер */
42
+ size?: Size;
43
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@snack-uikit/attachment",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "title": "Attachment",
7
+ "version": "0.0.1-preview-8b6f4393.0",
8
+ "sideEffects": [
9
+ "*.css",
10
+ "*.woff",
11
+ "*.woff2"
12
+ ],
13
+ "description": "",
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "homepage": "https://cloud-ru-tech.github.io/snack-uikit/packages/attachment",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/cloud-ru-tech/snack-uikit/tree/master",
20
+ "directory": "packages/attachment"
21
+ },
22
+ "author": "Nikita Ershov <niershov@cloud.ru>",
23
+ "contributors": [
24
+ "Nikita Ershov <niershov@cloud.ru>"
25
+ ],
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "./CHANGELOG.md",
30
+ "./LICENSE"
31
+ ],
32
+ "license": "Apache-2.0",
33
+ "scripts": {},
34
+ "dependencies": {
35
+ "@snack-uikit/button": "0.17.1",
36
+ "@snack-uikit/card": "0.12.13",
37
+ "@snack-uikit/icon-predefined": "0.5.1",
38
+ "@snack-uikit/icons": "0.21.0",
39
+ "@snack-uikit/loaders": "0.5.2",
40
+ "@snack-uikit/truncate-string": "0.4.14",
41
+ "@snack-uikit/utils": "3.3.0",
42
+ "classnames": "2.3.2"
43
+ },
44
+ "gitHead": "22c140322d6f41f78990eaf8ae6a9b38ea039247"
45
+ }
@@ -0,0 +1,55 @@
1
+ import { Card } from '@snack-uikit/card';
2
+ import { extractSupportProps } from '@snack-uikit/utils';
3
+
4
+ import { AttachmentContext } from '../../contexts';
5
+ import { Actions, TextBlock } from '../../helperComponents';
6
+ import { getBaseFileName, getFileExtension } from '../../helpers';
7
+ import { useImage } from '../../hooks';
8
+ import { AttachmentProps } from '../../types';
9
+ import { Emblem } from './components/Emblem';
10
+ import styles from './styles.module.scss';
11
+
12
+ export function Attachment({
13
+ file,
14
+ loading,
15
+ extension2Icon,
16
+ title: titleProp,
17
+ description: descriptionProp,
18
+ error,
19
+ disabled,
20
+ truncateVariant,
21
+ checked,
22
+ multipleSelection,
23
+ onDownload,
24
+ onDelete,
25
+ onClick,
26
+ size = 's',
27
+ ...rest
28
+ }: AttachmentProps) {
29
+ const { loading: loadingImage, imageData } = useImage(file);
30
+ const isLoading = loading || loadingImage;
31
+
32
+ const title = titleProp || getBaseFileName(file?.name);
33
+ const description = descriptionProp || getFileExtension(file?.name);
34
+
35
+ return (
36
+ <Card
37
+ {...extractSupportProps(rest)}
38
+ outline
39
+ disabled={disabled}
40
+ checked={checked}
41
+ multipleSelection={multipleSelection}
42
+ size={size}
43
+ onClick={!isLoading && !disabled ? onClick : undefined}
44
+ header={
45
+ <AttachmentContext.Provider value={{ file, truncateVariant, onDelete, onDownload, disabled, size }}>
46
+ <div className={styles.composition} data-size={size}>
47
+ <Emblem title={title} loading={isLoading} extension2Icon={extension2Icon} imageData={imageData} />
48
+ <TextBlock title={title} description={description} error={error} />
49
+ <Actions hideDownload={isLoading} />
50
+ </div>
51
+ </AttachmentContext.Provider>
52
+ }
53
+ />
54
+ );
55
+ }
@@ -0,0 +1,47 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { IconPredefined } from '@snack-uikit/icon-predefined';
4
+ import { FileSVG } from '@snack-uikit/icons';
5
+ import { Spinner } from '@snack-uikit/loaders';
6
+
7
+ import { useAttachmentContext } from '../../../../contexts';
8
+ import { getFileExtension } from '../../../../helpers';
9
+ import { AttachmentProps } from '../../../../types';
10
+ import styles from './styled.module.scss';
11
+
12
+ export type EmblemProps = {
13
+ imageData?: string;
14
+ } & Pick<AttachmentProps, 'extension2Icon' | 'loading' | 'title'>;
15
+
16
+ export function Emblem({ loading, extension2Icon, imageData, title }: EmblemProps) {
17
+ const { file, size } = useAttachmentContext();
18
+
19
+ const extension = useMemo(() => getFileExtension(file?.name) ?? '', [file?.name]);
20
+
21
+ if (loading) {
22
+ return (
23
+ <IconPredefined size={size} icon={() => <Spinner size='s' />} decor={false} appearance='neutral' shape='square' />
24
+ );
25
+ }
26
+
27
+ if (imageData) {
28
+ return (
29
+ <IconPredefined
30
+ size={size}
31
+ icon={() => <img src={imageData} alt={title} data-size={size || undefined} className={styles.img} />}
32
+ decor={false}
33
+ shape='square'
34
+ />
35
+ );
36
+ }
37
+
38
+ return (
39
+ <IconPredefined
40
+ size={size}
41
+ icon={extension2Icon?.[extension] ?? FileSVG}
42
+ decor
43
+ appearance='neutral'
44
+ shape='square'
45
+ />
46
+ );
47
+ }
@@ -0,0 +1 @@
1
+ export * from './Emblem';
@@ -0,0 +1,16 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-attachment';
2
+
3
+ $sizes: 's', 'm';
4
+
5
+ .img {
6
+ overflow: hidden;
7
+ flex-shrink: 0;
8
+ box-sizing: border-box;
9
+ object-fit: cover;
10
+
11
+ @each $size in $sizes {
12
+ &[data-size='#{$size}'] {
13
+ @include composite-var($attachment, 'composition', 'start-item', $size);
14
+ }
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ export * from './Emblem';
@@ -0,0 +1 @@
1
+ export * from './Attachment';
@@ -0,0 +1,20 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-attachment';
2
+
3
+ $sizes: 's', 'm';
4
+
5
+ .composition {
6
+ overflow: hidden;
7
+ display: flex;
8
+ align-items: flex-start;
9
+
10
+ box-sizing: border-box;
11
+ width: 100%;
12
+ margin: calc(0px - $dimension-050m);
13
+ padding: $dimension-050m;
14
+
15
+ @each $size in $sizes {
16
+ &[data-size='#{$size}'] {
17
+ @include composite-var($attachment, 'composition', $size);
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,56 @@
1
+ import { Card } from '@snack-uikit/card';
2
+ import { extractSupportProps } from '@snack-uikit/utils';
3
+
4
+ import { AttachmentContext } from '../../contexts';
5
+ import { getBaseFileName, getFileExtension } from '../../helpers';
6
+ import { useImage } from '../../hooks';
7
+ import { AttachmentSquareProps } from '../../types';
8
+ import { HoverContent, LoadingContent, MainContent } from './components';
9
+ import styles from './styles.module.scss';
10
+
11
+ export function AttachmentSquare({
12
+ file,
13
+ loading,
14
+ extension2Icon,
15
+ title: titleProp,
16
+ description: descriptionProp,
17
+ error,
18
+ disabled,
19
+ truncateVariant,
20
+ checked,
21
+ multipleSelection,
22
+ onDownload,
23
+ onDelete,
24
+ onClick,
25
+ ...rest
26
+ }: AttachmentSquareProps) {
27
+ const { loading: loadingImage, imageData } = useImage(file);
28
+ const isLoading = loading || loadingImage;
29
+
30
+ const title = titleProp || getBaseFileName(file?.name);
31
+ const description = descriptionProp || getFileExtension(file?.name);
32
+
33
+ return (
34
+ <Card
35
+ {...extractSupportProps(rest)}
36
+ outline
37
+ disabled={disabled}
38
+ size='s'
39
+ className={styles.wrapper}
40
+ checked={checked}
41
+ multipleSelection={multipleSelection}
42
+ onClick={!isLoading && !disabled ? onClick : undefined}
43
+ header={
44
+ <AttachmentContext.Provider value={{ file, truncateVariant, onDelete, onDownload, disabled, size: 's' }}>
45
+ {!isLoading && !disabled && (
46
+ <MainContent title={title} extension2Icon={extension2Icon} imageData={imageData} className={styles.main} />
47
+ )}
48
+ {!isLoading && (
49
+ <HoverContent title={title} description={description} error={error} className={styles.hover} />
50
+ )}
51
+ {isLoading && <LoadingContent />}
52
+ </AttachmentContext.Provider>
53
+ }
54
+ />
55
+ );
56
+ }
@@ -0,0 +1,24 @@
1
+ import cn from 'classnames';
2
+
3
+ import { useAttachmentContext } from '../../../../contexts';
4
+ import { Actions, TextBlock } from '../../../../helperComponents';
5
+ import { AttachmentProps } from '../../../../types';
6
+ import styles from '../styles.module.scss';
7
+
8
+ type HoverContentProps = Pick<AttachmentProps, 'title' | 'description' | 'error' | 'className'>;
9
+
10
+ export function HoverContent({ title, description, error, className = '' }: HoverContentProps) {
11
+ const { disabled } = useAttachmentContext();
12
+
13
+ return (
14
+ <div
15
+ className={cn(styles.composition, {
16
+ [className]: !disabled,
17
+ })}
18
+ >
19
+ <TextBlock title={title} error={error} description={!error ? description : undefined} align='center' />
20
+
21
+ <Actions />
22
+ </div>
23
+ );
24
+ }
@@ -0,0 +1 @@
1
+ export * from './HoverContent';
@@ -0,0 +1,14 @@
1
+ import { IconPredefined } from '@snack-uikit/icon-predefined';
2
+ import { Spinner } from '@snack-uikit/loaders';
3
+
4
+ import { Actions } from '../../../../helperComponents';
5
+ import styles from '../styles.module.scss';
6
+
7
+ export function LoadingContent() {
8
+ return (
9
+ <div className={styles.composition}>
10
+ <IconPredefined size='s' icon={() => <Spinner size='s' />} decor={false} appearance='neutral' shape='square' />
11
+ <Actions hideDownload />
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1 @@
1
+ export * from './LoadingContent';
@@ -0,0 +1,44 @@
1
+ import cn from 'classnames';
2
+ import { useMemo } from 'react';
3
+
4
+ import { Card } from '@snack-uikit/card';
5
+ import { IconPredefined } from '@snack-uikit/icon-predefined';
6
+ import { FileSVG } from '@snack-uikit/icons';
7
+
8
+ import { useAttachmentContext } from '../../../../contexts';
9
+ import { TextBlock } from '../../../../helperComponents';
10
+ import { getFileExtension } from '../../../../helpers';
11
+ import { AttachmentProps } from '../../../../types';
12
+ import styles from '../styles.module.scss';
13
+
14
+ type MainContentProps = {
15
+ imageData?: string;
16
+ } & Pick<AttachmentProps, 'title' | 'extension2Icon' | 'className'>;
17
+
18
+ export function MainContent({ title, extension2Icon, imageData, className }: MainContentProps) {
19
+ const { file } = useAttachmentContext();
20
+
21
+ const extension = useMemo(() => getFileExtension(file?.name) ?? '', [file?.name]);
22
+
23
+ if (imageData) {
24
+ return (
25
+ <div className={cn(styles.composition, className)}>
26
+ <Card.Image src={imageData} alt={file?.name ?? 'file'} mode='background' hideFading />
27
+ </div>
28
+ );
29
+ }
30
+
31
+ return (
32
+ <div className={cn(styles.composition, className)}>
33
+ <TextBlock title={title} align='center' />
34
+
35
+ <IconPredefined
36
+ size='s'
37
+ icon={extension2Icon?.[extension] ?? FileSVG}
38
+ decor
39
+ appearance='neutral'
40
+ shape='square'
41
+ />
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1 @@
1
+ export * from './MainContent';
@@ -0,0 +1,3 @@
1
+ export * from './LoadingContent';
2
+ export * from './HoverContent';
3
+ export * from './MainContent';
@@ -0,0 +1,17 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-attachment';
2
+
3
+ .composition {
4
+ @include composite-var($attachment-composition-s);
5
+
6
+ overflow: hidden;
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+
11
+ box-sizing: content-box;
12
+ width: 100%;
13
+ margin: calc(0px - $dimension-050m);
14
+ padding: $dimension-050m;
15
+
16
+ text-align: center;
17
+ }
@@ -0,0 +1 @@
1
+ export * from './AttachmentSquare';
@@ -0,0 +1,25 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-attachment';
2
+
3
+ .wrapper {
4
+ @include composite-var($attachment-container-square);
5
+
6
+ .main {
7
+ display: flex;
8
+ }
9
+
10
+ .hover {
11
+ display: none;
12
+ }
13
+
14
+ &:hover,
15
+ &:focus-within,
16
+ &:focus-visible {
17
+ .hover {
18
+ display: flex;
19
+ }
20
+
21
+ .main {
22
+ display: none;
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,2 @@
1
+ export * from './AttachmentSquare';
2
+ export * from './Attachment';
@@ -0,0 +1,14 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ import { AttachmentProps } from './types';
4
+
5
+ export type AttachmentContextType = Pick<
6
+ AttachmentProps,
7
+ 'size' | 'file' | 'truncateVariant' | 'onDownload' | 'onDelete' | 'disabled'
8
+ >;
9
+
10
+ export const AttachmentContext = createContext<AttachmentContextType>({});
11
+
12
+ export function useAttachmentContext() {
13
+ return useContext(AttachmentContext);
14
+ }