@idealyst/files 1.2.96

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 (42) hide show
  1. package/package.json +94 -0
  2. package/src/components/DropZone.native.tsx +96 -0
  3. package/src/components/DropZone.styles.tsx +99 -0
  4. package/src/components/DropZone.web.tsx +178 -0
  5. package/src/components/FilePickerButton.native.tsx +82 -0
  6. package/src/components/FilePickerButton.styles.tsx +112 -0
  7. package/src/components/FilePickerButton.web.tsx +84 -0
  8. package/src/components/UploadProgress.native.tsx +203 -0
  9. package/src/components/UploadProgress.styles.tsx +90 -0
  10. package/src/components/UploadProgress.web.tsx +201 -0
  11. package/src/components/index.native.ts +8 -0
  12. package/src/components/index.ts +6 -0
  13. package/src/components/index.web.ts +8 -0
  14. package/src/constants.ts +336 -0
  15. package/src/examples/index.ts +181 -0
  16. package/src/hooks/createUseFilePickerHook.ts +169 -0
  17. package/src/hooks/createUseFileUploadHook.ts +173 -0
  18. package/src/hooks/index.native.ts +12 -0
  19. package/src/hooks/index.ts +12 -0
  20. package/src/hooks/index.web.ts +12 -0
  21. package/src/index.native.ts +142 -0
  22. package/src/index.ts +139 -0
  23. package/src/index.web.ts +142 -0
  24. package/src/permissions/index.native.ts +8 -0
  25. package/src/permissions/index.ts +8 -0
  26. package/src/permissions/index.web.ts +8 -0
  27. package/src/permissions/permissions.native.ts +177 -0
  28. package/src/permissions/permissions.web.ts +96 -0
  29. package/src/picker/FilePicker.native.ts +407 -0
  30. package/src/picker/FilePicker.web.ts +366 -0
  31. package/src/picker/index.native.ts +2 -0
  32. package/src/picker/index.ts +2 -0
  33. package/src/picker/index.web.ts +2 -0
  34. package/src/types.ts +990 -0
  35. package/src/uploader/ChunkedUploader.ts +312 -0
  36. package/src/uploader/FileUploader.native.ts +435 -0
  37. package/src/uploader/FileUploader.web.ts +350 -0
  38. package/src/uploader/UploadQueue.ts +519 -0
  39. package/src/uploader/index.native.ts +4 -0
  40. package/src/uploader/index.ts +4 -0
  41. package/src/uploader/index.web.ts +4 -0
  42. package/src/utils.ts +586 -0
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@idealyst/files",
3
+ "version": "1.2.96",
4
+ "description": "Cross-platform file picker, upload, and local file management for React and React Native",
5
+ "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/files#readme",
6
+ "readme": "README.md",
7
+ "main": "src/index.ts",
8
+ "module": "src/index.ts",
9
+ "types": "src/index.ts",
10
+ "react-native": "src/index.native.ts",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/IdealystIO/idealyst-framework.git",
14
+ "directory": "packages/files"
15
+ },
16
+ "author": "Idealyst <hello@idealyst.io>",
17
+ "license": "MIT",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "react-native": "./src/index.native.ts",
24
+ "browser": "./src/index.web.ts",
25
+ "import": "./src/index.ts",
26
+ "require": "./src/index.ts",
27
+ "types": "./src/index.ts"
28
+ },
29
+ "./examples": {
30
+ "import": "./src/examples/index.ts",
31
+ "require": "./src/examples/index.ts",
32
+ "types": "./src/examples/index.ts"
33
+ }
34
+ },
35
+ "scripts": {
36
+ "prepublishOnly": "echo 'Publishing TypeScript source directly'",
37
+ "publish:npm": "npm publish"
38
+ },
39
+ "peerDependencies": {
40
+ "@idealyst/components": "^1.2.96",
41
+ "@idealyst/theme": "^1.2.96",
42
+ "react": ">=16.8.0",
43
+ "react-native": ">=0.60.0",
44
+ "react-native-document-picker": ">=9.0.0",
45
+ "react-native-image-picker": ">=7.0.0",
46
+ "react-native-blob-util": ">=0.19.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "@idealyst/components": {
50
+ "optional": true
51
+ },
52
+ "@idealyst/theme": {
53
+ "optional": true
54
+ },
55
+ "react-native": {
56
+ "optional": true
57
+ },
58
+ "react-native-document-picker": {
59
+ "optional": true
60
+ },
61
+ "react-native-image-picker": {
62
+ "optional": true
63
+ },
64
+ "react-native-blob-util": {
65
+ "optional": true
66
+ }
67
+ },
68
+ "devDependencies": {
69
+ "@idealyst/components": "^1.2.96",
70
+ "@idealyst/theme": "^1.2.96",
71
+ "@types/react": "^19.1.0",
72
+ "@types/react-native": "^0.73.0",
73
+ "react": "^19.1.0",
74
+ "react-native": "^0.80.0",
75
+ "react-native-unistyles": "^3.0.10",
76
+ "typescript": "^5.0.0"
77
+ },
78
+ "files": [
79
+ "src",
80
+ "README.md"
81
+ ],
82
+ "keywords": [
83
+ "react",
84
+ "react-native",
85
+ "file",
86
+ "upload",
87
+ "picker",
88
+ "document",
89
+ "image",
90
+ "cross-platform",
91
+ "chunked-upload",
92
+ "background-upload"
93
+ ]
94
+ }
@@ -0,0 +1,96 @@
1
+ import React, { useCallback } from 'react';
2
+ import { View, Text, TouchableOpacity } from 'react-native';
3
+ import type { DropZoneProps, DropZoneState } from '../types';
4
+ import { dropZoneStyles } from './DropZone.styles';
5
+ import { useFilePicker } from '../hooks';
6
+
7
+ /**
8
+ * DropZone - A touch area for file selection (Native).
9
+ *
10
+ * Note: Drag and drop is not supported on native. This component acts as a
11
+ * styled button that opens the file picker when pressed.
12
+ */
13
+ export const DropZone: React.FC<DropZoneProps> = (props) => {
14
+ const {
15
+ onDrop,
16
+ onReject,
17
+ config,
18
+ children,
19
+ disabled = false,
20
+ style,
21
+ testID,
22
+ } = props;
23
+
24
+ const { pick, isPicking } = useFilePicker({ config });
25
+
26
+ const state: DropZoneState = {
27
+ isDragActive: false,
28
+ isDragReject: false,
29
+ isProcessing: isPicking,
30
+ };
31
+
32
+ // Handle press to open file picker
33
+ const handlePress = useCallback(async () => {
34
+ if (disabled || isPicking) return;
35
+
36
+ const result = await pick();
37
+ if (!result.cancelled && result.files.length > 0) {
38
+ onDrop?.(result.files);
39
+ }
40
+ if (result.rejected.length > 0) {
41
+ onReject?.(result.rejected);
42
+ }
43
+ }, [disabled, isPicking, pick, onDrop, onReject]);
44
+
45
+ // Apply variants
46
+ dropZoneStyles.useVariants({
47
+ active: false,
48
+ reject: false,
49
+ disabled,
50
+ });
51
+
52
+ // Render children
53
+ const renderChildren = () => {
54
+ if (typeof children === 'function') {
55
+ return children(state);
56
+ }
57
+
58
+ if (children) {
59
+ return children;
60
+ }
61
+
62
+ // Default content
63
+ return (
64
+ <View style={dropZoneStyles.content({})}>
65
+ <Text style={dropZoneStyles.icon({})}>
66
+ 📁
67
+ </Text>
68
+ <Text style={dropZoneStyles.text({})}>
69
+ Tap to select files
70
+ </Text>
71
+ <Text style={dropZoneStyles.hint({})}>
72
+ or use the camera
73
+ </Text>
74
+ </View>
75
+ );
76
+ };
77
+
78
+ return (
79
+ <TouchableOpacity
80
+ onPress={handlePress}
81
+ disabled={disabled || isPicking}
82
+ activeOpacity={0.75}
83
+ testID={testID}
84
+ accessibilityRole="button"
85
+ accessibilityLabel="Touch to select files"
86
+ style={[
87
+ dropZoneStyles.container({ disabled }),
88
+ style,
89
+ ]}
90
+ >
91
+ {renderChildren()}
92
+ </TouchableOpacity>
93
+ );
94
+ };
95
+
96
+ DropZone.displayName = 'DropZone';
@@ -0,0 +1,99 @@
1
+ /**
2
+ * DropZone styles using defineStyle.
3
+ */
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
+ import type { Theme as BaseTheme, Intent } from '@idealyst/theme';
7
+
8
+ // Required: Unistyles must see StyleSheet usage to process this file
9
+ void StyleSheet;
10
+
11
+ // Wrap theme for $iterator support
12
+ type Theme = ThemeStyleWrapper<BaseTheme>;
13
+
14
+ export type DropZoneVariants = {
15
+ active: boolean;
16
+ reject: boolean;
17
+ disabled: boolean;
18
+ };
19
+
20
+ export type DropZoneDynamicProps = {
21
+ active?: boolean;
22
+ reject?: boolean;
23
+ disabled?: boolean;
24
+ intent?: Intent;
25
+ };
26
+
27
+ export const dropZoneStyles = defineStyle('DropZone', (theme: Theme) => ({
28
+ container: ({ active = false, reject = false, disabled = false }: DropZoneDynamicProps) => ({
29
+ borderWidth: 2,
30
+ borderStyle: 'dashed' as const,
31
+ borderRadius: 12,
32
+ padding: 32,
33
+ alignItems: 'center',
34
+ justifyContent: 'center',
35
+ backgroundColor: active
36
+ ? theme.intents.primary.light
37
+ : reject
38
+ ? theme.intents.danger.light
39
+ : theme.colors.surface.secondary,
40
+ borderColor: active
41
+ ? theme.intents.primary.primary
42
+ : reject
43
+ ? theme.intents.danger.primary
44
+ : theme.colors.border.secondary,
45
+ opacity: disabled ? 0.6 : 1,
46
+ _web: {
47
+ cursor: disabled ? 'not-allowed' : 'pointer',
48
+ transition: 'all 0.2s ease',
49
+ },
50
+ variants: {
51
+ active: {
52
+ true: {
53
+ backgroundColor: theme.intents.primary.light,
54
+ borderColor: theme.intents.primary.primary,
55
+ },
56
+ false: {},
57
+ },
58
+ reject: {
59
+ true: {
60
+ backgroundColor: theme.intents.danger.light,
61
+ borderColor: theme.intents.danger.primary,
62
+ },
63
+ false: {},
64
+ },
65
+ disabled: {
66
+ true: { opacity: 0.6, _web: { cursor: 'not-allowed' } },
67
+ false: { opacity: 1, _web: { cursor: 'pointer' } },
68
+ },
69
+ },
70
+ }),
71
+ content: (_props: DropZoneDynamicProps) => ({
72
+ alignItems: 'center',
73
+ justifyContent: 'center',
74
+ gap: 12,
75
+ }),
76
+ icon: ({ active = false, reject = false }: DropZoneDynamicProps) => ({
77
+ fontSize: 48,
78
+ color: active
79
+ ? theme.intents.primary.primary
80
+ : reject
81
+ ? theme.intents.danger.primary
82
+ : theme.colors.text.secondary,
83
+ }),
84
+ text: ({ active = false, reject = false }: DropZoneDynamicProps) => ({
85
+ fontSize: 16,
86
+ fontWeight: '500',
87
+ textAlign: 'center',
88
+ color: active
89
+ ? theme.intents.primary.primary
90
+ : reject
91
+ ? theme.intents.danger.primary
92
+ : theme.colors.text.primary,
93
+ }),
94
+ hint: (_props: DropZoneDynamicProps) => ({
95
+ fontSize: 14,
96
+ color: theme.colors.text.secondary,
97
+ textAlign: 'center',
98
+ }),
99
+ }));
@@ -0,0 +1,178 @@
1
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
2
+ import { View, Text, Pressable } from 'react-native';
3
+ import type { DropZoneProps, DropZoneState, PickedFile, RejectedFile } from '../types';
4
+ import { dropZoneStyles } from './DropZone.styles';
5
+ import { useFilePicker } from '../hooks';
6
+
7
+ /**
8
+ * DropZone - A drag-and-drop area for file selection (Web).
9
+ */
10
+ export const DropZone: React.FC<DropZoneProps> = (props) => {
11
+ const {
12
+ onDrop,
13
+ onReject,
14
+ config,
15
+ children,
16
+ disabled = false,
17
+ style,
18
+ activeStyle,
19
+ rejectStyle,
20
+ testID,
21
+ } = props;
22
+
23
+ const { validateFiles, pick, isPicking } = useFilePicker({ config });
24
+ const dropRef = useRef<HTMLDivElement>(null);
25
+ const dragCounter = useRef(0);
26
+
27
+ const [state, setState] = useState<DropZoneState>({
28
+ isDragActive: false,
29
+ isDragReject: false,
30
+ isProcessing: false,
31
+ });
32
+
33
+ // Handle drag enter
34
+ const handleDragEnter = useCallback((e: DragEvent) => {
35
+ e.preventDefault();
36
+ e.stopPropagation();
37
+
38
+ if (disabled) return;
39
+
40
+ dragCounter.current++;
41
+
42
+ if (e.dataTransfer?.items?.length) {
43
+ setState(s => ({ ...s, isDragActive: true }));
44
+ }
45
+ }, [disabled]);
46
+
47
+ // Handle drag over
48
+ const handleDragOver = useCallback((e: DragEvent) => {
49
+ e.preventDefault();
50
+ e.stopPropagation();
51
+ }, []);
52
+
53
+ // Handle drag leave
54
+ const handleDragLeave = useCallback((e: DragEvent) => {
55
+ e.preventDefault();
56
+ e.stopPropagation();
57
+
58
+ dragCounter.current--;
59
+
60
+ if (dragCounter.current === 0) {
61
+ setState(s => ({ ...s, isDragActive: false, isDragReject: false }));
62
+ }
63
+ }, []);
64
+
65
+ // Handle drop
66
+ const handleDrop = useCallback(async (e: DragEvent) => {
67
+ e.preventDefault();
68
+ e.stopPropagation();
69
+
70
+ dragCounter.current = 0;
71
+
72
+ if (disabled) {
73
+ setState(s => ({ ...s, isDragActive: false, isDragReject: false }));
74
+ return;
75
+ }
76
+
77
+ setState(s => ({ ...s, isDragActive: false, isProcessing: true }));
78
+
79
+ const files = Array.from(e.dataTransfer?.files || []);
80
+ const { accepted, rejected } = validateFiles(files);
81
+
82
+ if (rejected.length > 0) {
83
+ onReject?.(rejected);
84
+ }
85
+
86
+ if (accepted.length > 0) {
87
+ onDrop?.(accepted);
88
+ }
89
+
90
+ setState(s => ({ ...s, isProcessing: false }));
91
+ }, [disabled, validateFiles, onDrop, onReject]);
92
+
93
+ // Handle click to open file picker
94
+ const handleClick = useCallback(async () => {
95
+ if (disabled || isPicking) return;
96
+
97
+ const result = await pick();
98
+ if (!result.cancelled && result.files.length > 0) {
99
+ onDrop?.(result.files);
100
+ }
101
+ if (result.rejected.length > 0) {
102
+ onReject?.(result.rejected);
103
+ }
104
+ }, [disabled, isPicking, pick, onDrop, onReject]);
105
+
106
+ // Set up drag event listeners
107
+ useEffect(() => {
108
+ const element = dropRef.current;
109
+ if (!element) return;
110
+
111
+ element.addEventListener('dragenter', handleDragEnter);
112
+ element.addEventListener('dragover', handleDragOver);
113
+ element.addEventListener('dragleave', handleDragLeave);
114
+ element.addEventListener('drop', handleDrop);
115
+
116
+ return () => {
117
+ element.removeEventListener('dragenter', handleDragEnter);
118
+ element.removeEventListener('dragover', handleDragOver);
119
+ element.removeEventListener('dragleave', handleDragLeave);
120
+ element.removeEventListener('drop', handleDrop);
121
+ };
122
+ }, [handleDragEnter, handleDragOver, handleDragLeave, handleDrop]);
123
+
124
+ // Apply variants
125
+ dropZoneStyles.useVariants({
126
+ active: state.isDragActive,
127
+ reject: state.isDragReject,
128
+ disabled,
129
+ });
130
+
131
+ // Render children
132
+ const renderChildren = () => {
133
+ if (typeof children === 'function') {
134
+ return children(state);
135
+ }
136
+
137
+ if (children) {
138
+ return children;
139
+ }
140
+
141
+ // Default content
142
+ return (
143
+ <View style={dropZoneStyles.content({})}>
144
+ <Text style={dropZoneStyles.icon({ active: state.isDragActive, reject: state.isDragReject })}>
145
+ {state.isDragActive ? '📂' : '📁'}
146
+ </Text>
147
+ <Text style={dropZoneStyles.text({ active: state.isDragActive, reject: state.isDragReject })}>
148
+ {state.isDragActive ? 'Drop files here' : 'Drag & drop files here'}
149
+ </Text>
150
+ <Text style={dropZoneStyles.hint({})}>
151
+ or click to browse
152
+ </Text>
153
+ </View>
154
+ );
155
+ };
156
+
157
+ return (
158
+ <Pressable
159
+ onPress={handleClick}
160
+ disabled={disabled}
161
+ testID={testID}
162
+ accessibilityRole="button"
163
+ accessibilityLabel="Drop zone for file upload"
164
+ style={[
165
+ dropZoneStyles.container({ active: state.isDragActive, reject: state.isDragReject, disabled }),
166
+ style,
167
+ state.isDragActive && activeStyle,
168
+ state.isDragReject && rejectStyle,
169
+ ]}
170
+ >
171
+ <View ref={dropRef as any}>
172
+ {renderChildren()}
173
+ </View>
174
+ </Pressable>
175
+ );
176
+ };
177
+
178
+ DropZone.displayName = 'DropZone';
@@ -0,0 +1,82 @@
1
+ import React, { forwardRef, useCallback } from 'react';
2
+ import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
3
+ import type { FilePickerButtonProps, FilePickerResult, FilePickerError } from '../types';
4
+ import { filePickerButtonStyles } from './FilePickerButton.styles';
5
+ import { useFilePicker } from '../hooks';
6
+
7
+ /**
8
+ * FilePickerButton - A styled button that opens the file picker (Native).
9
+ */
10
+ export const FilePickerButton = forwardRef<View, FilePickerButtonProps>((props, ref) => {
11
+ const {
12
+ children = 'Select File',
13
+ pickerConfig,
14
+ onPick,
15
+ onError,
16
+ disabled = false,
17
+ loading = false,
18
+ variant = 'solid',
19
+ size = 'md',
20
+ intent = 'primary',
21
+ leftIcon = 'file-upload',
22
+ style,
23
+ testID,
24
+ } = props;
25
+
26
+ const { pick, isPicking } = useFilePicker({ config: pickerConfig });
27
+
28
+ const isDisabled = disabled || loading || isPicking;
29
+ const isLoading = loading || isPicking;
30
+
31
+ const handlePress = useCallback(async () => {
32
+ if (isDisabled) return;
33
+
34
+ try {
35
+ const result: FilePickerResult = await pick();
36
+ if (!result.cancelled) {
37
+ onPick?.(result);
38
+ }
39
+ } catch (err) {
40
+ onError?.(err as FilePickerError);
41
+ }
42
+ }, [isDisabled, pick, onPick, onError]);
43
+
44
+ // Apply variants
45
+ filePickerButtonStyles.useVariants({
46
+ size,
47
+ intent,
48
+ variant,
49
+ disabled: isDisabled,
50
+ });
51
+
52
+ return (
53
+ <TouchableOpacity
54
+ ref={ref}
55
+ onPress={handlePress}
56
+ disabled={isDisabled}
57
+ activeOpacity={0.75}
58
+ style={[filePickerButtonStyles.button({ intent, variant }), style]}
59
+ testID={testID}
60
+ accessibilityRole="button"
61
+ accessibilityState={{ disabled: isDisabled }}
62
+ accessibilityLabel={typeof children === 'string' ? children : 'Select file'}
63
+ >
64
+ {isLoading ? (
65
+ <ActivityIndicator
66
+ size="small"
67
+ color={filePickerButtonStyles.spinner({ intent, variant }).color as string}
68
+ />
69
+ ) : leftIcon ? (
70
+ <Text style={filePickerButtonStyles.icon({ intent, variant })}>
71
+ {/* Icon placeholder - in a real implementation, use MDI icon */}
72
+ 📁
73
+ </Text>
74
+ ) : null}
75
+ <Text style={filePickerButtonStyles.text({ intent, variant })}>
76
+ {children}
77
+ </Text>
78
+ </TouchableOpacity>
79
+ );
80
+ });
81
+
82
+ FilePickerButton.displayName = 'FilePickerButton';
@@ -0,0 +1,112 @@
1
+ /**
2
+ * FilePickerButton styles using defineStyle.
3
+ */
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
+ import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
7
+
8
+ // Required: Unistyles must see StyleSheet usage to process this file
9
+ void StyleSheet;
10
+
11
+ // Wrap theme for $iterator support
12
+ type Theme = ThemeStyleWrapper<BaseTheme>;
13
+
14
+ type ButtonType = 'solid' | 'outline' | 'ghost';
15
+
16
+ export type FilePickerButtonVariants = {
17
+ size: Size;
18
+ intent: Intent;
19
+ variant: ButtonType;
20
+ disabled: boolean;
21
+ };
22
+
23
+ export type FilePickerButtonDynamicProps = {
24
+ intent?: Intent;
25
+ variant?: ButtonType;
26
+ size?: Size;
27
+ disabled?: boolean;
28
+ };
29
+
30
+ export const filePickerButtonStyles = defineStyle('FilePickerButton', (theme: Theme) => ({
31
+ button: ({ intent = 'primary', variant = 'solid' }: FilePickerButtonDynamicProps) => ({
32
+ boxSizing: 'border-box',
33
+ alignItems: 'center',
34
+ justifyContent: 'center',
35
+ borderRadius: 8,
36
+ fontWeight: '600',
37
+ textAlign: 'center',
38
+ flexDirection: 'row',
39
+ gap: 8,
40
+ backgroundColor: variant === 'solid'
41
+ ? theme.intents[intent].primary
42
+ : variant === 'outline'
43
+ ? theme.colors.surface.primary
44
+ : 'transparent',
45
+ borderColor: variant === 'outline'
46
+ ? theme.intents[intent].primary
47
+ : 'transparent',
48
+ borderWidth: variant === 'outline' ? 1 : 0,
49
+ borderStyle: variant === 'outline' ? 'solid' as const : undefined,
50
+ _web: {
51
+ display: 'flex',
52
+ transition: 'all 0.1s ease',
53
+ cursor: 'pointer',
54
+ },
55
+ variants: {
56
+ size: {
57
+ paddingVertical: theme.sizes.$button.paddingVertical,
58
+ paddingHorizontal: theme.sizes.$button.paddingHorizontal,
59
+ minHeight: theme.sizes.$button.minHeight,
60
+ },
61
+ disabled: {
62
+ true: { opacity: 0.6, _web: { cursor: 'not-allowed' } },
63
+ false: { opacity: 1, _web: { cursor: 'pointer', _hover: { opacity: 0.90 }, _active: { opacity: 0.75 } } },
64
+ },
65
+ },
66
+ }),
67
+ text: ({ intent = 'primary', variant = 'solid' }: FilePickerButtonDynamicProps) => ({
68
+ fontWeight: '600',
69
+ textAlign: 'center',
70
+ color: variant === 'solid'
71
+ ? theme.intents[intent].contrast
72
+ : theme.intents[intent].primary,
73
+ variants: {
74
+ size: {
75
+ fontSize: theme.sizes.$button.fontSize,
76
+ lineHeight: theme.sizes.$button.fontSize,
77
+ },
78
+ disabled: {
79
+ true: { opacity: 0.6 },
80
+ false: { opacity: 1 },
81
+ },
82
+ },
83
+ }),
84
+ icon: ({ intent = 'primary', variant = 'solid' }: FilePickerButtonDynamicProps) => ({
85
+ display: 'flex',
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ color: variant === 'solid'
89
+ ? theme.intents[intent].contrast
90
+ : theme.intents[intent].primary,
91
+ variants: {
92
+ size: {
93
+ width: theme.sizes.$button.iconSize,
94
+ height: theme.sizes.$button.iconSize,
95
+ },
96
+ },
97
+ }),
98
+ spinner: ({ intent = 'primary', variant = 'solid' }: FilePickerButtonDynamicProps) => ({
99
+ display: 'flex',
100
+ alignItems: 'center',
101
+ justifyContent: 'center',
102
+ color: variant === 'solid'
103
+ ? theme.intents[intent].contrast
104
+ : theme.intents[intent].primary,
105
+ variants: {
106
+ size: {
107
+ width: theme.sizes.$button.iconSize,
108
+ height: theme.sizes.$button.iconSize,
109
+ },
110
+ },
111
+ }),
112
+ }));