@ssa-ui-kit/core 3.16.3 → 3.17.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.
@@ -0,0 +1,36 @@
1
+ import { FileAttachmentProps } from './types';
2
+ /**
3
+ * FileAttachment - Read-only row displaying a single attached/uploading file
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * <FileAttachment
8
+ * file={{ name: 'Report.pdf', size: 20 * 1024 * 1024 }}
9
+ * progress={50}
10
+ * onRemove={() => handleRemove(file)}
11
+ * />
12
+ * ```
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // Progress bar look instead of the default percentage text
17
+ * <FileAttachment
18
+ * file={{ name: 'Report.pdf', size: 20 * 1024 * 1024 }}
19
+ * progress={50}
20
+ * progressDisplay="bar"
21
+ * onRemove={() => handleRemove(file)}
22
+ * />
23
+ * ```
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * // Omitting `progress` entirely shows just the file size, no progress copy —
28
+ * // e.g. a file that's been selected but whose upload hasn't started yet.
29
+ * <FileAttachment
30
+ * file={{ name: 'Report.pdf', size: 20 * 1024 * 1024 }}
31
+ * onRemove={() => handleRemove(file)}
32
+ * />
33
+ * ```
34
+ */
35
+ declare const FileAttachment: ({ file, size, progress, progressDisplay, uploadingText, uploadedText, showDescription, icon, isDisabled, onRemove, className, css: cssProp, }: FileAttachmentProps) => import("@emotion/react/jsx-runtime").JSX.Element;
36
+ export default FileAttachment;
@@ -0,0 +1 @@
1
+ export { useFilePreviewUrl } from './useFilePreviewUrl';
@@ -0,0 +1,2 @@
1
+ /** Creates an object URL for a local File/Blob and revokes it on unmount or when `content` changes, avoiding a memory leak. */
2
+ export declare const useFilePreviewUrl: (content?: File | Blob) => string | undefined;
@@ -0,0 +1,2 @@
1
+ export { default } from './FileAttachment';
2
+ export type * from './types';
@@ -0,0 +1,22 @@
1
+ import { Theme } from '@emotion/react';
2
+ export declare const iconSizeBySize: {
3
+ large: number;
4
+ small: number;
5
+ };
6
+ /** Placeholder glyph is inset ~20% on each side of its box in the design (40px box -> 24px glyph). */
7
+ export declare const placeholderIconSizeBySize: {
8
+ large: number;
9
+ small: number;
10
+ };
11
+ export declare const container: (theme: Theme, size: "large" | "small") => import("@emotion/react").SerializedStyles;
12
+ export declare const disabledContainer: (theme: Theme) => import("@emotion/react").SerializedStyles;
13
+ export declare const iconWrapper: (size: "large" | "small") => import("@emotion/react").SerializedStyles;
14
+ export declare const placeholderWrapper: (theme: Theme) => import("@emotion/react").SerializedStyles;
15
+ export declare const previewImage: import("@emotion/react").SerializedStyles;
16
+ export declare const textColumn: import("@emotion/react").SerializedStyles;
17
+ export declare const title: (theme: Theme) => import("@emotion/react").SerializedStyles;
18
+ export declare const description: (theme: Theme) => import("@emotion/react").SerializedStyles;
19
+ export declare const dot: (theme: Theme) => import("@emotion/react").SerializedStyles;
20
+ export declare const progressTrack: (theme: Theme) => import("@emotion/react").SerializedStyles;
21
+ export declare const progressFill: (theme: Theme) => import("@emotion/react").SerializedStyles;
22
+ export declare const deleteButton: (theme: Theme) => import("@emotion/react").SerializedStyles;
@@ -0,0 +1,24 @@
1
+ import { Interpolation, Theme } from '@emotion/react';
2
+ import { IconProps } from '../Icon/types';
3
+ export interface FileAttachmentFile {
4
+ name: string;
5
+ size: number;
6
+ /** Local file/blob not yet uploaded — used to render an image preview via an object URL. Ignored for non-image file names. */
7
+ content?: File | Blob;
8
+ /** URL of an already-uploaded image, e.g. returned by the backend. Ignored for non-image file names. Takes precedence over `content`. */
9
+ previewUrl?: string;
10
+ }
11
+ export interface FileAttachmentProps {
12
+ file: FileAttachmentFile;
13
+ size?: 'large' | 'small';
14
+ progress?: number;
15
+ progressDisplay?: 'text' | 'bar';
16
+ uploadingText?: string;
17
+ uploadedText?: string;
18
+ showDescription?: boolean;
19
+ icon?: IconProps['name'];
20
+ isDisabled?: boolean;
21
+ onRemove?: () => void;
22
+ className?: string;
23
+ css?: Interpolation<Theme>;
24
+ }
@@ -0,0 +1,6 @@
1
+ import { IconProps } from '../Icon/types';
2
+ export declare const formatBytes: (bytes: number) => string;
3
+ /** Returns the file-type icon for a known extension, or `null` when the design's grey Placeholder look should be used instead. */
4
+ export declare const getFileTypeIcon: (fileName: string) => IconProps["name"] | null;
5
+ /** Gates image-preview rendering strictly on the file name's extension, so a pdf/php/etc. is never rendered via `<img>` even if preview data is (incorrectly) supplied for it. */
6
+ export declare const isImageFile: (fileName: string) => boolean;
@@ -4,7 +4,8 @@ import { FileUploadProps } from './types';
4
4
  *
