• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alma-oss / spirit-design-system / 25500013980

07 May 2026 01:50PM UTC coverage: 84.089% (-0.3%) from 84.395%
25500013980

Pull #2631

github

adamkudrna
feat(web,web-react)!: drop `Tag` appearance feature flag #DS-2456

BREAKING CHANGE: The new `Tag` appearance (`inline-flex` layout with explicit
height and inside spacing) is now default. Delete the `$enable-v5-tag-appearance`
Sass variable and the `spirit-feature-enable-v5-tag-appearance` CSS class from
your project — they have no effect. See the Tag: Appearance Feature Flag Removed
sections in the web and web-react package Migration Guides to version 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pull Request #2631: feat(web,web-react)!: drop `Tag` appearance feature flag #DS-2456

2274 of 3120 branches covered (72.88%)

Branch coverage included in aggregate %.

7149 of 8086 relevant lines covered (88.41%)

197.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.53
/packages/web-react/src/components/FileUploader/useFileUploaderStyleProps.ts
1
import classNames from 'classnames';
189✔
2
import { type CSSProperties } from 'react';
3
import { type ObjectFit } from '../../constants';
4
import { useClassNamePrefix } from '../../hooks';
189✔
5
import { type FileMetadata, type FileUploaderQueueLimitBehaviorType, type Validation } from '../../types';
6
import { FileUploaderCropCSS, IMAGE_DIMENSION } from './constants';
189✔
7

8
export interface FileUploaderStyleProps extends Validation {
9
  imageObjectFit?: (typeof ObjectFit)[keyof typeof ObjectFit];
10
  isDisabled?: boolean;
11
  isDisabledByQueueLimitBehavior?: boolean;
12
  isDragAndDropSupported?: boolean;
13
  isDragging?: boolean;
14
  isDropZoneHidden?: boolean;
15
  isLabelHidden?: boolean;
16
  meta?: FileMetadata;
17
  queueLimitBehavior?: FileUploaderQueueLimitBehaviorType;
18
}
19

20
type ImageCropCSS = {
21
  [FileUploaderCropCSS.TOP]?: string;
22
  [FileUploaderCropCSS.LEFT]?: string;
23
  [FileUploaderCropCSS.WIDTH]?: string;
24
  [FileUploaderCropCSS.HEIGHT]?: string;
25
} & CSSProperties;
26

27
type ImageObjectFit = {
28
  '--file-uploader-attachment-image-object-fit': string;
29
};
30

31
type ImageCropMeta = {
32
  x: number;
33
  y: number;
34
  cropWidth: number;
35
  cropHeight: number;
36
  originalWidth: number;
37
  originalHeight: number;
38
};
39

40
export interface FileUploaderStyleReturn {
41
  /** className props */
42
  classProps: {
43
    root: string;
44
    input: {
45
      root: string;
46
      input: string;
47
      dropLabel: string;
48
      link: string;
49
      dropZone: {
50
        root: string;
51
        label: string;
52
      };
53
    };
54
    list: string;
55
    attachment: {
56
      root: string;
57
      button: string;
58
      name: string;
59
      image: string;
60
      slot: string;
61
    };
62
    imageCropStyles?: ImageCropCSS;
63
    attachmentStyles?: ImageObjectFit;
64
  };
65
}
66

