@bitrise/bitkit 12.42.1 → 12.43.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitrise/bitkit",
3
3
  "description": "Bitrise React component library",
4
- "version": "12.42.1",
4
+ "version": "12.43.0",
5
5
  "repository": "git@github.com:bitrise-io/bitkit.git",
6
6
  "main": "src/index.ts",
7
7
  "license": "UNLICENSED",
@@ -0,0 +1,75 @@
1
+ import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
2
+
3
+ const { defineMultiStyleConfig } = createMultiStyleConfigHelpers(['container', 'label', 'input']);
4
+
5
+ const getContainerType = (hasSelectedFiles?: boolean) => {
6
+ if (hasSelectedFiles) {
7
+ return {
8
+ display: 'flex',
9
+ alignItems: 'center',
10
+ justifyContent: 'space-between',
11
+ borderRadius: '8',
12
+ backgroundColor: 'neutral.100',
13
+ paddingX: '24',
14
+ paddingY: '16',
15
+ border: '1px solid',
16
+ borderColor: 'neutral.90',
17
+ };
18
+ }
19
+ return {
20
+ borderRadius: '8',
21
+ backgroundColor: 'neutral.95',
22
+ padding: '8',
23
+ cursor: 'pointer',
24
+ _hover: {
25
+ backgroundColor: 'purple.93',
26
+ },
27
+ _active: {
28
+ backgroundColor: 'purple.93',
29
+ },
30
+ _disabled: {
31
+ _active: {
32
+ backgroundColor: 'neutral.95',
33
+ },
34
+ _hover: {
35
+ backgroundColor: 'neutral.95',
36
+ },
37
+ },
38
+ };
39
+ };
40
+
41
+ const FileInputTheme = defineMultiStyleConfig({
42
+ baseStyle: ({ hasSelectedFiles }) => ({
43
+ container: getContainerType(hasSelectedFiles),
44
+ label: {
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ gap: '8',
49
+ padding: '24',
50
+ border: '1px dashed',
51
+ borderColor: 'purple.50',
52
+ borderRadius: '4',
53
+ color: 'purple.50',
54
+ cursor: 'pointer',
55
+ _active: {
56
+ color: 'purple.10',
57
+ borderColor: 'purple.10',
58
+ },
59
+ _disabled: {
60
+ borderColor: 'neutral.60',
61
+ color: 'neutral.60',
62
+ cursor: 'not-allowed',
63
+ },
64
+ _invalid: {
65
+ borderColor: 'red.50',
66
+ color: 'red.50',
67
+ },
68
+ },
69
+ input: {
70
+ display: 'none',
71
+ },
72
+ }),
73
+ });
74
+
75
+ export default FileInputTheme;
@@ -0,0 +1,150 @@
1
+ import { ChangeEvent, DragEvent, useRef, useState } from 'react';
2
+ import {
3
+ FormControl,
4
+ FormControlProps,
5
+ FormErrorMessage,
6
+ FormHelperText,
7
+ forwardRef,
8
+ useId,
9
+ useMultiStyleConfig,
10
+ } from '@chakra-ui/react';
11
+ import Box from '../../Box/Box';
12
+ import Button from '../../Button/Button';
13
+ import Icon from '../../Icon/Icon';
14
+ import Text from '../../Text/Text';
15
+
16
+ export interface FileInputProps extends Omit<FormControlProps, 'onChange'> {
17
+ accept?: HTMLInputElement['accept'];
18
+ disabledLabel?: string;
19
+ errorText?: string;
20
+ helperText?: string;
21
+ name?: string;
22
+ onChange: (files: FileList | null) => void;
23
+ }
24
+
25
+ const FileInput = forwardRef<FileInputProps, 'div'>((props, ref) => {
26
+ const {
27
+ accept,
28
+ disabledLabel,
29
+ errorText,
30
+ helperText,
31
+ isDisabled,
32
+ isInvalid,
33
+ isRequired,
34
+ label,
35
+ name,
36
+ onChange,
37
+ ...rest
38
+ } = props;
39
+
40
+ const id = useId();
41
+ const inputRef = useRef<HTMLInputElement>(null);
42
+
43
+ const [selectedFileNames, setSelectedFileNames] = useState<string[]>([]);
44
+ const [internalErrorMessage, setInternalErrorMessage] = useState('');
45
+
46
+ const style = useMultiStyleConfig('FileInput', { hasSelectedFiles: selectedFileNames.length > 0 });
47
+
48
+ const handleFiles = async (files: FileList | null) => {
49
+ setSelectedFileNames(files ? Array.from(files).map((file) => file.name) : []);
50
+ onChange(files);
51
+ };
52
+
53
+ const onFileInputChange = (event: ChangeEvent<HTMLInputElement>) => {
54
+ if (event.target.files) {
55
+ handleFiles(event.target.files);
56
+ }
57
+ };
58
+
59
+ const onDragOver = (event: DragEvent<HTMLElement>) => {
60
+ event.preventDefault();
61
+ };
62
+
63
+ const onDrop = (event: DragEvent<HTMLElement>) => {
64
+ event.preventDefault();
65
+ if (isDisabled) {
66
+ return;
67
+ }
68
+ if (accept) {
69
+ const acceptedFormats = accept.split(', ');
70
+ if (!acceptedFormats.includes(event.dataTransfer.files[0].type)) {
71
+ setInternalErrorMessage(`Invalid format. You can upload only ${accept} file.`);
72
+ return;
73
+ }
74
+ }
75
+ if (inputRef.current) {
76
+ inputRef.current.files = event.dataTransfer.files;
77
+ }
78
+
79
+ handleFiles(event.dataTransfer.files);
80
+ };
81
+
82
+ const onRemoveClick = () => {
83
+ if (inputRef.current) {
84
+ inputRef.current.value = '';
85
+ }
86
+ onChange(null);
87
+ setSelectedFileNames([]);
88
+ };
89
+
90
+ const isInputInvalid = isInvalid || !!internalErrorMessage;
91
+
92
+ return (
93
+ <FormControl {...rest} isDisabled={isDisabled} isInvalid={isInputInvalid} ref={ref}>
94
+ <Box __css={style.container} data-disabled={isDisabled || undefined}>
95
+ {selectedFileNames.length > 0 ? (
96
+ <>
97
+ <div>
98
+ <Text as="h6" size="2" fontWeight="bold">
99
+ Selected file
100
+ </Text>
101
+ <Text as="span">{selectedFileNames[0]}</Text>
102
+ </div>
103
+ <Button leftIconName="MinusRemove" size="small" variant="secondary" onClick={onRemoveClick}>
104
+ Remove
105
+ </Button>
106
+ </>
107
+ ) : (
108
+ <Box
109
+ as="label"
110
+ aria-disabled={isDisabled || undefined}
111
+ data-invalid={isInputInvalid || undefined}
112
+ htmlFor={id}
113
+ onDragOver={onDragOver}
114
+ onDrop={onDrop}
115
+ __css={style.label}
116
+ >
117
+ <Icon name="Upload" size="24" />
118
+ {isDisabled && disabledLabel ? disabledLabel : label}
119
+ </Box>
120
+ )}
121
+ </Box>
122
+ <FormErrorMessage as="p" marginBlockStart="8">
123
+ {errorText || internalErrorMessage}
124
+ </FormErrorMessage>
125
+ {helperText && (
126
+ <FormHelperText as="p" marginBlockStart={isInvalid ? '4' : '8'}>
127
+ {helperText}
128
+ </FormHelperText>
129
+ )}
130
+ <Box
131
+ as="input"
132
+ aria-hidden="true"
133
+ accept={accept}
134
+ disabled={isDisabled}
135
+ id={id}
136
+ name={name}
137
+ type="file"
138
+ onChange={onFileInputChange}
139
+ ref={inputRef}
140
+ __css={style.input}
141
+ />
142
+ </FormControl>
143
+ );
144
+ });
145
+
146
+ FileInput.defaultProps = {
147
+ label: 'Drag and drop file here or click to select',
148
+ };
149
+
150
+ export default FileInput;
package/src/index.ts CHANGED
@@ -301,3 +301,6 @@ export { default as TableIconButton } from './Components/Table/TableIconButton';
301
301
 
302
302
  export type { ExpandableCardProps } from './Components/ExpandableCard/ExpandableCard';
303
303
  export { default as ExpandableCard } from './Components/ExpandableCard/ExpandableCard';
304
+
305
+ export type { FileInputProps } from './Components/Form/FileInput/FileInput';
306
+ export { default as FileInput } from './Components/Form/FileInput/FileInput';
package/src/theme.ts CHANGED
@@ -40,6 +40,7 @@ import Note from './Components/Note/Note.theme';
40
40
  import CodeBlock from './Components/CodeBlock/CodeBlock.theme';
41
41
  import DefinitionTooltip from './Components/DefinitionTooltip/DefinitionTooltip.theme';
42
42
  import ExpandableCard from './Components/ExpandableCard/ExpandableCard.theme';
43
+ import FileInput from './Components/Form/FileInput/FileInput.theme';
43
44
 
44
45
  import breakpoints from './Foundations/Breakpoints/Breakpoints';
45
46
  import colors from './Foundations/Colors/Colors';
@@ -127,6 +128,7 @@ const theme = {
127
128
  CodeBlock,
128
129
  DefinitionTooltip,
129
130
  ExpandableCard,
131
+ FileInput,
130
132
  },
131
133
  };
132
134