5
5
  * Supports single and multi-file selection with built-in validation for
6
6
  * file formats and size. In multi-file mode, selected files are listed
7
- * below the input with individual remove controls.
7
+ * below the input with individual remove controls. Single-file mode can opt
8
+ * into the same list treatment via `showFileAttachment`.
8
9
  *
9
10
  * @example
10
11
  * ```tsx
@@ -32,6 +33,36 @@ import { FileUploadProps } from './types';
32
33
  * onChange={setFiles}
33
34
  * />
34
35
  * ```
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // Multi-file with per-file upload progress, driven by the consumer's own
40
+ * // upload requests — FileUpload only handles local selection, so it has no
41
+ * // progress data of its own.
42
+ * <FileUpload
43
+ * isMultiFile
44
+ * value={files}
45
+ * onChange={setFiles}
46
+ * uploadProgress={[
47
+ * { name: 'report.pdf', progress: 50 },
48
+ * { name: 'photo.png', progress: 100 },
49
+ * ]}
50
+ * />
51
+ * ```
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * // Single file, shown as a FileAttachment card below the input (icon,
56
+ * // size, delete button, image preview) instead of inline text next to the
57
+ * // button. `uploadProgress` here can just be a single number, since there's
58
+ * // only ever one file.
59
+ * <FileUpload
60
+ * showFileAttachment
61
+ * value={file}
62
+ * onChange={(files) => setFile(files[0])}
63
+ * uploadProgress={70}
64
+ * />
65
+ * ```
35
66
  */
36
- declare const FileUpload: ({ label, placeholder, helperText, actionText, error, disabled, css, className, allowedFormats, maxFileSize, isMultiFile, maxFiles, withDropArea, uploadedSectionTitle, value, onChange, onFileRejected, }: FileUploadProps) => import("@emotion/react/jsx-runtime").JSX.Element;
67
+ declare const FileUpload: ({ label, placeholder, helperText, actionText, error, disabled, css, className, allowedFormats, maxFileSize, isMultiFile, maxFiles, withDropArea, uploadedSectionTitle, uploadProgress, showFileAttachment, value, onChange, onFileRejected, }: FileUploadProps) => import("@emotion/react/jsx-runtime").JSX.Element;
37
68
  export default FileUpload;
@@ -1,3 +1,2 @@
1
1
  export { default } from './FileUpload';
2
- export { default as FileUploadItem } from './FileUploadItem';
3
- export type { FileUploadProps, FileRejectionReason } from './types';
2
+ export type { FileUploadProps, FileRejectionReason, FileUploadProgress, FileUploadProgressEntry, } from './types';
@@ -16,9 +16,3 @@ export declare const dropAreaAction: (theme: Theme) => import("@emotion/react").
16
16
  export declare const dropAreaClearButton: (theme: Theme) => import("@emotion/react").SerializedStyles;
17
17
  export declare const filesList: import("@emotion/react").SerializedStyles;
18
18
  export declare const filesListTitle: (theme: Theme) => import("@emotion/react").SerializedStyles;
19
- export declare const fileItem: (theme: Theme) => import("@emotion/react").SerializedStyles;
20
- export declare const fileIconWrapper: import("@emotion/react").SerializedStyles;
21
- export declare const fileInfo: import("@emotion/react").SerializedStyles;
22
- export declare const fileName: (theme: Theme) => import("@emotion/react").SerializedStyles;
23
- export declare const fileSize: (theme: Theme) => import("@emotion/react").SerializedStyles;
24
- export declare const deleteButton: (theme: Theme) => import("@emotion/react").SerializedStyles;
@@ -1,5 +1,11 @@
1
1
  import { Interpolation, Theme } from '@emotion/react';
2
2
  export type FileRejectionReason = 'size' | 'format';