67
export const useFileUploaderStyleProps = (props?: FileUploaderStyleProps): FileUploaderStyleReturn => {
189✔
68
  const fileUploaderClass = useClassNamePrefix('FileUploader');
68✔
69
  const fileUploaderHasDragAndDropClass = 'has-drag-and-drop';
68✔
70
  const fileUploaderInputClass = `${fileUploaderClass}Input`;
68✔
71
  const fileUploaderInputDisabledClass = `${fileUploaderInputClass}--disabled`;
68✔
72
  const fileUploaderInputValidationClass = `${fileUploaderInputClass}--${props?.validationState}`;
68✔
73
  const fileUploaderInputDraggingClass = 'is-dragging';
68✔
74
  const fileUploaderInputDropLabelClass = `${fileUploaderInputClass}__dragAndDropLabel`;
68✔
75
  const fileUploaderInputDropZoneClass = `${fileUploaderInputClass}__dropZone`;
68✔
76
  const fileUploaderInputDropZoneLabelClass = `${fileUploaderInputDropZoneClass}Label`;
68✔
77
  const fileUploaderInputHiddenClass = 'd-none';
68✔
78
  const fileUploaderInputInputClass = `${fileUploaderInputClass}__input`;
68✔
79
  const fileUploaderInputLinkClass = `${fileUploaderInputClass}__link`;
68✔
80
  const fileUploaderInputLinkUtilityClasses = ['link-primary', 'link-underlined'];
68✔
81
  const fileUploaderListClass = `${fileUploaderClass}List`;
68✔
82
  const fileUploaderAttachmentClass = `${fileUploaderClass}Attachment`;
68✔
83
  const fileUploaderAttachmentNameClass = `${fileUploaderAttachmentClass}__name`;
68✔
84
  const fileUploaderAttachmentButtonClass = `${fileUploaderAttachmentClass}__action`;
68✔
85
  const fileUploaderAttachmentImageClass = `${fileUploaderAttachmentClass}__image`;
68✔
86
  const fileUploaderAttachmentSlotClass = `${fileUploaderAttachmentClass}__slot`;
68✔
87

88
  const { meta, imageObjectFit } = props || {};
68✔
89
  let imageCropCSS: ImageCropCSS | undefined;
90
  let imageObjectFitCSS: ImageObjectFit | undefined;
91
  const hasCoordsInMeta =
92
    meta != null &&
68✔
93
    ['x', 'y', 'cropWidth', 'cropHeight', 'originalWidth', 'originalHeight'].every((coord) => meta[coord] != null);
6✔
94

95
  if (hasCoordsInMeta) {
68✔
96
    const { x, y, cropWidth, cropHeight, originalWidth, originalHeight } = meta as ImageCropMeta;
1✔
97
    const previewHeight = IMAGE_DIMENSION;
1✔
98
    let scale;
99
    if (cropHeight > cropWidth) {
1!
100
      // scale for portrait images
101
      scale = previewHeight / cropWidth;
×
102
    } else {
103
      // scale for landscape images
104
      scale = previewHeight / cropHeight;
1✔
105
    }
106

107
    const cropX = Math.round(x * scale);
1✔
108
    const cropY = Math.round(y * scale);
1✔
109
    const imageWidth = Math.round(originalWidth * scale);
1✔
110
    const imageHeight = Math.round(originalHeight * scale);
1✔
111

112
    imageCropCSS = {
1✔
113
      [FileUploaderCropCSS.TOP]: `-${cropY}px`,
114
      [FileUploaderCropCSS.LEFT]: `-${cropX}px`,
115
      [FileUploaderCropCSS.WIDTH]: `${imageWidth}px`,
116
      [FileUploaderCropCSS.HEIGHT]: `${imageHeight}px`,
117
    };
118
  }
119

120
  if (imageObjectFit) {
68!
121
    imageObjectFitCSS = {
×
122
      '--file-uploader-attachment-image-object-fit': imageObjectFit,
123
    };
124
  }
125

126
  return {
68✔
127
    classProps: {
128
      root: fileUploaderClass,
129
      input: {
130
        root: classNames(fileUploaderInputClass, {
131
          [fileUploaderHasDragAndDropClass]: props?.isDragAndDropSupported,
132
          [fileUploaderInputDisabledClass]:
133
            props?.isDisabled || (props?.isDisabledByQueueLimitBehavior && props?.queueLimitBehavior === 'disable'),
130✔
134
          [fileUploaderInputDraggingClass]: props?.isDragging,
135
          [fileUploaderInputHiddenClass]: props?.isDropZoneHidden && props.queueLimitBehavior === 'hide',
69✔
136
          [fileUploaderInputValidationClass]: props?.validationState,
137
        }),
138
        input: fileUploaderInputInputClass,
139
        dropLabel: fileUploaderInputDropLabelClass,
140
        link: classNames(fileUploaderInputLinkClass, ...fileUploaderInputLinkUtilityClasses),
141
        dropZone: {
142
          root: fileUploaderInputDropZoneClass,
143
          label: fileUploaderInputDropZoneLabelClass,
144
        },
145
      },
146
      list: fileUploaderListClass,
147
      attachment: {
148
        root: fileUploaderAttachmentClass,
149
        button: fileUploaderAttachmentButtonClass,
150
        name: fileUploaderAttachmentNameClass,
151
        image: fileUploaderAttachmentImageClass,
152
        slot: fileUploaderAttachmentSlotClass,
153
      },
154
      ...(hasCoordsInMeta && { imageCropStyles: imageCropCSS }),
69✔
155
      ...(imageObjectFit && { attachmentStyles: imageObjectFitCSS }),
68!
156
    },
157
  };
158
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc