@beydesign/storybook 0.2.28 → 0.2.30

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.
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { DropdownType } from './types';
4
- import { Box, CircularProgress, ClickAwayListener, Popper, } from '@mui/material';
4
+ import { Box, CircularProgress, ClickAwayListener, Grow, Popper, } from '@mui/material';
5
5
  import { Typography, TypographySize, TypographyWeight } from '../Typography';
6
6
  import { getIconComponent } from '../../utils';
7
7
  import { Checkbox } from './Checkbox';
8
- import { Badge, BadgeColor, BadgeSize } from '../UI';
8
+ import { Avatar, AvatarSize, Badge, BadgeColor, BadgeSize } from '../UI';
9
9
  export const Dropdown = ({ disabled = false, placeholder = 'Select an option', label = 'Select', showLeadingIcon = false, leadingIcon = 'List', showClear = true, options, required = false, showCheckboxes = false, showTicks = true, onEndReached, isLoadingMore = false, ...props }) => {
10
10
  const [open, setOpen] = useState(false);
11
11
  const [highlightedIndex, setHighlightedIndex] = useState(-1);
@@ -36,6 +36,7 @@ export const Dropdown = ({ disabled = false, placeholder = 'Select an option', l
36
36
  // trigger renders the rich "Avatar leading" layout; otherwise it falls back to
37
37
  // the plain joined-label text (preserving the original behavior).
38
38
  const hasRichSelection = selectedOptions.some((option) => option.avatar || option.badge);
39
+ const hasAvatarOption = normalizedOptions.some((option) => option.avatar);
39
40
  const hasSelection = selectedOptions.length > 0;
40
41
  // Plain-text representation of the selection, used for the non-rich trigger.
41
42
  const displayValue = useMemo(() => selectedOptions.map((option) => option.label).join(', '), [selectedOptions]);
@@ -151,10 +152,10 @@ export const Dropdown = ({ disabled = false, placeholder = 'Select an option', l
151
152
  ? 'var(--color-border-border-brand)'
152
153
  : 'var(--color-border-border-component)'}`, borderRadius: '4px', bgcolor: disabled
153
154
  ? 'var(--color-background-bg-component-disabled)'
154
- : 'var(--color-background-bg-component)', position: 'relative', onClick: () => !disabled && setOpen((prev) => !prev), onKeyDown: handleKeyDown, tabIndex: disabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": open, "aria-disabled": disabled, role: "combobox", sx: { boxSizing: 'border-box' }, children: [_jsx(Box, { display: "flex", flexDirection: "row", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-sm)", flexGrow: 1, minWidth: 0, overflow: "hidden", children: hasSelection && hasRichSelection ? (_jsxs(_Fragment, { children: [selectedOptions[0].avatar && (_jsx(Box, { component: "img", src: selectedOptions[0].avatar, alt: "", width: "24px", height: "24px", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-full)", flexShrink: 0, sx: {
155
- objectFit: 'cover',
156
- border: '0.5px solid var(--color-border-border-divider)',
157
- } })), _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: "var(--color-text-text-title)", textStyle: {
155
+ : 'var(--color-background-bg-component)', position: 'relative', onClick: () => !disabled && setOpen((prev) => !prev), onKeyDown: handleKeyDown, tabIndex: disabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": open, "aria-disabled": disabled, role: "combobox", sx: {
156
+ boxSizing: 'border-box',
157
+ cursor: disabled ? 'not-allowed' : 'pointer',
158
+ }, children: [_jsx(Box, { display: "flex", flexDirection: "row", alignItems: "center", gap: "var(--spacing-tokens-size-in-px-spacing-spacing-sm)", flexGrow: 1, minWidth: 0, overflow: "hidden", children: hasSelection && (hasRichSelection || hasAvatarOption) ? (_jsxs(_Fragment, { children: [hasAvatarOption && (_jsx(Box, { flexShrink: 0, display: "flex", children: _jsx(Avatar, { image: selectedOptions[0].avatar, text: selectedOptions[0].label, size: AvatarSize.xs, border: true }) })), _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: "var(--color-text-text-title)", textStyle: {
158
159
  minWidth: 0,
159
160
  textAlign: 'left',
160
161
  overflow: 'hidden',
@@ -179,8 +180,8 @@ export const Dropdown = ({ disabled = false, placeholder = 'Select an option', l
179
180
  showClear &&
180
181
  hasSelection &&
181
182
  getIconComponent('X', {
182
- width: '24px',
183
- height: '24px',
183
+ width: '20px',
184
+ height: '20px',
184
185
  color: 'var(--color-foreground-fg-secondary)',
185
186
  onClick: handleClear,
186
187
  'aria-label': 'Clear selection',
@@ -193,36 +194,37 @@ export const Dropdown = ({ disabled = false, placeholder = 'Select an option', l
193
194
  }
194
195
  },
195
196
  }), !disabled &&
196
- getIconComponent(open ? 'CaretUp' : 'CaretDown', {
197
- width: '24px',
198
- height: '24px',
197
+ getIconComponent('CaretDown', {
198
+ width: '20px',
199
+ height: '20px',
199
200
  color: 'var(--color-foreground-fg-secondary)',
200
201
  'aria-hidden': 'true',
201
- })] })] }), _jsx(Popper, { open: open, anchorEl: anchorRef.current, placement: "bottom-start", style: { width: anchorRef.current?.offsetWidth, zIndex: 1300 }, children: _jsxs(Box, { ref: optionsContainerRef, onScroll: handleOptionsScroll, bgcolor: 'var(--color-background-bg-component)', borderRadius: '4px', width: '100%', maxHeight: '200px', overflow: 'auto', boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)', mt: '4px', role: "listbox", sx: {
202
- border: '1px solid var(--color-border-border-component)',
203
- }, children: [normalizedOptions.length === 0 && !isLoadingMore ? (_jsx(Box, { display: 'flex', flexDirection: 'row', paddingX: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', paddingY: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, children: "No options found" }) })) : (normalizedOptions.map((option, index) => (_jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', height: '36px', padding: '0 var(--spacing-tokens-size-in-px-spacing-spacing-md)', onClick: () => handleOptionSelect(option.value), onMouseEnter: () => setHighlightedIndex(index), sx: {
204
- cursor: 'pointer',
205
- backgroundColor: highlightedIndex === index
206
- ? 'var(--color-background-bg-secondary-action-hover)'
207
- : 'transparent',
208
- '&:hover': {
209
- backgroundColor: 'var(--color-background-bg-secondary-action-hover)',
210
- },
211
- width: '100%',
212
- boxSizing: 'border-box',
213
- }, role: "option", "aria-selected": isOptionSelected(option.value), children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', flexGrow: 1, minWidth: 0, overflow: "hidden", children: [showCheckboxes && (_jsx(Box, { flexShrink: 0, children: _jsx(Checkbox, { checked: isOptionSelected(option.value), onChange: () => handleOptionSelect(option.value), style: { margin: 0, padding: 0 } }) })), option.avatar && (_jsx(Box, { component: "img", src: option.avatar, alt: "", width: "24px", height: "24px", borderRadius: "var(--spacing-tokens-size-in-px-radius-radius-full)", flexShrink: 0, sx: {
214
- objectFit: 'cover',
215
- border: '0.5px solid var(--color-border-border-divider)',
216
- } })), _jsx(Box, { flexGrow: 1, minWidth: 0, overflow: "hidden", children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: "var(--color-text-text-title)", textStyle: {
217
- overflow: 'hidden',
218
- textOverflow: 'ellipsis',
219
- whiteSpace: 'nowrap',
220
- width: '100%',
221
- }, children: option.label }) }), option.badge && (_jsx(Box, { flexShrink: 0, children: _jsx(Badge, { size: BadgeSize.xs, color: BadgeColor.Default, text: option.badge }) }))] }), _jsx(Box, { flexShrink: 0, display: "flex", alignItems: "center", children: showTicks &&
222
- isOptionSelected(option.value) &&
223
- getIconComponent('Check', {
224
- width: '20px',
225
- height: '20px',
226
- color: 'var(--color-foreground-fg-brand-primary)',
227
- }) })] }, option.value)))), isLoadingMore && (_jsx(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', height: '36px', width: '100%', role: "status", "aria-label": "Loading more options", children: _jsx(CircularProgress, { size: 20, sx: { color: 'var(--color-foreground-fg-brand-primary)' } }) }))] }) })] }) }));
202
+ style: {
203
+ transition: 'transform 200ms ease',
204
+ transform: open ? 'rotate(180deg)' : 'rotate(0deg)',
205
+ },
206
+ })] })] }), _jsx(Popper, { open: open, anchorEl: anchorRef.current, placement: "bottom-start", transition: true, style: { width: anchorRef.current?.offsetWidth, zIndex: 1300 }, children: ({ TransitionProps }) => (_jsx(Grow, { ...TransitionProps, timeout: 200, style: { transformOrigin: 'top' }, children: _jsxs(Box, { ref: optionsContainerRef, onScroll: handleOptionsScroll, bgcolor: 'var(--color-background-bg-component)', borderRadius: '4px', width: '100%', maxHeight: '200px', overflow: 'auto', boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)', mt: '4px', role: "listbox", sx: {
207
+ border: '1px solid var(--color-border-border-component)',
208
+ }, children: [normalizedOptions.length === 0 && !isLoadingMore ? (_jsx(Box, { display: 'flex', flexDirection: 'row', paddingX: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', paddingY: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, children: "No options found" }) })) : (normalizedOptions.map((option, index) => (_jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', height: '36px', padding: '0 var(--spacing-tokens-size-in-px-spacing-spacing-md)', onClick: () => handleOptionSelect(option.value), onMouseEnter: () => setHighlightedIndex(index), sx: {
209
+ cursor: 'pointer',
210
+ backgroundColor: highlightedIndex === index
211
+ ? 'var(--color-background-bg-secondary-action-hover)'
212
+ : 'transparent',
213
+ '&:hover': {
214
+ backgroundColor: 'var(--color-background-bg-secondary-action-hover)',
215
+ },
216
+ width: '100%',
217
+ boxSizing: 'border-box',
218
+ }, role: "option", "aria-selected": isOptionSelected(option.value), children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', flexGrow: 1, minWidth: 0, overflow: "hidden", children: [showCheckboxes && (_jsx(Box, { flexShrink: 0, children: _jsx(Checkbox, { checked: isOptionSelected(option.value), onChange: () => handleOptionSelect(option.value), style: { margin: 0, padding: 0 } }) })), hasAvatarOption && (_jsx(Box, { flexShrink: 0, display: "flex", children: _jsx(Avatar, { image: option.avatar, text: option.label, size: AvatarSize.xs, border: true }) })), _jsx(Box, { flexGrow: 1, minWidth: 0, overflow: "hidden", children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: "var(--color-text-text-title)", textStyle: {
219
+ overflow: 'hidden',
220
+ textOverflow: 'ellipsis',
221
+ whiteSpace: 'nowrap',
222
+ width: '100%',
223
+ }, children: option.label }) }), option.badge && (_jsx(Box, { flexShrink: 0, children: _jsx(Badge, { size: BadgeSize.xs, color: BadgeColor.Default, text: option.badge }) }))] }), _jsx(Box, { flexShrink: 0, display: "flex", alignItems: "center", children: showTicks &&
224
+ isOptionSelected(option.value) &&
225
+ getIconComponent('Check', {
226
+ width: '20px',
227
+ height: '20px',
228
+ color: 'var(--color-foreground-fg-brand-primary)',
229
+ }) })] }, option.value)))), isLoadingMore && (_jsx(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', height: '36px', width: '100%', role: "status", "aria-label": "Loading more options", children: _jsx(CircularProgress, { size: 20, sx: { color: 'var(--color-foreground-fg-brand-primary)' } }) }))] }) })) })] }) }));
228
230
  };
@@ -55,14 +55,20 @@ export const FileUpload = ({ disabled, sx, acceptedFileTypes = '.pdf,.doc,.docx,
55
55
  .split(',')
56
56
  .map((ext) => ext.trim())
57
57
  .filter((ext) => ext.startsWith('.'));
58
- // Get unique icons for the accepted file types
59
- const uniqueIcons = new Map();
58
+ // Collect one icon per accepted file type, de-duplicated by component
59
+ // identity (e.g. `.doc` and `.docx` share FileDoc) while preserving order.
60
+ // NB: we can't key on `IconComponent.name` — Phosphor icons are forwardRef
61
+ // objects with no `name`, so every entry would collapse to a single key.
62
+ const seen = new Set();
63
+ const icons = [];
60
64
  extensions.forEach((ext) => {
61
65
  const IconComponent = fileIconMap[ext] || defaultIcon;
62
- // Use the component name as key to avoid duplicates
63
- uniqueIcons.set(IconComponent.name, IconComponent);
66
+ if (!seen.has(IconComponent)) {
67
+ seen.add(IconComponent);
68
+ icons.push(IconComponent);
69
+ }
64
70
  });
65
- return Array.from(uniqueIcons.values());
71
+ return icons;
66
72
  };
67
73
  const fileIcons = getFileTypeIcons();
68
74
  const handleFileSelect = (files) => {
@@ -104,25 +110,29 @@ export const FileUpload = ({ disabled, sx, acceptedFileTypes = '.pdf,.doc,.docx,
104
110
  handleFileSelect(e.target.files);
105
111
  };
106
112
  const iconProps = {
107
- height: '20px',
108
- width: '20px',
113
+ height: '24px',
114
+ width: '24px',
109
115
  color: disabled
110
116
  ? 'var(--color-text-text-subtle)'
111
117
  : 'var(--color-text-text-paragraph)',
112
118
  };
113
- return (_jsxs(Box, { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', border: `1.5px dashed ${isDragging ? 'var(--color-background-bg-brand-primary-hover)' : 'var(--color-background-bg-brand-primary)'}`, bgcolor: disabled
114
- ? 'var(--color-background-bg-component-contrast-subtle)'
119
+ return (_jsxs(Box, { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', border: `1px dashed ${disabled
120
+ ? 'var(--color-border-border-disabled)'
121
+ : isDragging
122
+ ? 'var(--color-border-border-brand-hover)'
123
+ : 'var(--color-border-border-component)'}`, bgcolor: disabled
124
+ ? 'var(--color-background-bg-component-disabled)'
115
125
  : isDragging
116
126
  ? 'var(--color-background-bg-component-hover)'
117
- : 'var(--color-background-bg-element)', sx: { cursor: disabled ? 'not-allowed' : 'pointer', ...sx }, onClick: handleClick, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [_jsx("input", { type: "file", ref: fileInputRef, style: { display: 'none' }, accept: acceptedFileTypes, onChange: handleInputChange, disabled: disabled }), _jsx(Box, { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', border: `2px solid ${disabled
118
- ? 'var(--color-border-border-component)'
119
- : isDragging
120
- ? 'var(--color-background-bg-brand-primary-hover)'
121
- : 'var(--color-background-bg-brand-primary)'}`, borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', bgcolor: 'var(--color-background-bg-component)', children: _jsx(UploadSimple, { height: '20px', width: '20px', weight: "bold", color: disabled
127
+ : 'var(--color-background-bg-element)', sx: { cursor: disabled ? 'not-allowed' : 'pointer', ...sx }, onClick: handleClick, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [_jsx("input", { type: "file", ref: fileInputRef, style: { display: 'none' }, accept: acceptedFileTypes, onChange: handleInputChange, disabled: disabled }), _jsx(Box, { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', border: `1px solid ${disabled
128
+ ? 'var(--color-border-border-disabled)'
129
+ : 'var(--color-border-border-component)'}`, borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', bgcolor: disabled
130
+ ? 'var(--color-background-bg-button-disabled)'
131
+ : 'var(--color-background-bg-element)', children: _jsx(UploadSimple, { height: '20px', width: '20px', weight: "bold", color: disabled
122
132
  ? 'var(--color-foreground-fg-disabled)'
123
133
  : 'var(--color-foreground-fg-brand-primary)' }) }), _jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', children: [_jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.SemiBold, color: disabled
134
+ ? 'var(--color-text-text-inactive)'
135
+ : 'var(--color-text-text-clicable)', textStyle: { cursor: disabled ? 'not-allowed' : 'pointer' }, children: "Click to upload" }), _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: disabled
124
136
  ? 'var(--color-text-text-disabled)'
125
- : 'var(--color-text-text-accent)', textStyle: { cursor: disabled ? 'not-allowed' : 'pointer' }, children: "Click to upload" }), _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: disabled
126
- ? 'var(--color-text-text-subtle)'
127
137
  : 'var(--color-text-text-paragraph)', children: "or drag and drop" }), _jsxs(Box, { display: 'flex', flexDirection: 'row', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', children: [fileIcons.map((Icon, index) => (_jsx(Icon, { ...iconProps }, index))), fileIcons.length === 0 && _jsx(FileCode, { ...iconProps })] })] })] }));
128
138
  };
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment } from 'react';
3
2
  import { FileUploadingStatus, FileUploadingProgressMode, } from '../types';
4
3
  import { Box } from '@mui/material';
5
4
  import { Typography, TypographySize, TypographyWeight } from '../../Typography';
@@ -7,7 +6,18 @@ import { getIconComponent } from '../../../utils';
7
6
  import { Player } from '@lottiefiles/react-lottie-player';
8
7
  import loadingAnimation from '../../../assets/animations/loading-animation.json';
9
8
  import { ProgressIndicator, ProgressIndicatorMode } from '../../UI';
10
- export const FileUploading = ({ uploadProgress, uploadingStatus, progressMode = FileUploadingProgressMode.Animation, sx, onCancel, onRetry, fileName, showCancel, showRetry, }) => {
9
+ // Transparent, subtly shadowed frame that wraps a trailing action icon,
10
+ // matching the "Button" node in the Figma design.
11
+ const actionButtonSx = {
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)',
16
+ borderRadius: 'var(--spacing-tokens-size-in-px-radius-radius-xs)',
17
+ boxShadow: '0px 1px 1px rgba(25, 28, 31, 0.05)',
18
+ backgroundColor: 'transparent',
19
+ };
20
+ export const FileUploading = ({ uploadProgress, uploadingStatus, progressMode = FileUploadingProgressMode.Percentage, sx, onCancel, onRetry, fileName, showCancel, showRetry, }) => {
11
21
  const isUploading = uploadingStatus === FileUploadingStatus.Uploading;
12
22
  const isSuccess = uploadingStatus === FileUploadingStatus.Success;
13
23
  const isError = uploadingStatus === FileUploadingStatus.Error;
@@ -23,27 +33,27 @@ export const FileUploading = ({ uploadProgress, uploadingStatus, progressMode =
23
33
  return 'FileTxt';
24
34
  return 'File';
25
35
  };
26
- return (_jsxs(Box, { display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', paddingX: 'var(--spacing-tokens-size-in-px-spacing-spacing-xl)', paddingY: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', border: `1px solid ${isError ? 'var(--color-border-border-error)' : 'var(--color-border-border-component)'}`, bgcolor: 'var(--color-background-bg-component-contrast-subtle)', minWidth: '300px', sx: { ...sx }, children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', children: [getIconComponent(getFileIcon(), {
27
- width: '24px',
28
- height: '24px',
29
- color: isError
30
- ? 'var(--color-text-text-error)'
31
- : 'var(--color-foreground-fg-primary)',
32
- }), _jsx(Typography, { size: TypographySize.TextM, weight: TypographyWeight.Regular, color: isError
33
- ? 'var(--color-text-text-error)'
34
- : 'var(--color-text-text-title)', children: fileName })] }), _jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', paddingY: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', children: [showCancel &&
35
- onCancel &&
36
- isUploading &&
37
- getIconComponent('X', {
38
- width: '24px',
39
- height: '24px',
40
- color: 'var(--color-text-text-title)',
41
- onClick: onCancel,
42
- style: { cursor: 'pointer' },
43
- }), isSuccess &&
44
- getIconComponent('Check', {
45
- width: '24px',
46
- height: '24px',
47
- color: 'var(--color-foreground-fg-success)',
48
- })] })] }), _jsxs(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', width: '100%', children: [isUploading && isAnimationProgress && (_jsx(Player, { autoplay: true, loop: true, src: loadingAnimation, style: { height: '100%', width: '100%' } })), isUploading && isPercentageProgress && (_jsx(ProgressIndicator, { currentValue: uploadProgress || 0, maxValue: 100, mode: ProgressIndicatorMode.Percentage, style: { width: '100%', height: '100%' } })), isError && (_jsxs(Fragment, { children: [_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: 'var(--color-text-text-error)', children: "Upload failed, please try again." }), showRetry && onRetry && (_jsx(Box, { onClick: onRetry, style: { cursor: 'pointer' }, children: _jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.SemiBold, color: 'var(--color-text-text-error)', children: "Try again" }) }))] }))] })] }));
36
+ return (_jsxs(Box, { display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', paddingX: 'var(--spacing-tokens-size-in-px-spacing-spacing-xl)', paddingY: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', border: `1px solid ${isError ? 'var(--color-border-border-error)' : 'var(--color-border-border-divider)'}`, bgcolor: isError
37
+ ? 'var(--color-background-bg-badge-accent-red)'
38
+ : 'var(--color-background-bg-component-contrast-subtle)', minWidth: '300px', sx: { ...sx }, children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', width: '100%', children: [getIconComponent(getFileIcon(), {
39
+ width: '24px',
40
+ height: '24px',
41
+ color: isError
42
+ ? 'var(--color-text-text-error)'
43
+ : 'var(--color-foreground-fg-primary)',
44
+ }), _jsx(Box, { flex: 1, minWidth: 0, children: _jsx(Typography, { size: TypographySize.TextS, weight: isError ? TypographyWeight.SemiBold : TypographyWeight.Regular, color: isError
45
+ ? 'var(--color-text-text-error)'
46
+ : 'var(--color-text-text-title)', children: fileName }) }), isUploading && showCancel && onCancel && (_jsx(Box, { onClick: onCancel, sx: { ...actionButtonSx, cursor: 'pointer' }, children: getIconComponent('X', {
47
+ width: '20px',
48
+ height: '20px',
49
+ color: 'var(--color-text-text-title)',
50
+ }) })), isSuccess && (_jsx(Box, { sx: actionButtonSx, children: getIconComponent('CheckCircle', {
51
+ width: '20px',
52
+ height: '20px',
53
+ weight: 'fill',
54
+ color: 'var(--color-foreground-fg-success)',
55
+ }) }))] }), _jsxs(Box, { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', width: '100%', children: [isUploading && isAnimationProgress && (_jsx(Player, { autoplay: true, loop: true, src: loadingAnimation, style: { height: '100%', width: '100%' } })), ((isUploading && isPercentageProgress) || isSuccess) && (_jsx(ProgressIndicator, { currentValue: isSuccess ? 100 : uploadProgress || 0, maxValue: 100, mode: ProgressIndicatorMode.Percentage, fillColor: 'var(--color-foreground-fg-violet)', backgroundColor: 'var(--color-background-bg-element)', textColor: 'var(--color-text-text-paragraph)', minWidth: 0, style: {
56
+ width: '100%',
57
+ gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)',
58
+ } })), isError && (_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'baseline', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', width: '100%', children: [_jsx(Typography, { size: TypographySize.TextXS, weight: TypographyWeight.Regular, color: 'var(--color-text-text-error)', children: "Upload failed, please try again" }), showRetry && onRetry && (_jsx(Box, { onClick: onRetry, style: { cursor: 'pointer' }, children: _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.SemiBold, color: 'var(--color-text-text-error)', children: "Try again" }) }))] }))] })] }));
49
59
  };
@@ -1,12 +1,23 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { FileType } from '../types';
3
4
  import { Box } from '@mui/material';
4
5
  import { File } from '@phosphor-icons/react';
5
6
  import { Typography, TypographySize, TypographyWeight } from '../../Typography';
6
- import { ButtonDisplayMode, ButtonHierarchy, ButtonSize, MainButton, } from '../../Buttons';
7
- export const UploadedFile = ({ type = FileType.Default, showDelete = true, onDelete, showDownload = true, onDownload, fileName, sx, }) => {
7
+ import { Menu } from '../../UI';
8
+ import { ButtonHierarchy, ButtonSize } from '../../Buttons';
9
+ export const UploadedFile = ({ type = FileType.Default, showMenu = true, menuItems, fileName, sx, }) => {
8
10
  const isSampleFile = type === FileType.Sample;
9
- return (_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', padding: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', paddingLeft: 'var(--spacing-tokens-size-in-px-spacing-spacing-lg)', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', bgcolor: isSampleFile
11
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
12
+ // While its overflow menu is open, the row takes on the active highlight so
13
+ // it stands out from the other files in the list.
14
+ const backgroundColor = isMenuOpen
15
+ ? 'var(--color-background-bg-component-hover)'
16
+ : isSampleFile
10
17
  ? 'var(--color-background-bg-card-highlight)'
11
- : 'var(--color-background-bg-component-contrast-subtle)', minWidth: '240px', minHeight: '36px', sx: { ...sx }, children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', children: [_jsx(File, { width: '24px', height: '24px', color: 'var(--color-foreground-fg-primary)' }), _jsx(Typography, { size: TypographySize.TextM, weight: TypographyWeight.Medium, color: 'var(--color-foreground-fg-primary)', children: fileName })] }), _jsxs(Box, { display: 'flex', flexDirection: 'row', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', children: [showDownload && onDownload && (_jsx(MainButton, { hierarchy: ButtonHierarchy.Secondary, size: ButtonSize.xs, displayIconMode: ButtonDisplayMode.Only, displayIcon: "DownloadSimple", onClick: onDownload })), showDelete && onDelete && (_jsx(MainButton, { hierarchy: ButtonHierarchy.Secondary, size: ButtonSize.xs, displayIconMode: ButtonDisplayMode.Only, displayIcon: "X", onClick: onDelete }))] })] }));
18
+ : 'var(--color-background-bg-element)';
19
+ return (_jsx(Box, { display: 'flex', flexDirection: 'column', alignItems: 'flex-start', paddingLeft: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', paddingRight: 'var(--spacing-tokens-size-in-px-spacing-spacing-sm)', paddingY: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', borderRadius: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', bgcolor: backgroundColor, minWidth: '240px', sx: { ...sx }, children: _jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-md)', width: '100%', children: [_jsxs(Box, { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'var(--spacing-tokens-size-in-px-spacing-spacing-xs)', flex: 1, minWidth: 0, children: [_jsx(File, { width: '20px', height: '20px', color: 'var(--color-foreground-fg-secondary)' }), _jsx(Typography, { size: TypographySize.TextS, weight: TypographyWeight.Regular, color: 'var(--color-text-text-title)', children: fileName })] }), showMenu && (_jsx(Menu, { icon: "DotsThree", iconWeight: "bold", items: menuItems, size: ButtonSize.xs, hierarchy: ButtonHierarchy.Tertiary, onOpenChange: setIsMenuOpen, style: {
20
+ background: 'transparent',
21
+ boxShadow: '0px 1px 1px rgba(25, 28, 31, 0.05)',
22
+ } }))] }) }));
12
23
  };
@@ -23,25 +23,15 @@ const meta = {
23
23
  description: 'The type of the file',
24
24
  table: { type: { summary: 'string' } },
25
25
  },
26
- showDelete: {
26
+ showMenu: {
27
27
  control: 'boolean',
28
- description: 'Whether to show the delete button',
28
+ description: 'Whether to show the trailing overflow-menu (kebab) button',
29
29
  table: { type: { summary: 'boolean' } },
30
30
  },
31
- showDownload: {
32
- control: 'boolean',
33
- description: 'Whether to show the download button',
34
- table: { type: { summary: 'boolean' } },
35
- },
36
- onDelete: {
37
- action: 'onDelete',
38
- description: 'Callback function when the delete button is clicked',
39
- table: { type: { summary: 'function' } },
40
- },
41
- onDownload: {
42
- action: 'onDownload',
43
- description: 'Callback function when the download button is clicked',
44
- table: { type: { summary: 'function' } },
31
+ menuItems: {
32
+ control: 'object',
33
+ description: 'Items rendered in the overflow (kebab) menu',
34
+ table: { type: { summary: 'MenuItemProps[]' } },
45
35
  },
46
36
  sx: {
47
37
  control: 'object',
@@ -51,23 +41,28 @@ const meta = {
51
41
  },
52
42
  };
53
43
  export default meta;
44
+ const sampleMenuItems = [
45
+ { label: 'Download', icon: 'DownloadSimple', onClick: () => { } },
46
+ {
47
+ label: 'Delete',
48
+ icon: 'Trash',
49
+ onClick: () => { },
50
+ destructive: true,
51
+ },
52
+ ];
54
53
  export const Default = {
55
54
  args: {
56
- fileName: 'example.pdf',
55
+ fileName: 'File name sample.csv',
57
56
  type: FileType.Default,
58
- showDelete: true,
59
- showDownload: true,
60
- onDelete: () => { },
61
- onDownload: () => { },
57
+ showMenu: true,
58
+ menuItems: sampleMenuItems,
62
59
  },
63
60
  };
64
61
  export const Sample = {
65
62
  args: {
66
- fileName: 'sample.txt',
63
+ fileName: 'File name sample.csv',
67
64
  type: FileType.Sample,
68
- showDelete: false,
69
- showDownload: false,
70
- onDelete: () => { },
71
- onDownload: () => { },
65
+ showMenu: true,
66
+ menuItems: sampleMenuItems,
72
67
  },
73
68
  };
@@ -1,5 +1,5 @@
1
1
  import { SxProps, Theme } from '@mui/material';
2
- import { HintBubbleProps } from '../UI';
2
+ import { HintBubbleProps, MenuItemProps } from '../UI';
3
3
  import { IconName } from '../../utils';
4
4
  import React from 'react';
5
5
  /**
@@ -165,14 +165,10 @@ export interface UploadedFileProps {
165
165
  type?: FileType;
166
166
  /** File name */
167
167
  fileName: string;
168
- /** Whether to show the delete button */
169
- showDelete?: boolean;
170
- /** Callback function when the delete button is clicked */
171
- onDelete?: () => void;
172
- /** Whether to show the download button */
173
- showDownload?: boolean;
174
- /** Callback function when the download button is clicked */
175
- onDownload?: () => void;
168
+ /** Whether to show the trailing overflow-menu (kebab) button */
169
+ showMenu?: boolean;
170
+ /** Items rendered in the overflow (kebab) menu, e.g. Download / Delete */
171
+ menuItems?: MenuItemProps[];
176
172
  /** Style of the uploaded file */
177
173
  sx?: SxProps<Theme>;
178
174
  }
@@ -4,14 +4,16 @@ import { Fragment, useState } from 'react';
4
4
  import { MainButton, ButtonDisplayMode, ButtonHierarchy, ButtonType, ButtonSize, } from '../Buttons';
5
5
  import { Typography, TypographySize, TypographyWeight } from '../Typography';
6
6
  import { getIconComponent } from '../../utils';
7
- export const Menu = ({ icon = 'DotsThreeOutline', iconWeight = 'bold', items, style, testKey, size = ButtonSize.xl, hierarchy = ButtonHierarchy.Tertiary, }) => {
7
+ export const Menu = ({ icon = 'DotsThreeOutline', iconWeight = 'bold', items, style, testKey, size = ButtonSize.xl, hierarchy = ButtonHierarchy.Tertiary, onOpenChange, }) => {
8
8
  const [anchorEl, setAnchorEl] = useState(null);
9
9
  const open = Boolean(anchorEl);
10
10
  const handleClick = (event) => {
11
11
  setAnchorEl(event.currentTarget);
12
+ onOpenChange?.(true);
12
13
  };
13
14
  const handleClose = () => {
14
15
  setAnchorEl(null);
16
+ onOpenChange?.(false);
15
17
  };
16
18
  const handleMenuItemClick = (item, event) => {
17
19
  handleClose();
@@ -217,6 +217,8 @@ export type MenuProps = {
217
217
  size?: ButtonSize;
218
218
  /** Hierarchy of the trigger button */
219
219
  hierarchy?: ButtonHierarchy;
220
+ /** Called when the menu opens (true) or closes (false) */
221
+ onOpenChange?: (open: boolean) => void;
220
222
  };
221
223
  /**
222
224
  * Check icon color variants
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beydesign/storybook",
3
3
  "private": false,
4
- "version": "0.2.28",
4
+ "version": "0.2.30",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",