3
+ export interface FileUploadProgressEntry {
4
+ name: string;
5
+ progress: number;
6
+ }
7
+ /** A single percentage applies to every listed file uniformly; a list matches per-file by `name` — files with no matching entry show no progress. */
8
+ export type FileUploadProgress = number | FileUploadProgressEntry[];
3
9
  export interface FileUploadProps {
4
10
  label?: string;
5
11
  placeholder?: string;
@@ -14,6 +20,10 @@ export interface FileUploadProps {
14
20
  maxFiles?: number;
15
21
  withDropArea?: boolean;
16
22
  uploadedSectionTitle?: string;
23
+ /** Upload progress for files in the multi-file list (`isMultiFile`). FileUpload only handles local selection, not the network upload itself — pass this in as the consumer's own upload reports progress. Files with no corresponding progress show just their size. */
24
+ uploadProgress?: FileUploadProgress;
25
+ /** Single-file mode only (`!isMultiFile`, ignored when `withDropArea` is set — its own selected-file view already covers this): show the selected file as a `FileAttachment` card below the input, instead of inline text next to the button. The input row then always shows `placeholder`, matching how multi-file mode behaves. */
26
+ showFileAttachment?: boolean;
17
27
  className?: string;
18
28
  value?: File | File[];
19
29
  onChange?: (files: File[]) => void;
@@ -41,8 +41,9 @@ export * from './ColorPicker';
41
41
  export * from './SearchBox';
42
42
  export type * from './SearchBox/types';
43
43
  export { default as FileUpload } from './FileUpload';
44
- export { FileUploadItem } from './FileUpload';
45
44
  export type * from './FileUpload/types';
45
+ export { default as FileAttachment } from './FileAttachment';
46
+ export type * from './FileAttachment/types';
46
47
  export * from './Field';
47
48
  export { default as Form } from './Form';
48
49
  export { default as FormAction } from './FormAction';
package/dist/index.js CHANGED
@@ -7999,8 +7999,8 @@ __webpack_require__.d(__webpack_exports__, {
7999
7999
  DropdownOptions: () => (/* reexport */ DropdownOptions_DropdownOptions),
8000
8000
  DropdownToggle: () => (/* reexport */ DropdownToggle_DropdownToggle),
8001
8001
  Field: () => (/* reexport */ index_parts_namespaceObject),
8002
+ FileAttachment: () => (/* reexport */ FileAttachment_FileAttachment),
8002
8003
  FileUpload: () => (/* reexport */ FileUpload_FileUpload),
8003
- FileUploadItem: () => (/* reexport */ FileUpload_FileUploadItem),
8004
8004
  Filters: () => (/* reexport */ Filters),
8005
8005
  FiltersMultiSelect: () => (/* reexport */ FiltersMultiSelect),
8006
8006
  FiltersMultiSelectEmpty: () => (/* reexport */ FiltersMultiSelectEmpty),
@@ -20626,11 +20626,11 @@ const multipleStyles = ({
20626
20626
  isOpen
20627
20627
  }) => {
20628
20628
  const borderColor = isOpen ? theme.palette.primary.main : theme.colors.grey;
20629
- return /*#__PURE__*/(0,react_namespaceObject.css)("justify-content:space-between;height:40px;padding:11px 15px 9px 10px;font-size:14px;font-weight:500;color:", theme.colors.greyDarker, ";border:1px solid ", borderColor, ";border-radius:12px;background:", theme.colors.white, ";max-width:250px;svg path{stroke:", theme.colors.greyDarker, ";}&:disabled{background:", theme.colors.greyLighter, ";border-color:", theme.colors.grey, ";color:", theme.colors.greyDarker60, ";cursor:default;svg path{stroke:", theme.colors.grey, ";}}&:focus:not(:disabled){border-color:", theme.palette.primary.main, ";}&:hover:not(:disabled){border-color:", isOpen ? theme.palette.primary.main : theme.colors.greyDarker80, ";}" + ( true ? "" : 0), true ? "" : 0);
20629
+ return /*#__PURE__*/(0,react_namespaceObject.css)("height:40px;padding:11px 15px 9px 10px;font-size:14px;font-weight:500;color:", theme.colors.greyDarker, ";border:1px solid ", borderColor, ";border-radius:12px;background:", theme.colors.white, ";max-width:250px;svg path{stroke:", theme.colors.greyDarker, ";}&:disabled{background:", theme.colors.greyLighter, ";border-color:", theme.colors.grey, ";color:", theme.colors.greyDarker60, ";cursor:default;svg path{stroke:", theme.colors.grey, ";}}&:focus:not(:disabled){border-color:", theme.palette.primary.main, ";}&:hover:not(:disabled){border-color:", isOpen ? theme.palette.primary.main : theme.colors.greyDarker80, ";}" + ( true ? "" : 0), true ? "" : 0);
20630
20630
  };
20631
20631
  const DropdownToggleBase = /*#__PURE__*/base_default()("button", true ? {
20632
20632
  target: "er3kf7h0"
20633
- } : 0)("display:flex;flex-flow:row nowrap;align-items:center;justify-content:flex-start;gap:8px;position:relative;width:auto;height:44px;padding:0 14px;font:inherit;font-size:14px;font-weight:500;text-align:left;line-height:18px;cursor:pointer;outline:inherit;border-radius:12px;", ({
20633
+ } : 0)("display:flex;flex-flow:row nowrap;align-items:center;justify-content:space-between;gap:8px;position:relative;width:auto;height:44px;padding:0 14px;font:inherit;font-size:14px;font-weight:500;text-align:left;line-height:18px;cursor:pointer;outline:inherit;border-radius:12px;", ({
20634
20634
  isMultiple,
20635
20635
  isOpen,
20636
20636
  disabled,
@@ -28423,6 +28423,207 @@ const SearchBox = ({
28423
28423
 
28424
28424
 
28425
28425
 
28426
+ ;// ./src/components/FileAttachment/utils.ts
28427
+ const formatBytes = bytes => {
28428
+ if (bytes < 1024) return `${bytes} B`;
28429
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
28430
+ return `${Math.round(bytes / (1024 * 1024))} MB`;
28431
+ };
28432
+ const EXTENSION_ICON_MAP = {
28433
+ pdf: 'file-pdf',
28434
+ doc: 'file-word',
28435
+ docx: 'file-word',
28436
+ xls: 'excel-download',
28437
+ xlsx: 'excel-download'
28438
+ };
28439
+
28440
+ /** Returns the file-type icon for a known extension, or `null` when the design's grey Placeholder look should be used instead. */
28441
+ const getFileTypeIcon = fileName => {
28442
+ const extension = fileName.split('.').pop()?.toLowerCase();
28443
+ return extension && EXTENSION_ICON_MAP[extension] || null;
28444
+ };
28445
+ const IMAGE_FILE_NAME_REGEX = /\.(png|jpe?g|gif|webp|bmp|avif|svg)$/i;
28446
+
28447
+ /** Gates image-preview rendering strictly on the file name's extension, so a pdf/php/etc. is never rendered via `<img>` even if preview data is (incorrectly) supplied for it. */
28448
+ const isImageFile = fileName => IMAGE_FILE_NAME_REGEX.test(fileName);
28449
+ ;// ./src/components/FileAttachment/hooks/useFilePreviewUrl.ts
28450
+
28451
+
28452
+ /** Creates an object URL for a local File/Blob and revokes it on unmount or when `content` changes, avoiding a memory leak. */
28453
+ const useFilePreviewUrl = content => {
28454
+ const [url, setUrl] = (0,external_react_namespaceObject.useState)();
28455
+ (0,external_react_namespaceObject.useEffect)(() => {
28456
+ if (!content) {
28457
+ setUrl(undefined);
28458
+ return;
28459
+ }
28460
+ const objectUrl = URL.createObjectURL(content);
28461
+ setUrl(objectUrl);
28462
+ return () => URL.revokeObjectURL(objectUrl);
28463
+ }, [content]);
28464
+ return url;
28465
+ };
28466
+ ;// ./src/components/FileAttachment/styles.ts
28467
+ function FileAttachment_styles_EMOTION_STRINGIFIED_CSS_ERROR_() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
28468
+
28469
+ const paddingBySize = {
28470
+ large: 16,
28471
+ small: 12
28472
+ };
28473
+ const iconSizeBySize = {
28474
+ large: 40,
28475
+ small: 24
28476
+ };
28477
+ /** Placeholder glyph is inset ~20% on each side of its box in the design (40px box -> 24px glyph). */
28478
+ const placeholderIconSizeBySize = {
28479
+ large: 24,
28480
+ small: 14
28481
+ };
28482
+ const container = (theme, size) => /*#__PURE__*/(0,react_namespaceObject.css)("display:flex;align-items:center;gap:8px;width:100%;padding:", paddingBySize[size], "px;background:", theme.colors.white, ";border:1px solid ", theme.colors.grey, ";border-radius:12px;" + ( true ? "" : 0), true ? "" : 0);
28483
+ const disabledContainer = theme => /*#__PURE__*/(0,react_namespaceObject.css)("background:", theme.colors.greyLighter, ";" + ( true ? "" : 0), true ? "" : 0);
28484
+ const iconWrapper = size => /*#__PURE__*/(0,react_namespaceObject.css)("flex-shrink:0;display:flex;align-items:center;justify-content:center;width:", iconSizeBySize[size], "px;height:", iconSizeBySize[size], "px;" + ( true ? "" : 0), true ? "" : 0);
28485
+ const placeholderWrapper = theme => /*#__PURE__*/(0,react_namespaceObject.css)("background:", theme.palette.secondary.dark, ";border-radius:4px;" + ( true ? "" : 0), true ? "" : 0);
28486
+ const previewImage = true ? {
28487
+ name: "rys4i",
28488
+ styles: "width:100%;height:100%;border-radius:4px;object-fit:cover;display:block"
28489
+ } : 0;
28490
+ const textColumn = true ? {
28491
+ name: "1vjbjgs",
28492
+ styles: "flex:1;min-width:0;display:flex;flex-direction:column;gap:4px"
28493
+ } : 0;
28494
+ const title = theme => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:0.875rem;font-weight:600;color:", theme.colors.greyDarker, ";overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" + ( true ? "" : 0), true ? "" : 0);
28495
+ const description = theme => /*#__PURE__*/(0,react_namespaceObject.css)("display:flex;align-items:center;gap:6px;font-size:0.875rem;font-weight:500;color:", theme.colors.greyDarker60, ";white-space:nowrap;" + ( true ? "" : 0), true ? "" : 0);
28496
+ const dot = theme => /*#__PURE__*/(0,react_namespaceObject.css)("flex-shrink:0;width:4px;height:4px;border-radius:50%;background:", theme.colors.greyDarker60, ";" + ( true ? "" : 0), true ? "" : 0);
28497
+ const progressTrack = theme => /*#__PURE__*/(0,react_namespaceObject.css)("position:relative;flex-shrink:0;overflow:hidden;width:60px;height:4px;border-radius:4px;background:", theme.palette.secondary.light, ";" + ( true ? "" : 0), true ? "" : 0);
28498
+ const progressFill = theme => /*#__PURE__*/(0,react_namespaceObject.css)("position:absolute;top:0;left:0;height:4px;border-radius:4px;background:", theme.palette.primary.main, ";" + ( true ? "" : 0), true ? "" : 0);
28499
+ const deleteButton = theme => /*#__PURE__*/(0,react_namespaceObject.css)("flex-shrink:0;display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;background:transparent;border:none;cursor:pointer;color:", theme.colors.greyDarker60, ";transition:color 0.15s ease;&:hover{color:", theme.palette.error.main, ";}&:disabled{cursor:default;color:", theme.colors.grey, ";}" + ( true ? "" : 0), true ? "" : 0);
28500
+ ;// ./src/components/FileAttachment/FileAttachment.tsx
28501
+
28502
+
28503
+
28504
+
28505
+
28506
+
28507
+ /**
28508
+ * FileAttachment - Read-only row displaying a single attached/uploading file
28509
+ *
28510
+ * @example
28511
+ * ```tsx
28512
+ * <FileAttachment
28513
+ * file={{ name: 'Report.pdf', size: 20 * 1024 * 1024 }}
28514
+ * progress={50}
28515
+ * onRemove={() => handleRemove(file)}
28516
+ * />
28517
+ * ```
28518
+ *
28519
+ * @example
28520
+ * ```tsx
28521
+ * // Progress bar look instead of the default percentage text
28522
+ * <FileAttachment
28523
+ * file={{ name: 'Report.pdf', size: 20 * 1024 * 1024 }}
28524
+ * progress={50}
28525
+ * progressDisplay="bar"
28526
+ * onRemove={() => handleRemove(file)}
28527
+ * />
28528
+ * ```
28529
+ *
28530
+ * @example
28531
+ * ```tsx
28532
+ * // Omitting `progress` entirely shows just the file size, no progress copy —
28533
+ * // e.g. a file that's been selected but whose upload hasn't started yet.
28534
+ * <FileAttachment
28535
+ * file={{ name: 'Report.pdf', size: 20 * 1024 * 1024 }}
28536
+ * onRemove={() => handleRemove(file)}
28537
+ * />
28538
+ * ```
28539
+ */
28540
+
28541
+ const FileAttachment = ({
28542
+ file,
28543
+ size = 'large',
28544
+ progress,
28545
+ progressDisplay = 'text',
28546
+ uploadingText = 'Uploading',
28547
+ uploadedText = 'Uploaded Successfully',
28548
+ showDescription = true,
28549
+ icon,
28550
+ isDisabled = false,
28551
+ onRemove,
28552
+ className,
28553
+ css: cssProp
28554
+ }) => {
28555
+ const theme = (0,react_namespaceObject.useTheme)();
28556
+ const clampedProgress = progress === undefined ? undefined : Math.min(100, Math.max(0, progress));
28557
+ const isUploaded = clampedProgress !== undefined && clampedProgress >= 100;
28558
+ const iconName = icon ?? getFileTypeIcon(file.name);
28559
+ const canPreviewImage = !icon && isImageFile(file.name);
28560
+ const objectPreviewUrl = useFilePreviewUrl(canPreviewImage ? file.content : undefined);
28561
+ const previewSrc = canPreviewImage ? file.previewUrl ?? objectPreviewUrl : undefined;
28562
+ return (0,jsx_runtime_namespaceObject.jsxs)("div", {
28563
+ css: [container(theme, size), isDisabled && disabledContainer(theme), cssProp, true ? "" : 0, true ? "" : 0],
28564
+ className: className,
28565
+ children: [(0,jsx_runtime_namespaceObject.jsx)("div", {
28566
+ css: [iconWrapper(size), !iconName && !previewSrc && placeholderWrapper(theme), true ? "" : 0, true ? "" : 0],
28567
+ children: previewSrc ? (0,jsx_runtime_namespaceObject.jsx)("img", {
28568
+ src: previewSrc,
28569
+ alt: "",
28570
+ css: previewImage
28571
+ }) : (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
28572
+ name: iconName ?? 'picture',
28573
+ size: iconName ? iconSizeBySize[size] : placeholderIconSizeBySize[size],
28574
+ color: iconName ? theme.colors.greyDarker60 : theme.colors.white
28575
+ })
28576
+ }), (0,jsx_runtime_namespaceObject.jsxs)("div", {
28577
+ css: textColumn,
28578
+ children: [(0,jsx_runtime_namespaceObject.jsx)("span", {
28579
+ css: title(theme),
28580
+ children: file.name
28581
+ }), showDescription && (0,jsx_runtime_namespaceObject.jsxs)("div", {
28582
+ css: description(theme),
28583
+ children: [(0,jsx_runtime_namespaceObject.jsx)("span", {
28584
+ children: formatBytes(file.size)
28585
+ }), clampedProgress !== undefined && (0,jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
28586
+ children: [(0,jsx_runtime_namespaceObject.jsx)("span", {
28587
+ children: "|"
28588
+ }), progressDisplay === 'bar' ? (0,jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
28589
+ children: [(0,jsx_runtime_namespaceObject.jsx)("div", {
28590
+ css: progressTrack(theme),
28591
+ role: "progressbar",
28592
+ children: (0,jsx_runtime_namespaceObject.jsx)("div", {
28593
+ css: progressFill(theme),
28594
+ style: {
28595
+ width: `${clampedProgress}%`
28596
+ }
28597
+ })
28598
+ }), (0,jsx_runtime_namespaceObject.jsxs)("span", {
28599
+ children: [clampedProgress, "%"]
28600
+ })]
28601
+ }) : (0,jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
28602
+ children: [(0,jsx_runtime_namespaceObject.jsxs)("span", {
28603
+ children: [clampedProgress, "%"]
28604
+ }), (0,jsx_runtime_namespaceObject.jsx)("span", {
28605
+ css: dot(theme)
28606
+ }), (0,jsx_runtime_namespaceObject.jsx)("span", {
28607
+ children: isUploaded ? uploadedText : uploadingText
28608
+ })]
28609
+ })]
28610
+ })]
28611
+ })]
28612
+ }), onRemove && (0,jsx_runtime_namespaceObject.jsx)("button", {
28613
+ type: "button",
28614
+ css: deleteButton(theme),
28615
+ disabled: isDisabled,
28616
+ onClick: onRemove,
28617
+ "aria-label": `Remove ${file.name}`,
28618
+ children: (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
28619
+ name: "delete",
28620
+ size: 16,
28621
+ color: "currentColor"
28622
+ })
28623
+ })]
28624
+ });
28625
+ };
28626
+ /* harmony default export */ const FileAttachment_FileAttachment = (FileAttachment);
28426
28627
  ;// ./src/components/FileUpload/styles.ts
28427
28628
  function FileUpload_styles_EMOTION_STRINGIFIED_CSS_ERROR_() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
28428
28629
 
@@ -28461,71 +28662,6 @@ const filesList = true ? {
28461
28662
  styles: "display:flex;flex-direction:column;gap:8px;margin-top:12px"
28462
28663
  } : 0;
28463
28664
  const filesListTitle = theme => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:0.875rem;font-weight:500;color:", theme.colors.greyDarker, ";margin-bottom:4px;" + ( true ? "" : 0), true ? "" : 0);
28464
-
28465
- // ─── File item ────────────────────────────────────────────────────────────────
28466
-
28467
- const fileItem = theme => /*#__PURE__*/(0,react_namespaceObject.css)("display:flex;align-items:center;gap:12px;padding:12px 16px;background:", theme.colors.white, ";border:1px solid ", theme.colors.grey, ";border-radius:12px;" + ( true ? "" : 0), true ? "" : 0);
28468
- const fileIconWrapper = true ? {
28469
- name: "1xuq60z",
28470
- styles: "flex-shrink:0;display:flex;align-items:center;justify-content:center"
28471
- } : 0;
28472
- const fileInfo = true ? {
28473
- name: "1k33dov",
28474
- styles: "flex:1;display:flex;flex-direction:column;gap:2px;min-width:0"
28475
- } : 0;
28476
- const fileName = theme => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:0.875rem;font-weight:600;color:", theme.colors.greyDarker, ";overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" + ( true ? "" : 0), true ? "" : 0);
28477
- const fileSize = theme => /*#__PURE__*/(0,react_namespaceObject.css)("font-size:0.75rem;font-weight:400;color:", theme.colors.greyDarker60, ";" + ( true ? "" : 0), true ? "" : 0);
28478
- const deleteButton = theme => /*#__PURE__*/(0,react_namespaceObject.css)("flex-shrink:0;display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;background:transparent;border:none;border-radius:8px;cursor:pointer;color:", theme.colors.greyDarker60, ";transition:color 0.15s ease,background 0.15s ease;&:hover{color:", theme.palette.error.main, ";background:", theme.colors.greyLighter, ";}&:disabled{cursor:not-allowed;opacity:0.5;}" + ( true ? "" : 0), true ? "" : 0);
28479
- ;// ./src/components/FileUpload/FileUploadItem.tsx
28480
-
28481
-
28482
-
28483
-
28484
- const formatFileSize = bytes => {
28485
- if (bytes === 0) return '0 B';
28486
- if (bytes < 1024) return `${bytes} B`;
28487
- if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
28488
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
28489
- };
28490
- const FileUploadItem = ({
28491
- file,
28492
- onRemove,
28493
- disabled
28494
- }) => {
28495
- const theme = (0,react_namespaceObject.useTheme)();
28496
- return (0,jsx_runtime_namespaceObject.jsxs)("div", {
28497
- css: fileItem(theme),
28498
- children: [(0,jsx_runtime_namespaceObject.jsx)("div", {
28499
- css: fileIconWrapper,
28500
- children: (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
28501
- name: "file-pdf",
28502
- size: 32,
28503
- color: theme.colors.greyDarker60
28504
- })
28505
- }), (0,jsx_runtime_namespaceObject.jsxs)("div", {
28506
- css: fileInfo,
28507
- children: [(0,jsx_runtime_namespaceObject.jsx)("span", {
28508
- css: fileName(theme),
28509
- children: file.name
28510
- }), (0,jsx_runtime_namespaceObject.jsx)("span", {
28511
- css: fileSize(theme),
28512
- children: formatFileSize(file.size)
28513
- })]
28514
- }), (0,jsx_runtime_namespaceObject.jsx)("button", {
28515
- css: deleteButton(theme),
28516
- type: "button",
28517
- disabled: disabled,
28518
- onClick: () => onRemove(file),
28519
- "aria-label": `Remove ${file.name}`,
28520
- children: (0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
28521
- name: "bin",
28522
- size: 16,
28523
- color: "currentColor"
28524
- })
28525
- })]
28526
- });
28527
- };
28528
- /* harmony default export */ const FileUpload_FileUploadItem = (FileUploadItem);
28529
28665
  ;// ./src/components/FileUpload/FileUpload.tsx
28530
28666
 
28531
28667
 
@@ -28536,14 +28672,23 @@ const FileUploadItem = ({
28536
28672
 
28537
28673
 
28538
28674
 
28675
+
28539
28676
  const normalizeValue = value => {
28540
28677
  if (!value) return [];
28541
28678
  return Array.isArray(value) ? value : [value];
28542
28679
  };
28543
- const formatBytes = bytes => {
28544
- if (bytes < 1024) return `${bytes} B`;
28545
- if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
28546
- return `${Math.round(bytes / (1024 * 1024))} MB`;
28680
+
28681
+ /**
28682
+ * Matches by the Nth occurrence of a name rather than the first, so files
28683
+ * that share a name (e.g. two "photo.png" from different folders) each get
28684
+ * their own progress entry instead of colliding on the same one.
28685
+ */
28686
+ const getFileProgress = (file, index, files, uploadProgress) => {
28687
+ if (uploadProgress === undefined) return undefined;
28688
+ if (typeof uploadProgress === 'number') return uploadProgress;
28689
+ const occurrence = files.slice(0, index + 1).filter(f => f.name === file.name).length;
28690
+ const matches = uploadProgress.filter(entry => entry.name === file.name);
28691
+ return matches[occurrence - 1]?.progress;
28547
28692
  };
28548
28693
 
28549
28694
  /**
@@ -28551,7 +28696,8 @@ const formatBytes = bytes => {
28551
28696
  *
28552
28697
  * Supports single and multi-file selection with built-in validation for
28553
28698
  * file formats and size. In multi-file mode, selected files are listed
28554
- * below the input with individual remove controls.
28699
+ * below the input with individual remove controls. Single-file mode can opt
28700
+ * into the same list treatment via `showFileAttachment`.
28555
28701
  *
28556
28702
  * @example
28557
28703
  * ```tsx
@@ -28579,6 +28725,36 @@ const formatBytes = bytes => {
28579
28725
  * onChange={setFiles}
28580
28726
  * />
28581
28727
  * ```
28728
+ *
28729
+ * @example
28730
+ * ```tsx
28731
+ * // Multi-file with per-file upload progress, driven by the consumer's own
28732
+ * // upload requests — FileUpload only handles local selection, so it has no
28733
+ * // progress data of its own.
28734
+ * <FileUpload
28735
+ * isMultiFile
28736
+ * value={files}
28737
+ * onChange={setFiles}
28738
+ * uploadProgress={[
28739
+ * { name: 'report.pdf', progress: 50 },
28740
+ * { name: 'photo.png', progress: 100 },
28741
+ * ]}
28742
+ * />
28743
+ * ```
28744
+ *
28745
+ * @example
28746
+ * ```tsx
28747
+ * // Single file, shown as a FileAttachment card below the input (icon,
28748
+ * // size, delete button, image preview) instead of inline text next to the
28749
+ * // button. `uploadProgress` here can just be a single number, since there's
28750
+ * // only ever one file.
28751
+ * <FileUpload
28752
+ * showFileAttachment
28753
+ * value={file}
28754
+ * onChange={(files) => setFile(files[0])}
28755
+ * uploadProgress={70}
28756
+ * />
28757
+ * ```
28582
28758
  */
28583
28759
  const FileUpload = ({
28584
28760
  label,
@@ -28595,6 +28771,8 @@ const FileUpload = ({
28595
28771
  maxFiles,
28596
28772
  withDropArea = false,
28597
28773
  uploadedSectionTitle,
28774
+ uploadProgress,
28775
+ showFileAttachment = false,
28598
28776
  value,
28599
28777
  onChange,
28600
28778
  onFileRejected
@@ -28657,8 +28835,8 @@ const FileUpload = ({
28657
28835
  };
28658
28836
  const acceptAttr = allowedFormats?.map(f => `.${f}`).join(',');
28659
28837
  const hasError = !!error;
28660
- const inlineFileName = !isMultiFile && files[0]?.name;
28661
- const showUploadedFiles = isMultiFile && files.length > 0;
28838
+ const inlineFileName = !isMultiFile && !showFileAttachment && files[0]?.name;
28839
+ const attachmentFiles = isMultiFile || showFileAttachment && !withDropArea ? files : [];
28662
28840
  return (0,jsx_runtime_namespaceObject.jsxs)("div", {
28663
28841
  css: [wrapper, css, true ? "" : 0, true ? "" : 0],
28664
28842
  className: className,
@@ -28684,7 +28862,7 @@ const FileUpload = ({
28684
28862
  onKeyDown: e => e.key === 'Enter' && !files[0] && handleChooseClick(),
28685
28863
  children: !isMultiFile && files[0] ? (0,jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
28686
28864
  children: [(0,jsx_runtime_namespaceObject.jsx)(Icon_Icon, {
28687
- name: "file-pdf",
28865
+ name: getFileTypeIcon(files[0].name) ?? 'picture',
28688
28866
  size: 36,
28689
28867
  color: theme.colors.greyDarker60
28690
28868
  }), (0,jsx_runtime_namespaceObject.jsx)("span", {
@@ -28741,15 +28919,20 @@ const FileUpload = ({
28741
28919
  marginTop: 12
28742
28920
  } : undefined,
28743
28921
  children: error || helperText
28744
- }), showUploadedFiles && (0,jsx_runtime_namespaceObject.jsxs)("div", {
28922
+ }), attachmentFiles.length > 0 && (0,jsx_runtime_namespaceObject.jsxs)("div", {
28745
28923
  css: filesList,
28746
28924
  children: [uploadedSectionTitle && (0,jsx_runtime_namespaceObject.jsx)("span", {
28747
28925
  css: filesListTitle(theme),
28748
28926
  children: uploadedSectionTitle
28749
- }), files.map((file, index) => (0,jsx_runtime_namespaceObject.jsx)(FileUpload_FileUploadItem, {
28750
- file: file,
28751
- onRemove: handleRemove,
28752
- disabled: disabled
28927
+ }), attachmentFiles.map((file, index) => (0,jsx_runtime_namespaceObject.jsx)(FileAttachment_FileAttachment, {
28928
+ file: {
28929
+ name: file.name,
28930
+ size: file.size,
28931
+ content: file
28932
+ },
28933
+ progress: getFileProgress(file, index, files, uploadProgress),
28934
+ onRemove: () => handleRemove(file),
28935
+ isDisabled: disabled
28753
28936
  }, `${file.name}-${index}`))]
28754
28937
  })]
28755
28938
  });
@@ -53986,7 +54169,7 @@ function History_styles_EMOTION_STRINGIFIED_CSS_ERROR_() { return "You have trie
53986
54169
 
53987
54170
  const FIRST_LINE_TOP_PADDING = 2;
53988
54171
  const FIRST_LINE_HEIGHT = 20;
53989
- const container = true ? {
54172
+ const styles_container = true ? {
53990
54173
  name: "1fttcpj",
53991
54174
  styles: "display:flex;flex-direction:column"
53992
54175
  } : 0;
@@ -54053,7 +54236,7 @@ const History_History = ({
54053
54236
  const circleTopOffset = Math.max(0, FIRST_LINE_TOP_PADDING + (FIRST_LINE_HEIGHT - circleSize) / 2);
54054
54237
  return (0,jsx_runtime_namespaceObject.jsx)("div", {
54055
54238
  "data-testid": "history",
54056
- css: container,
54239
+ css: styles_container,
54057
54240
  style: sx,
54058
54241
  children: items.map((item, index) => {
54059
54242
  const isLast = index === items.length - 1;
@@ -54088,11 +54271,11 @@ const History_History = ({
54088
54271
  function Pagination_styles_EMOTION_STRINGIFIED_CSS_ERROR_() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
54089
54272
 
54090
54273
 
54091
- /** Square 26x26 cell shared by page/selected/arrow buttons (design "Number" atom). */
54274
+ /** 26px-tall cell shared by page/selected/arrow buttons (design "Number" atom); width grows past 26 to fit multi-digit page numbers instead of clipping/overlapping. */
54092
54275
  const baseBtnStyles = {
54093
- width: 26,
54276
+ minWidth: 26,
54094
54277
  height: 26,
54095
- padding: 0,
54278
+ padding: '0 4px',
54096
54279
  borderRadius: 6,
54097
54280
  justifyContent: 'center',
54098
54281
  fontSize: 14,
@@ -54319,8 +54502,8 @@ const PageButton = ({
54319
54502
  * - Proper focus management
54320
54503
  */
54321
54504
  var PaginationButtons_ref = true ? {
54322
- name: "1lcc3va",
54323
- styles: "display:flex;align-items:center;gap:0"
54505
+ name: "1q7mb2q",
54506
+ styles: "display:flex;align-items:center;gap:2px"
54324
54507
  } : 0;
54325
54508
  const PaginationButtons = ({
54326
54509
  range,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssa-ui-kit/core",
3
- "version": "3.16.3",
3
+ "version": "3.17.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -39,8 +39,8 @@
39
39
  "luxon": "3.5.0",
40
40
  "plotly.js": "3.0.0",
41
41
  "react-plotly.js": "2.6.0",
42
- "@ssa-ui-kit/hooks": "^3.16.3",
43
- "@ssa-ui-kit/utils": "^3.16.3"
42
+ "@ssa-ui-kit/hooks": "^3.17.0",
43
+ "@ssa-ui-kit/utils": "^3.17.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@emotion/css": "11.13.5",
@@ -1,7 +0,0 @@
1
- interface FileUploadItemProps {
2
- file: File;
3
- onRemove: (file: File) => void;
4
- disabled?: boolean;
5
- }
6
- declare const FileUploadItem: ({ file, onRemove, disabled }: FileUploadItemProps) => import("@emotion/react/jsx-runtime").JSX.Element;
7
- export default FileUploadItem;