@flipdish/portal-library 7.4.0 → 7.5.1

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 (40) hide show
  1. package/README.md +6 -0
  2. package/dist/components/molecules/Autocomplete/index.cjs.js.map +1 -1
  3. package/dist/components/molecules/Autocomplete/index.d.ts +2 -0
  4. package/dist/components/molecules/Autocomplete/index.js.map +1 -1
  5. package/dist/components/organisms/FileUpload/components/FileDropZone.cjs.js +2 -0
  6. package/dist/components/organisms/FileUpload/components/FileDropZone.cjs.js.map +1 -0
  7. package/dist/components/organisms/FileUpload/components/FileDropZone.d.ts +78 -0
  8. package/dist/components/organisms/FileUpload/components/FileDropZone.js +2 -0
  9. package/dist/components/organisms/FileUpload/components/FileDropZone.js.map +1 -0
  10. package/dist/components/organisms/FileUpload/components/FileItem.cjs.js +2 -0
  11. package/dist/components/organisms/FileUpload/components/FileItem.cjs.js.map +1 -0
  12. package/dist/components/organisms/FileUpload/components/FileItem.d.ts +12 -0
  13. package/dist/components/organisms/FileUpload/components/FileItem.js +2 -0
  14. package/dist/components/organisms/FileUpload/components/FileItem.js.map +1 -0
  15. package/dist/components/organisms/FileUpload/components/FileThumbnail.cjs.js +2 -0
  16. package/dist/components/organisms/FileUpload/components/FileThumbnail.cjs.js.map +1 -0
  17. package/dist/components/organisms/FileUpload/components/FileThumbnail.d.ts +9 -0
  18. package/dist/components/organisms/FileUpload/components/FileThumbnail.js +2 -0
  19. package/dist/components/organisms/FileUpload/components/FileThumbnail.js.map +1 -0
  20. package/dist/components/organisms/FileUpload/index.cjs.js +2 -0
  21. package/dist/components/organisms/FileUpload/index.cjs.js.map +1 -0
  22. package/dist/components/organisms/FileUpload/index.d.ts +78 -0
  23. package/dist/components/organisms/FileUpload/index.js +2 -0
  24. package/dist/components/organisms/FileUpload/index.js.map +1 -0
  25. package/dist/components/organisms/SummaryList/components/SummaryListCell.cjs.js +2 -0
  26. package/dist/components/organisms/SummaryList/components/SummaryListCell.cjs.js.map +1 -0
  27. package/dist/components/organisms/SummaryList/components/SummaryListCell.d.ts +28 -0
  28. package/dist/components/organisms/SummaryList/components/SummaryListCell.js +2 -0
  29. package/dist/components/organisms/SummaryList/components/SummaryListCell.js.map +1 -0
  30. package/dist/components/organisms/SummaryList/components/SummaryListRow.cjs.js +2 -0
  31. package/dist/components/organisms/SummaryList/components/SummaryListRow.cjs.js.map +1 -0
  32. package/dist/components/organisms/SummaryList/components/SummaryListRow.d.ts +18 -0
  33. package/dist/components/organisms/SummaryList/components/SummaryListRow.js +2 -0
  34. package/dist/components/organisms/SummaryList/components/SummaryListRow.js.map +1 -0
  35. package/dist/components/organisms/SummaryList/index.cjs.js +2 -0
  36. package/dist/components/organisms/SummaryList/index.cjs.js.map +1 -0
  37. package/dist/components/organisms/SummaryList/index.d.ts +34 -0
  38. package/dist/components/organisms/SummaryList/index.js +2 -0
  39. package/dist/components/organisms/SummaryList/index.js.map +1 -0
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -76,6 +76,12 @@ import FlipdishUIThemeProvider from '@flipdish/portal-library/themes/ThemeProvid
76
76
  </FlipdishUIThemeProvider>;
77
77
  ```
78
78
 
79
+ ## Contributing
80
+
81
+ Start with the contributing guide for editor setup, conventions, and component guidelines:
82
+
83
+ - [CONTRIBUTING.md](./CONTRIBUTING.md)
84
+
79
85
  ## Using Icons
80
86
 
81
87
  To use an icon in your project, import the icon and use it like any other component. You can specify the size using the `size` prop.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../../../src/components/molecules/Autocomplete/index.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nimport MuiAutocomplete, {\n type AutocompleteChangeDetails,\n type AutocompleteChangeReason,\n type AutocompleteInputChangeReason,\n type AutocompleteProps as MuiAutocompleteProps,\n type AutocompleteRenderGetTagProps,\n type AutocompleteRenderInputParams,\n type AutocompleteRenderOptionState,\n type AutocompleteValue,\n} from '@mui/material/Autocomplete';\nimport Box from '@mui/material/Box';\nimport { styled } from '@mui/material/styles';\n// TODO: Replace with our own Tooltip component\nimport MuiTooltip from '@mui/material/Tooltip';\n\nimport ArrowDown01Icon from '@fd/icons/ArrowDown01';\nimport ArrowUp01Icon from '@fd/icons/ArrowUp01';\nimport CancelIcon from '@fd/icons/Cancel';\nimport SearchIcon from '@fd/icons/Search';\nimport { truncateWithEllipsis } from '@fd/utilities/stringUtilities';\n\nimport CircularProgress from '../../atoms/CircularProgress';\nimport MenuItem from '../../atoms/MenuItem';\nimport Tag from '../../atoms/Tag';\nimport TextField, { type TextFieldProps } from '../../atoms/TextField';\nimport { useDynamicLimitTags } from './hooks/useDynamicLimitTags';\n\n// Bind flags to MuiAutocomplete\ntype MuiCustomAutocompleteProps<\n M extends boolean = boolean, // Multiple\n DC extends boolean = boolean, // DisableClearable\n FS extends boolean = boolean, // FreeSolo\n> = MuiAutocompleteProps<AutocompleteOption, M, DC, FS>;\n\n/**\n * Represents an option in the Autocomplete dropdown.\n */\nexport interface AutocompleteOption {\n label: string;\n value: number | string;\n}\n\n/**\n * Props for the Autocomplete component.\n * Supports both single and multiple selection modes with optional search functionality.\n */\nexport interface AutocompleteProps\n extends Omit<TextFieldProps, 'endAdornment' | 'multiline' | 'onChange' | 'options' | 'startAdornment'> {\n /** Shows a loading indicator inside the input (e.g., while fetching). */\n loading?: boolean;\n /** Enables selection of multiple options and shows checkboxes and tags for selected values. */\n multiple?: MuiCustomAutocompleteProps['multiple'];\n /** Called when the selected value(s) change. */\n onChange: MuiCustomAutocompleteProps['onChange'];\n /** Called when the input text changes (typing or programmatic updates). */\n onInputChange?: MuiCustomAutocompleteProps['onInputChange'];\n /** Options to display in the dropdown list. */\n options: AutocompleteOption[];\n /** UI and accessibility labels used by the component. */\n translations: {\n /** Aria-label for the clear button. */\n clearTextAriaLabel: string;\n /** Aria-label for the dismiss tag button. */\n dismissTagAriaLabel: string;\n /** Text shown while loading options. */\n loadingText: string;\n /** Text shown when there are no options to display. */\n noOptionsText: string;\n /** Aria-label for the popup indicator button. */\n openPopupAriaLabel: string;\n };\n /** Visual behavior: 'search' shows a leading search icon; 'combobox' shows a popup button. */\n type?: 'combobox' | 'search';\n /** Controlled value (single option, array for multiple, or null). */\n value: MuiCustomAutocompleteProps['value'];\n}\n\n// Bind flags to MuiAutocomplete, see MuiCustomAutocompleteProps for more details\nconst MuiCustomAutocomplete = MuiAutocomplete<AutocompleteOption, boolean, boolean, boolean>;\nconst StyledAutocomplete = styled(MuiCustomAutocomplete, {\n shouldForwardProp: (prop) => prop !== 'fullWidth',\n})<{ fullWidth?: boolean }>(({ fullWidth = false }) => ({\n width: fullWidth ? '100%' : 'min(364px, 100%)',\n}));\n\nconst StyledAdornment = styled(Box, {\n shouldForwardProp: (prop) => !['clickable', 'disabled', 'placement'].includes(prop as string),\n})<{ clickable?: boolean; error?: boolean; disabled?: boolean; placement: 'end' | 'middle' | 'start' }>(\n ({ theme, clickable = false, disabled = false, placement }) => ({\n alignItems: 'center',\n cursor: clickable ? (disabled ? 'not-allowed' : 'pointer') : 'default',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n pointerEvents: clickable ? 'auto' : 'none',\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n\n '& svg': {\n color: disabled\n ? theme.palette.semantic.icon['icon-disabled']\n : theme.palette.semantic.icon['icon-strong'],\n },\n }),\n);\n\nconst StyledLoadingSpinnerContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'placement',\n})<{ placement?: 'end' | 'middle' | 'start' }>(({ theme, placement }) => ({\n alignItems: 'center',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n}));\n\n/**\n * A customizable Autocomplete component supporting search and combobox modes.\n * Supports single and multiple selection with dynamic tag rendering and optional loading states.\n *\n * @param props - The component props\n * @returns The rendered Autocomplete component\n */\nexport const Autocomplete = ({\n className,\n disabled = false,\n errorText,\n fdKey,\n fullWidth = false,\n helperText,\n label,\n loading = false,\n multiple = false,\n placeholder,\n onChange,\n onInputChange,\n options = [],\n required = false,\n translations,\n type = 'search',\n value,\n}: AutocompleteProps): JSX.Element => {\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const isSearch = type === 'search';\n const isCombobox = type === 'combobox';\n\n const { rootRef, limitTags } = useDynamicLimitTags({\n chipMax: 130,\n gap: 4,\n horizontalPadding: 32,\n reservedPx: isSearch ? 120 : 90,\n });\n\n // Close the dropdown (if open) when the component is dynamically disabled\n useEffect(() => {\n if (disabled) {\n setIsOpen(false);\n }\n }, [disabled]);\n\n /**\n * Toggles or sets the dropdown open state.\n * Does nothing when the component is disabled.\n *\n * @param open - Optional boolean to explicitly set open state; if undefined, toggles current state\n */\n const toggleOpen = (open?: boolean): void => {\n if (disabled) {\n return;\n }\n\n if (open !== undefined) {\n setIsOpen(open);\n return;\n }\n\n setIsOpen((prevOpen) => !prevOpen);\n };\n\n const renderSearchIcon = (): JSX.Element => {\n return (\n <StyledAdornment disabled={disabled} placement=\"start\">\n <SearchIcon aria-hidden=\"true\" />\n </StyledAdornment>\n );\n };\n\n const renderPopupIcon = (): JSX.Element => {\n return (\n <StyledAdornment clickable disabled={disabled} placement=\"end\">\n {isOpen ? <ArrowUp01Icon /> : <ArrowDown01Icon />}\n </StyledAdornment>\n );\n };\n\n const renderLoadingSpinner = (): JSX.Element => {\n return (\n <StyledLoadingSpinnerContainer placement={isSearch ? 'end' : 'middle'}>\n <CircularProgress size=\"small\" />\n </StyledLoadingSpinnerContainer>\n );\n };\n\n const renderInputField = (params: AutocompleteRenderInputParams): JSX.Element => (\n <TextField\n disabled={disabled}\n errorText={errorText}\n fdKey={fdKey}\n fullWidth={fullWidth}\n helperText={helperText}\n label={label}\n placeholder={placeholder}\n required={required}\n slotProps={{\n input: {\n ...params.InputProps,\n endAdornment: (\n <>\n {loading ? renderLoadingSpinner() : null}\n {params.InputProps.endAdornment}\n </>\n ),\n startAdornment: (\n <>\n {isSearch ? renderSearchIcon() : null}\n {params.InputProps.startAdornment}\n </>\n ),\n },\n htmlInput: {\n ...params.inputProps,\n },\n }}\n />\n );\n\n const renderMenuOption = (\n optionProps: React.HTMLAttributes<HTMLLIElement> & { key: React.Key },\n option: AutocompleteOption,\n state: AutocompleteRenderOptionState,\n ): JSX.Element => {\n const { key, onClick, ...rest } = optionProps;\n\n if (multiple) {\n return (\n <MenuItem\n {...rest}\n key={key}\n checked={state.selected}\n label={option.label}\n onCheckedChange={(_checked, e) => {\n onClick?.(e as React.MouseEvent<HTMLLIElement, MouseEvent>);\n }}\n type=\"checkbox\"\n />\n );\n }\n\n return (\n <MenuItem\n {...rest}\n key={key}\n label={option.label}\n onClick={onClick}\n selected={state.selected}\n type=\"text\"\n />\n );\n };\n\n const renderSelectedTags = (\n selected: AutocompleteOption[],\n getTagProps: AutocompleteRenderGetTagProps,\n ): React.ReactNode =>\n selected.map((tag, index) => {\n const { key, onDelete, ...tagProps } = getTagProps({ index });\n const rawLabel = typeof tag === 'string' ? tag : tag.label;\n const label = truncateWithEllipsis(rawLabel, 13, { includeEllipsisInLimit: true });\n const isTruncated = label.endsWith('...');\n const tagKey = key ?? `tag-${typeof tag === 'string' ? tag : tag.value}`;\n\n if (isTruncated) {\n return (\n <MuiTooltip key={tagKey} arrow placement=\"top\" title={rawLabel}>\n <span {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n </MuiTooltip>\n );\n }\n\n return (\n <span key={tagKey} {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n );\n });\n\n const handlePopupIndicatorClick = (event: React.MouseEvent): void => {\n if (disabled) {\n return;\n }\n\n event.stopPropagation();\n toggleOpen();\n };\n\n const handleChange = (\n event: React.SyntheticEvent,\n value: AutocompleteValue<AutocompleteOption, boolean, boolean, boolean>,\n reason: AutocompleteChangeReason,\n details?: AutocompleteChangeDetails<AutocompleteOption>,\n ): void => {\n if (!onChange) {\n return;\n }\n onChange(event, value, reason, details);\n };\n\n const handleInputChange = (\n event: React.SyntheticEvent,\n value: string,\n reason: AutocompleteInputChangeReason,\n ): void => {\n if (!onInputChange) {\n return;\n }\n onInputChange(event, value, reason);\n };\n\n /**\n * Returns all options without filtering for search mode.\n * Disables MUI's built-in client-side filtering when type is 'search'.\n */\n const disableClientFilter = (opts: AutocompleteOption[]): AutocompleteOption[] => opts;\n\n /**\n * Extracts the display label from an option.\n * Supports both object options and freeSolo string values.\n */\n const getOptionLabel = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : option.label;\n };\n\n /**\n * Provides a stable unique key for each option to optimize DOM reconciliation.\n */\n const getOptionKey = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : String(option.value);\n };\n\n /**\n * Determines equality between an option and the current value.\n * Handles both freeSolo string values and object options to prevent selection glitches.\n */\n const getIsOptionEqualToValue = (a: AutocompleteOption, b: AutocompleteOption): boolean => {\n if (b === null || b === undefined) {\n return false;\n }\n return typeof b === 'string' ? a.label === b || String(a.value) === b : a.value === b.value;\n };\n\n /**\n * Normalizes the value prop for MUI Autocomplete.\n * Handles both single and multiple selection modes, ensuring the value\n * format matches the mode (array for multiple, single value/null for single).\n *\n * @returns Normalized value compatible with MUI Autocomplete\n */\n const getValue = (): AutocompleteProps['value'] => {\n // Multiple value selection\n if (multiple) {\n if (Array.isArray(value)) return value;\n if (value === null || value === undefined) return [];\n return [value as AutocompleteOption];\n }\n\n // Single value selection\n if (Array.isArray(value)) return value.length > 0 ? value[0] : null;\n return value ?? null;\n };\n\n return (\n <StyledAutocomplete\n ref={rootRef}\n className={className}\n clearIcon={<CancelIcon size=\"md\" />}\n clearText={translations.clearTextAriaLabel}\n disableClearable={disabled || loading}\n disableCloseOnSelect={multiple}\n disabled={disabled}\n filterOptions={isSearch ? disableClientFilter : undefined}\n forcePopupIcon={isCombobox}\n freeSolo={isSearch}\n fullWidth={fullWidth}\n getOptionKey={getOptionKey}\n getOptionLabel={getOptionLabel}\n isOptionEqualToValue={getIsOptionEqualToValue}\n limitTags={limitTags}\n loading={loading}\n loadingText={translations.loadingText}\n multiple={multiple}\n noOptionsText={translations.noOptionsText}\n onChange={handleChange}\n onClose={() => toggleOpen(false)}\n onInputChange={handleInputChange}\n onOpen={() => toggleOpen(true)}\n open={isOpen}\n openText={translations.openPopupAriaLabel}\n options={options}\n popupIcon={isSearch ? null : renderPopupIcon()}\n renderInput={renderInputField}\n renderOption={renderMenuOption}\n renderTags={renderSelectedTags}\n slotProps={{\n popupIndicator: {\n onClick: handlePopupIndicatorClick,\n },\n }}\n value={getValue()}\n />\n );\n};\n\nexport default Autocomplete;\n"],"names":["MuiCustomAutocomplete","StyledAutocomplete","styled","shouldForwardProp","prop","fullWidth","width","StyledAdornment","Box","includes","theme","clickable","disabled","placement","alignItems","cursor","display","justifyContent","padding","pointerEvents","paddingRight","spacing","color","palette","semantic","icon","StyledLoadingSpinnerContainer","Autocomplete","className","errorText","fdKey","helperText","label","loading","multiple","placeholder","onChange","onInputChange","options","required","translations","type","value","isOpen","setIsOpen","useState","isSearch","isCombobox","rootRef","limitTags","useDynamicLimitTags","chipMax","gap","horizontalPadding","reservedPx","useEffect","toggleOpen","open","undefined","prevOpen","_jsx","ref","clearIcon","CancelIcon","size","clearText","clearTextAriaLabel","disableClearable","disableCloseOnSelect","filterOptions","opts","forcePopupIcon","freeSolo","getOptionKey","option","String","getOptionLabel","isOptionEqualToValue","a","b","loadingText","noOptionsText","event","reason","details","onClose","onOpen","openText","openPopupAriaLabel","popupIcon","children","ArrowUp01Icon","ArrowDown01Icon","renderInput","params","TextField","slotProps","input","InputProps","endAdornment","_jsxs","_Fragment","CircularProgress","startAdornment","SearchIcon","htmlInput","inputProps","renderOption","optionProps","state","key","onClick","rest","_createElement","MenuItem","checked","selected","onCheckedChange","_checked","e","renderTags","getTagProps","map","tag","index","onDelete","tagProps","rawLabel","truncateWithEllipsis","includeEllipsisInLimit","isTruncated","endsWith","tagKey","MuiTooltip","arrow","title","Tag","dismissAriaLabel","dismissTagAriaLabel","onDismiss","popupIndicator","stopPropagation","Array","isArray","length"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../../src/components/molecules/Autocomplete/index.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nimport MuiAutocomplete, {\n type AutocompleteChangeDetails,\n type AutocompleteChangeReason,\n type AutocompleteInputChangeReason,\n type AutocompleteProps as MuiAutocompleteProps,\n type AutocompleteRenderGetTagProps,\n type AutocompleteRenderInputParams,\n type AutocompleteRenderOptionState,\n type AutocompleteValue,\n} from '@mui/material/Autocomplete';\nimport Box from '@mui/material/Box';\nimport { styled } from '@mui/material/styles';\n// TODO: Replace with our own Tooltip component\nimport MuiTooltip from '@mui/material/Tooltip';\n\nimport ArrowDown01Icon from '@fd/icons/ArrowDown01';\nimport ArrowUp01Icon from '@fd/icons/ArrowUp01';\nimport CancelIcon from '@fd/icons/Cancel';\nimport SearchIcon from '@fd/icons/Search';\nimport { truncateWithEllipsis } from '@fd/utilities/stringUtilities';\n\nimport CircularProgress from '../../atoms/CircularProgress';\nimport MenuItem from '../../atoms/MenuItem';\nimport Tag from '../../atoms/Tag';\nimport TextField, { type TextFieldProps } from '../../atoms/TextField';\nimport { useDynamicLimitTags } from './hooks/useDynamicLimitTags';\n\n// Bind flags to MuiAutocomplete\ntype MuiCustomAutocompleteProps<\n M extends boolean = boolean, // Multiple\n DC extends boolean = boolean, // DisableClearable\n FS extends boolean = boolean, // FreeSolo\n> = MuiAutocompleteProps<AutocompleteOption, M, DC, FS>;\n\n/**\n * Represents an option in the Autocomplete dropdown.\n */\nexport interface AutocompleteOption {\n /** The label to display in the dropdown option. */\n label: string;\n /** The value to store in the dropdown option. */\n value: number | string;\n}\n\n/**\n * Props for the Autocomplete component.\n * Supports both single and multiple selection modes with optional search functionality.\n */\nexport interface AutocompleteProps\n extends Omit<TextFieldProps, 'endAdornment' | 'multiline' | 'onChange' | 'options' | 'startAdornment'> {\n /** Shows a loading indicator inside the input (e.g., while fetching). */\n loading?: boolean;\n /** Enables selection of multiple options and shows checkboxes and tags for selected values. */\n multiple?: MuiCustomAutocompleteProps['multiple'];\n /** Called when the selected value(s) change. */\n onChange: MuiCustomAutocompleteProps['onChange'];\n /** Called when the input text changes (typing or programmatic updates). */\n onInputChange?: MuiCustomAutocompleteProps['onInputChange'];\n /** Options to display in the dropdown list. */\n options: AutocompleteOption[];\n /** UI and accessibility labels used by the component. */\n translations: {\n /** Aria-label for the clear button. */\n clearTextAriaLabel: string;\n /** Aria-label for the dismiss tag button. */\n dismissTagAriaLabel: string;\n /** Text shown while loading options. */\n loadingText: string;\n /** Text shown when there are no options to display. */\n noOptionsText: string;\n /** Aria-label for the popup indicator button. */\n openPopupAriaLabel: string;\n };\n /** Visual behavior: 'search' shows a leading search icon; 'combobox' shows a popup button. */\n type?: 'combobox' | 'search';\n /** Controlled value (single option, array for multiple, or null). */\n value: MuiCustomAutocompleteProps['value'];\n}\n\n// Bind flags to MuiAutocomplete, see MuiCustomAutocompleteProps for more details\nconst MuiCustomAutocomplete = MuiAutocomplete<AutocompleteOption, boolean, boolean, boolean>;\nconst StyledAutocomplete = styled(MuiCustomAutocomplete, {\n shouldForwardProp: (prop) => prop !== 'fullWidth',\n})<{ fullWidth?: boolean }>(({ fullWidth = false }) => ({\n width: fullWidth ? '100%' : 'min(364px, 100%)',\n}));\n\nconst StyledAdornment = styled(Box, {\n shouldForwardProp: (prop) => !['clickable', 'disabled', 'placement'].includes(prop as string),\n})<{ clickable?: boolean; error?: boolean; disabled?: boolean; placement: 'end' | 'middle' | 'start' }>(\n ({ theme, clickable = false, disabled = false, placement }) => ({\n alignItems: 'center',\n cursor: clickable ? (disabled ? 'not-allowed' : 'pointer') : 'default',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n pointerEvents: clickable ? 'auto' : 'none',\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n\n '& svg': {\n color: disabled\n ? theme.palette.semantic.icon['icon-disabled']\n : theme.palette.semantic.icon['icon-strong'],\n },\n }),\n);\n\nconst StyledLoadingSpinnerContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'placement',\n})<{ placement?: 'end' | 'middle' | 'start' }>(({ theme, placement }) => ({\n alignItems: 'center',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n}));\n\n/**\n * A customizable Autocomplete component supporting search and combobox modes.\n * Supports single and multiple selection with dynamic tag rendering and optional loading states.\n *\n * @param props - The component props\n * @returns The rendered Autocomplete component\n */\nexport const Autocomplete = ({\n className,\n disabled = false,\n errorText,\n fdKey,\n fullWidth = false,\n helperText,\n label,\n loading = false,\n multiple = false,\n placeholder,\n onChange,\n onInputChange,\n options = [],\n required = false,\n translations,\n type = 'search',\n value,\n}: AutocompleteProps): JSX.Element => {\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const isSearch = type === 'search';\n const isCombobox = type === 'combobox';\n\n const { rootRef, limitTags } = useDynamicLimitTags({\n chipMax: 130,\n gap: 4,\n horizontalPadding: 32,\n reservedPx: isSearch ? 120 : 90,\n });\n\n // Close the dropdown (if open) when the component is dynamically disabled\n useEffect(() => {\n if (disabled) {\n setIsOpen(false);\n }\n }, [disabled]);\n\n /**\n * Toggles or sets the dropdown open state.\n * Does nothing when the component is disabled.\n *\n * @param open - Optional boolean to explicitly set open state; if undefined, toggles current state\n */\n const toggleOpen = (open?: boolean): void => {\n if (disabled) {\n return;\n }\n\n if (open !== undefined) {\n setIsOpen(open);\n return;\n }\n\n setIsOpen((prevOpen) => !prevOpen);\n };\n\n const renderSearchIcon = (): JSX.Element => {\n return (\n <StyledAdornment disabled={disabled} placement=\"start\">\n <SearchIcon aria-hidden=\"true\" />\n </StyledAdornment>\n );\n };\n\n const renderPopupIcon = (): JSX.Element => {\n return (\n <StyledAdornment clickable disabled={disabled} placement=\"end\">\n {isOpen ? <ArrowUp01Icon /> : <ArrowDown01Icon />}\n </StyledAdornment>\n );\n };\n\n const renderLoadingSpinner = (): JSX.Element => {\n return (\n <StyledLoadingSpinnerContainer placement={isSearch ? 'end' : 'middle'}>\n <CircularProgress size=\"small\" />\n </StyledLoadingSpinnerContainer>\n );\n };\n\n const renderInputField = (params: AutocompleteRenderInputParams): JSX.Element => (\n <TextField\n disabled={disabled}\n errorText={errorText}\n fdKey={fdKey}\n fullWidth={fullWidth}\n helperText={helperText}\n label={label}\n placeholder={placeholder}\n required={required}\n slotProps={{\n input: {\n ...params.InputProps,\n endAdornment: (\n <>\n {loading ? renderLoadingSpinner() : null}\n {params.InputProps.endAdornment}\n </>\n ),\n startAdornment: (\n <>\n {isSearch ? renderSearchIcon() : null}\n {params.InputProps.startAdornment}\n </>\n ),\n },\n htmlInput: {\n ...params.inputProps,\n },\n }}\n />\n );\n\n const renderMenuOption = (\n optionProps: React.HTMLAttributes<HTMLLIElement> & { key: React.Key },\n option: AutocompleteOption,\n state: AutocompleteRenderOptionState,\n ): JSX.Element => {\n const { key, onClick, ...rest } = optionProps;\n\n if (multiple) {\n return (\n <MenuItem\n {...rest}\n key={key}\n checked={state.selected}\n label={option.label}\n onCheckedChange={(_checked, e) => {\n onClick?.(e as React.MouseEvent<HTMLLIElement, MouseEvent>);\n }}\n type=\"checkbox\"\n />\n );\n }\n\n return (\n <MenuItem\n {...rest}\n key={key}\n label={option.label}\n onClick={onClick}\n selected={state.selected}\n type=\"text\"\n />\n );\n };\n\n const renderSelectedTags = (\n selected: AutocompleteOption[],\n getTagProps: AutocompleteRenderGetTagProps,\n ): React.ReactNode =>\n selected.map((tag, index) => {\n const { key, onDelete, ...tagProps } = getTagProps({ index });\n const rawLabel = typeof tag === 'string' ? tag : tag.label;\n const label = truncateWithEllipsis(rawLabel, 13, { includeEllipsisInLimit: true });\n const isTruncated = label.endsWith('...');\n const tagKey = key ?? `tag-${typeof tag === 'string' ? tag : tag.value}`;\n\n if (isTruncated) {\n return (\n <MuiTooltip key={tagKey} arrow placement=\"top\" title={rawLabel}>\n <span {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n </MuiTooltip>\n );\n }\n\n return (\n <span key={tagKey} {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n );\n });\n\n const handlePopupIndicatorClick = (event: React.MouseEvent): void => {\n if (disabled) {\n return;\n }\n\n event.stopPropagation();\n toggleOpen();\n };\n\n const handleChange = (\n event: React.SyntheticEvent,\n value: AutocompleteValue<AutocompleteOption, boolean, boolean, boolean>,\n reason: AutocompleteChangeReason,\n details?: AutocompleteChangeDetails<AutocompleteOption>,\n ): void => {\n if (!onChange) {\n return;\n }\n onChange(event, value, reason, details);\n };\n\n const handleInputChange = (\n event: React.SyntheticEvent,\n value: string,\n reason: AutocompleteInputChangeReason,\n ): void => {\n if (!onInputChange) {\n return;\n }\n onInputChange(event, value, reason);\n };\n\n /**\n * Returns all options without filtering for search mode.\n * Disables MUI's built-in client-side filtering when type is 'search'.\n */\n const disableClientFilter = (opts: AutocompleteOption[]): AutocompleteOption[] => opts;\n\n /**\n * Extracts the display label from an option.\n * Supports both object options and freeSolo string values.\n */\n const getOptionLabel = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : option.label;\n };\n\n /**\n * Provides a stable unique key for each option to optimize DOM reconciliation.\n */\n const getOptionKey = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : String(option.value);\n };\n\n /**\n * Determines equality between an option and the current value.\n * Handles both freeSolo string values and object options to prevent selection glitches.\n */\n const getIsOptionEqualToValue = (a: AutocompleteOption, b: AutocompleteOption): boolean => {\n if (b === null || b === undefined) {\n return false;\n }\n return typeof b === 'string' ? a.label === b || String(a.value) === b : a.value === b.value;\n };\n\n /**\n * Normalizes the value prop for MUI Autocomplete.\n * Handles both single and multiple selection modes, ensuring the value\n * format matches the mode (array for multiple, single value/null for single).\n *\n * @returns Normalized value compatible with MUI Autocomplete\n */\n const getValue = (): AutocompleteProps['value'] => {\n // Multiple value selection\n if (multiple) {\n if (Array.isArray(value)) return value;\n if (value === null || value === undefined) return [];\n return [value as AutocompleteOption];\n }\n\n // Single value selection\n if (Array.isArray(value)) return value.length > 0 ? value[0] : null;\n return value ?? null;\n };\n\n return (\n <StyledAutocomplete\n ref={rootRef}\n className={className}\n clearIcon={<CancelIcon size=\"md\" />}\n clearText={translations.clearTextAriaLabel}\n disableClearable={disabled || loading}\n disableCloseOnSelect={multiple}\n disabled={disabled}\n filterOptions={isSearch ? disableClientFilter : undefined}\n forcePopupIcon={isCombobox}\n freeSolo={isSearch}\n fullWidth={fullWidth}\n getOptionKey={getOptionKey}\n getOptionLabel={getOptionLabel}\n isOptionEqualToValue={getIsOptionEqualToValue}\n limitTags={limitTags}\n loading={loading}\n loadingText={translations.loadingText}\n multiple={multiple}\n noOptionsText={translations.noOptionsText}\n onChange={handleChange}\n onClose={() => toggleOpen(false)}\n onInputChange={handleInputChange}\n onOpen={() => toggleOpen(true)}\n open={isOpen}\n openText={translations.openPopupAriaLabel}\n options={options}\n popupIcon={isSearch ? null : renderPopupIcon()}\n renderInput={renderInputField}\n renderOption={renderMenuOption}\n renderTags={renderSelectedTags}\n slotProps={{\n popupIndicator: {\n onClick: handlePopupIndicatorClick,\n },\n }}\n value={getValue()}\n />\n );\n};\n\nexport default Autocomplete;\n"],"names":["MuiCustomAutocomplete","StyledAutocomplete","styled","shouldForwardProp","prop","fullWidth","width","StyledAdornment","Box","includes","theme","clickable","disabled","placement","alignItems","cursor","display","justifyContent","padding","pointerEvents","paddingRight","spacing","color","palette","semantic","icon","StyledLoadingSpinnerContainer","Autocomplete","className","errorText","fdKey","helperText","label","loading","multiple","placeholder","onChange","onInputChange","options","required","translations","type","value","isOpen","setIsOpen","useState","isSearch","isCombobox","rootRef","limitTags","useDynamicLimitTags","chipMax","gap","horizontalPadding","reservedPx","useEffect","toggleOpen","open","undefined","prevOpen","_jsx","ref","clearIcon","CancelIcon","size","clearText","clearTextAriaLabel","disableClearable","disableCloseOnSelect","filterOptions","opts","forcePopupIcon","freeSolo","getOptionKey","option","String","getOptionLabel","isOptionEqualToValue","a","b","loadingText","noOptionsText","event","reason","details","onClose","onOpen","openText","openPopupAriaLabel","popupIcon","children","ArrowUp01Icon","ArrowDown01Icon","renderInput","params","TextField","slotProps","input","InputProps","endAdornment","_jsxs","_Fragment","CircularProgress","startAdornment","SearchIcon","htmlInput","inputProps","renderOption","optionProps","state","key","onClick","rest","_createElement","MenuItem","checked","selected","onCheckedChange","_checked","e","renderTags","getTagProps","map","tag","index","onDelete","tagProps","rawLabel","truncateWithEllipsis","includeEllipsisInLimit","isTruncated","endsWith","tagKey","MuiTooltip","arrow","title","Tag","dismissAriaLabel","dismissTagAriaLabel","onDismiss","popupIndicator","stopPropagation","Array","isArray","length"],"mappings":"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"}
@@ -8,7 +8,9 @@ FS extends boolean = boolean> = AutocompleteProps$1<AutocompleteOption, M, DC, F
8
8
  * Represents an option in the Autocomplete dropdown.
9
9
  */
10
10
  interface AutocompleteOption {
11
+ /** The label to display in the dropdown option. */
11
12
  label: string;
13
+ /** The value to store in the dropdown option. */
12
14
  value: number | string;
13
15
  }
14
16
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/molecules/Autocomplete/index.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nimport MuiAutocomplete, {\n type AutocompleteChangeDetails,\n type AutocompleteChangeReason,\n type AutocompleteInputChangeReason,\n type AutocompleteProps as MuiAutocompleteProps,\n type AutocompleteRenderGetTagProps,\n type AutocompleteRenderInputParams,\n type AutocompleteRenderOptionState,\n type AutocompleteValue,\n} from '@mui/material/Autocomplete';\nimport Box from '@mui/material/Box';\nimport { styled } from '@mui/material/styles';\n// TODO: Replace with our own Tooltip component\nimport MuiTooltip from '@mui/material/Tooltip';\n\nimport ArrowDown01Icon from '@fd/icons/ArrowDown01';\nimport ArrowUp01Icon from '@fd/icons/ArrowUp01';\nimport CancelIcon from '@fd/icons/Cancel';\nimport SearchIcon from '@fd/icons/Search';\nimport { truncateWithEllipsis } from '@fd/utilities/stringUtilities';\n\nimport CircularProgress from '../../atoms/CircularProgress';\nimport MenuItem from '../../atoms/MenuItem';\nimport Tag from '../../atoms/Tag';\nimport TextField, { type TextFieldProps } from '../../atoms/TextField';\nimport { useDynamicLimitTags } from './hooks/useDynamicLimitTags';\n\n// Bind flags to MuiAutocomplete\ntype MuiCustomAutocompleteProps<\n M extends boolean = boolean, // Multiple\n DC extends boolean = boolean, // DisableClearable\n FS extends boolean = boolean, // FreeSolo\n> = MuiAutocompleteProps<AutocompleteOption, M, DC, FS>;\n\n/**\n * Represents an option in the Autocomplete dropdown.\n */\nexport interface AutocompleteOption {\n label: string;\n value: number | string;\n}\n\n/**\n * Props for the Autocomplete component.\n * Supports both single and multiple selection modes with optional search functionality.\n */\nexport interface AutocompleteProps\n extends Omit<TextFieldProps, 'endAdornment' | 'multiline' | 'onChange' | 'options' | 'startAdornment'> {\n /** Shows a loading indicator inside the input (e.g., while fetching). */\n loading?: boolean;\n /** Enables selection of multiple options and shows checkboxes and tags for selected values. */\n multiple?: MuiCustomAutocompleteProps['multiple'];\n /** Called when the selected value(s) change. */\n onChange: MuiCustomAutocompleteProps['onChange'];\n /** Called when the input text changes (typing or programmatic updates). */\n onInputChange?: MuiCustomAutocompleteProps['onInputChange'];\n /** Options to display in the dropdown list. */\n options: AutocompleteOption[];\n /** UI and accessibility labels used by the component. */\n translations: {\n /** Aria-label for the clear button. */\n clearTextAriaLabel: string;\n /** Aria-label for the dismiss tag button. */\n dismissTagAriaLabel: string;\n /** Text shown while loading options. */\n loadingText: string;\n /** Text shown when there are no options to display. */\n noOptionsText: string;\n /** Aria-label for the popup indicator button. */\n openPopupAriaLabel: string;\n };\n /** Visual behavior: 'search' shows a leading search icon; 'combobox' shows a popup button. */\n type?: 'combobox' | 'search';\n /** Controlled value (single option, array for multiple, or null). */\n value: MuiCustomAutocompleteProps['value'];\n}\n\n// Bind flags to MuiAutocomplete, see MuiCustomAutocompleteProps for more details\nconst MuiCustomAutocomplete = MuiAutocomplete<AutocompleteOption, boolean, boolean, boolean>;\nconst StyledAutocomplete = styled(MuiCustomAutocomplete, {\n shouldForwardProp: (prop) => prop !== 'fullWidth',\n})<{ fullWidth?: boolean }>(({ fullWidth = false }) => ({\n width: fullWidth ? '100%' : 'min(364px, 100%)',\n}));\n\nconst StyledAdornment = styled(Box, {\n shouldForwardProp: (prop) => !['clickable', 'disabled', 'placement'].includes(prop as string),\n})<{ clickable?: boolean; error?: boolean; disabled?: boolean; placement: 'end' | 'middle' | 'start' }>(\n ({ theme, clickable = false, disabled = false, placement }) => ({\n alignItems: 'center',\n cursor: clickable ? (disabled ? 'not-allowed' : 'pointer') : 'default',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n pointerEvents: clickable ? 'auto' : 'none',\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n\n '& svg': {\n color: disabled\n ? theme.palette.semantic.icon['icon-disabled']\n : theme.palette.semantic.icon['icon-strong'],\n },\n }),\n);\n\nconst StyledLoadingSpinnerContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'placement',\n})<{ placement?: 'end' | 'middle' | 'start' }>(({ theme, placement }) => ({\n alignItems: 'center',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n}));\n\n/**\n * A customizable Autocomplete component supporting search and combobox modes.\n * Supports single and multiple selection with dynamic tag rendering and optional loading states.\n *\n * @param props - The component props\n * @returns The rendered Autocomplete component\n */\nexport const Autocomplete = ({\n className,\n disabled = false,\n errorText,\n fdKey,\n fullWidth = false,\n helperText,\n label,\n loading = false,\n multiple = false,\n placeholder,\n onChange,\n onInputChange,\n options = [],\n required = false,\n translations,\n type = 'search',\n value,\n}: AutocompleteProps): JSX.Element => {\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const isSearch = type === 'search';\n const isCombobox = type === 'combobox';\n\n const { rootRef, limitTags } = useDynamicLimitTags({\n chipMax: 130,\n gap: 4,\n horizontalPadding: 32,\n reservedPx: isSearch ? 120 : 90,\n });\n\n // Close the dropdown (if open) when the component is dynamically disabled\n useEffect(() => {\n if (disabled) {\n setIsOpen(false);\n }\n }, [disabled]);\n\n /**\n * Toggles or sets the dropdown open state.\n * Does nothing when the component is disabled.\n *\n * @param open - Optional boolean to explicitly set open state; if undefined, toggles current state\n */\n const toggleOpen = (open?: boolean): void => {\n if (disabled) {\n return;\n }\n\n if (open !== undefined) {\n setIsOpen(open);\n return;\n }\n\n setIsOpen((prevOpen) => !prevOpen);\n };\n\n const renderSearchIcon = (): JSX.Element => {\n return (\n <StyledAdornment disabled={disabled} placement=\"start\">\n <SearchIcon aria-hidden=\"true\" />\n </StyledAdornment>\n );\n };\n\n const renderPopupIcon = (): JSX.Element => {\n return (\n <StyledAdornment clickable disabled={disabled} placement=\"end\">\n {isOpen ? <ArrowUp01Icon /> : <ArrowDown01Icon />}\n </StyledAdornment>\n );\n };\n\n const renderLoadingSpinner = (): JSX.Element => {\n return (\n <StyledLoadingSpinnerContainer placement={isSearch ? 'end' : 'middle'}>\n <CircularProgress size=\"small\" />\n </StyledLoadingSpinnerContainer>\n );\n };\n\n const renderInputField = (params: AutocompleteRenderInputParams): JSX.Element => (\n <TextField\n disabled={disabled}\n errorText={errorText}\n fdKey={fdKey}\n fullWidth={fullWidth}\n helperText={helperText}\n label={label}\n placeholder={placeholder}\n required={required}\n slotProps={{\n input: {\n ...params.InputProps,\n endAdornment: (\n <>\n {loading ? renderLoadingSpinner() : null}\n {params.InputProps.endAdornment}\n </>\n ),\n startAdornment: (\n <>\n {isSearch ? renderSearchIcon() : null}\n {params.InputProps.startAdornment}\n </>\n ),\n },\n htmlInput: {\n ...params.inputProps,\n },\n }}\n />\n );\n\n const renderMenuOption = (\n optionProps: React.HTMLAttributes<HTMLLIElement> & { key: React.Key },\n option: AutocompleteOption,\n state: AutocompleteRenderOptionState,\n ): JSX.Element => {\n const { key, onClick, ...rest } = optionProps;\n\n if (multiple) {\n return (\n <MenuItem\n {...rest}\n key={key}\n checked={state.selected}\n label={option.label}\n onCheckedChange={(_checked, e) => {\n onClick?.(e as React.MouseEvent<HTMLLIElement, MouseEvent>);\n }}\n type=\"checkbox\"\n />\n );\n }\n\n return (\n <MenuItem\n {...rest}\n key={key}\n label={option.label}\n onClick={onClick}\n selected={state.selected}\n type=\"text\"\n />\n );\n };\n\n const renderSelectedTags = (\n selected: AutocompleteOption[],\n getTagProps: AutocompleteRenderGetTagProps,\n ): React.ReactNode =>\n selected.map((tag, index) => {\n const { key, onDelete, ...tagProps } = getTagProps({ index });\n const rawLabel = typeof tag === 'string' ? tag : tag.label;\n const label = truncateWithEllipsis(rawLabel, 13, { includeEllipsisInLimit: true });\n const isTruncated = label.endsWith('...');\n const tagKey = key ?? `tag-${typeof tag === 'string' ? tag : tag.value}`;\n\n if (isTruncated) {\n return (\n <MuiTooltip key={tagKey} arrow placement=\"top\" title={rawLabel}>\n <span {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n </MuiTooltip>\n );\n }\n\n return (\n <span key={tagKey} {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n );\n });\n\n const handlePopupIndicatorClick = (event: React.MouseEvent): void => {\n if (disabled) {\n return;\n }\n\n event.stopPropagation();\n toggleOpen();\n };\n\n const handleChange = (\n event: React.SyntheticEvent,\n value: AutocompleteValue<AutocompleteOption, boolean, boolean, boolean>,\n reason: AutocompleteChangeReason,\n details?: AutocompleteChangeDetails<AutocompleteOption>,\n ): void => {\n if (!onChange) {\n return;\n }\n onChange(event, value, reason, details);\n };\n\n const handleInputChange = (\n event: React.SyntheticEvent,\n value: string,\n reason: AutocompleteInputChangeReason,\n ): void => {\n if (!onInputChange) {\n return;\n }\n onInputChange(event, value, reason);\n };\n\n /**\n * Returns all options without filtering for search mode.\n * Disables MUI's built-in client-side filtering when type is 'search'.\n */\n const disableClientFilter = (opts: AutocompleteOption[]): AutocompleteOption[] => opts;\n\n /**\n * Extracts the display label from an option.\n * Supports both object options and freeSolo string values.\n */\n const getOptionLabel = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : option.label;\n };\n\n /**\n * Provides a stable unique key for each option to optimize DOM reconciliation.\n */\n const getOptionKey = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : String(option.value);\n };\n\n /**\n * Determines equality between an option and the current value.\n * Handles both freeSolo string values and object options to prevent selection glitches.\n */\n const getIsOptionEqualToValue = (a: AutocompleteOption, b: AutocompleteOption): boolean => {\n if (b === null || b === undefined) {\n return false;\n }\n return typeof b === 'string' ? a.label === b || String(a.value) === b : a.value === b.value;\n };\n\n /**\n * Normalizes the value prop for MUI Autocomplete.\n * Handles both single and multiple selection modes, ensuring the value\n * format matches the mode (array for multiple, single value/null for single).\n *\n * @returns Normalized value compatible with MUI Autocomplete\n */\n const getValue = (): AutocompleteProps['value'] => {\n // Multiple value selection\n if (multiple) {\n if (Array.isArray(value)) return value;\n if (value === null || value === undefined) return [];\n return [value as AutocompleteOption];\n }\n\n // Single value selection\n if (Array.isArray(value)) return value.length > 0 ? value[0] : null;\n return value ?? null;\n };\n\n return (\n <StyledAutocomplete\n ref={rootRef}\n className={className}\n clearIcon={<CancelIcon size=\"md\" />}\n clearText={translations.clearTextAriaLabel}\n disableClearable={disabled || loading}\n disableCloseOnSelect={multiple}\n disabled={disabled}\n filterOptions={isSearch ? disableClientFilter : undefined}\n forcePopupIcon={isCombobox}\n freeSolo={isSearch}\n fullWidth={fullWidth}\n getOptionKey={getOptionKey}\n getOptionLabel={getOptionLabel}\n isOptionEqualToValue={getIsOptionEqualToValue}\n limitTags={limitTags}\n loading={loading}\n loadingText={translations.loadingText}\n multiple={multiple}\n noOptionsText={translations.noOptionsText}\n onChange={handleChange}\n onClose={() => toggleOpen(false)}\n onInputChange={handleInputChange}\n onOpen={() => toggleOpen(true)}\n open={isOpen}\n openText={translations.openPopupAriaLabel}\n options={options}\n popupIcon={isSearch ? null : renderPopupIcon()}\n renderInput={renderInputField}\n renderOption={renderMenuOption}\n renderTags={renderSelectedTags}\n slotProps={{\n popupIndicator: {\n onClick: handlePopupIndicatorClick,\n },\n }}\n value={getValue()}\n />\n );\n};\n\nexport default Autocomplete;\n"],"names":["StyledAutocomplete","styled","shouldForwardProp","prop","fullWidth","width","StyledAdornment","Box","includes","theme","clickable","disabled","placement","alignItems","cursor","display","justifyContent","padding","pointerEvents","paddingRight","spacing","color","palette","semantic","icon","StyledLoadingSpinnerContainer","Autocomplete","className","errorText","fdKey","helperText","label","loading","multiple","placeholder","onChange","onInputChange","options","required","translations","type","value","isOpen","setIsOpen","useState","isSearch","isCombobox","rootRef","limitTags","useDynamicLimitTags","chipMax","gap","horizontalPadding","reservedPx","useEffect","toggleOpen","open","undefined","prevOpen","_jsx","ref","clearIcon","CancelIcon","size","clearText","clearTextAriaLabel","disableClearable","disableCloseOnSelect","filterOptions","opts","forcePopupIcon","freeSolo","getOptionKey","option","String","getOptionLabel","isOptionEqualToValue","a","b","loadingText","noOptionsText","event","reason","details","onClose","onOpen","openText","openPopupAriaLabel","popupIcon","children","ArrowUp01Icon","ArrowDown01Icon","renderInput","params","TextField","slotProps","input","InputProps","endAdornment","_jsxs","_Fragment","CircularProgress","startAdornment","SearchIcon","htmlInput","inputProps","renderOption","optionProps","state","key","onClick","rest","_createElement","MenuItem","checked","selected","onCheckedChange","_checked","e","renderTags","getTagProps","map","tag","index","onDelete","tagProps","rawLabel","truncateWithEllipsis","includeEllipsisInLimit","isTruncated","endsWith","tagKey","MuiTooltip","arrow","title","Tag","dismissAriaLabel","dismissTagAriaLabel","onDismiss","popupIndicator","stopPropagation","Array","isArray","length"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/molecules/Autocomplete/index.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nimport MuiAutocomplete, {\n type AutocompleteChangeDetails,\n type AutocompleteChangeReason,\n type AutocompleteInputChangeReason,\n type AutocompleteProps as MuiAutocompleteProps,\n type AutocompleteRenderGetTagProps,\n type AutocompleteRenderInputParams,\n type AutocompleteRenderOptionState,\n type AutocompleteValue,\n} from '@mui/material/Autocomplete';\nimport Box from '@mui/material/Box';\nimport { styled } from '@mui/material/styles';\n// TODO: Replace with our own Tooltip component\nimport MuiTooltip from '@mui/material/Tooltip';\n\nimport ArrowDown01Icon from '@fd/icons/ArrowDown01';\nimport ArrowUp01Icon from '@fd/icons/ArrowUp01';\nimport CancelIcon from '@fd/icons/Cancel';\nimport SearchIcon from '@fd/icons/Search';\nimport { truncateWithEllipsis } from '@fd/utilities/stringUtilities';\n\nimport CircularProgress from '../../atoms/CircularProgress';\nimport MenuItem from '../../atoms/MenuItem';\nimport Tag from '../../atoms/Tag';\nimport TextField, { type TextFieldProps } from '../../atoms/TextField';\nimport { useDynamicLimitTags } from './hooks/useDynamicLimitTags';\n\n// Bind flags to MuiAutocomplete\ntype MuiCustomAutocompleteProps<\n M extends boolean = boolean, // Multiple\n DC extends boolean = boolean, // DisableClearable\n FS extends boolean = boolean, // FreeSolo\n> = MuiAutocompleteProps<AutocompleteOption, M, DC, FS>;\n\n/**\n * Represents an option in the Autocomplete dropdown.\n */\nexport interface AutocompleteOption {\n /** The label to display in the dropdown option. */\n label: string;\n /** The value to store in the dropdown option. */\n value: number | string;\n}\n\n/**\n * Props for the Autocomplete component.\n * Supports both single and multiple selection modes with optional search functionality.\n */\nexport interface AutocompleteProps\n extends Omit<TextFieldProps, 'endAdornment' | 'multiline' | 'onChange' | 'options' | 'startAdornment'> {\n /** Shows a loading indicator inside the input (e.g., while fetching). */\n loading?: boolean;\n /** Enables selection of multiple options and shows checkboxes and tags for selected values. */\n multiple?: MuiCustomAutocompleteProps['multiple'];\n /** Called when the selected value(s) change. */\n onChange: MuiCustomAutocompleteProps['onChange'];\n /** Called when the input text changes (typing or programmatic updates). */\n onInputChange?: MuiCustomAutocompleteProps['onInputChange'];\n /** Options to display in the dropdown list. */\n options: AutocompleteOption[];\n /** UI and accessibility labels used by the component. */\n translations: {\n /** Aria-label for the clear button. */\n clearTextAriaLabel: string;\n /** Aria-label for the dismiss tag button. */\n dismissTagAriaLabel: string;\n /** Text shown while loading options. */\n loadingText: string;\n /** Text shown when there are no options to display. */\n noOptionsText: string;\n /** Aria-label for the popup indicator button. */\n openPopupAriaLabel: string;\n };\n /** Visual behavior: 'search' shows a leading search icon; 'combobox' shows a popup button. */\n type?: 'combobox' | 'search';\n /** Controlled value (single option, array for multiple, or null). */\n value: MuiCustomAutocompleteProps['value'];\n}\n\n// Bind flags to MuiAutocomplete, see MuiCustomAutocompleteProps for more details\nconst MuiCustomAutocomplete = MuiAutocomplete<AutocompleteOption, boolean, boolean, boolean>;\nconst StyledAutocomplete = styled(MuiCustomAutocomplete, {\n shouldForwardProp: (prop) => prop !== 'fullWidth',\n})<{ fullWidth?: boolean }>(({ fullWidth = false }) => ({\n width: fullWidth ? '100%' : 'min(364px, 100%)',\n}));\n\nconst StyledAdornment = styled(Box, {\n shouldForwardProp: (prop) => !['clickable', 'disabled', 'placement'].includes(prop as string),\n})<{ clickable?: boolean; error?: boolean; disabled?: boolean; placement: 'end' | 'middle' | 'start' }>(\n ({ theme, clickable = false, disabled = false, placement }) => ({\n alignItems: 'center',\n cursor: clickable ? (disabled ? 'not-allowed' : 'pointer') : 'default',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n pointerEvents: clickable ? 'auto' : 'none',\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n\n '& svg': {\n color: disabled\n ? theme.palette.semantic.icon['icon-disabled']\n : theme.palette.semantic.icon['icon-strong'],\n },\n }),\n);\n\nconst StyledLoadingSpinnerContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'placement',\n})<{ placement?: 'end' | 'middle' | 'start' }>(({ theme, placement }) => ({\n alignItems: 'center',\n display: 'flex',\n justifyContent: 'center',\n padding: 0,\n\n ...(placement === 'start' && { paddingRight: theme.spacing(1) }),\n ...(placement === 'middle' && { padding: theme.spacing(0, 1) }),\n}));\n\n/**\n * A customizable Autocomplete component supporting search and combobox modes.\n * Supports single and multiple selection with dynamic tag rendering and optional loading states.\n *\n * @param props - The component props\n * @returns The rendered Autocomplete component\n */\nexport const Autocomplete = ({\n className,\n disabled = false,\n errorText,\n fdKey,\n fullWidth = false,\n helperText,\n label,\n loading = false,\n multiple = false,\n placeholder,\n onChange,\n onInputChange,\n options = [],\n required = false,\n translations,\n type = 'search',\n value,\n}: AutocompleteProps): JSX.Element => {\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const isSearch = type === 'search';\n const isCombobox = type === 'combobox';\n\n const { rootRef, limitTags } = useDynamicLimitTags({\n chipMax: 130,\n gap: 4,\n horizontalPadding: 32,\n reservedPx: isSearch ? 120 : 90,\n });\n\n // Close the dropdown (if open) when the component is dynamically disabled\n useEffect(() => {\n if (disabled) {\n setIsOpen(false);\n }\n }, [disabled]);\n\n /**\n * Toggles or sets the dropdown open state.\n * Does nothing when the component is disabled.\n *\n * @param open - Optional boolean to explicitly set open state; if undefined, toggles current state\n */\n const toggleOpen = (open?: boolean): void => {\n if (disabled) {\n return;\n }\n\n if (open !== undefined) {\n setIsOpen(open);\n return;\n }\n\n setIsOpen((prevOpen) => !prevOpen);\n };\n\n const renderSearchIcon = (): JSX.Element => {\n return (\n <StyledAdornment disabled={disabled} placement=\"start\">\n <SearchIcon aria-hidden=\"true\" />\n </StyledAdornment>\n );\n };\n\n const renderPopupIcon = (): JSX.Element => {\n return (\n <StyledAdornment clickable disabled={disabled} placement=\"end\">\n {isOpen ? <ArrowUp01Icon /> : <ArrowDown01Icon />}\n </StyledAdornment>\n );\n };\n\n const renderLoadingSpinner = (): JSX.Element => {\n return (\n <StyledLoadingSpinnerContainer placement={isSearch ? 'end' : 'middle'}>\n <CircularProgress size=\"small\" />\n </StyledLoadingSpinnerContainer>\n );\n };\n\n const renderInputField = (params: AutocompleteRenderInputParams): JSX.Element => (\n <TextField\n disabled={disabled}\n errorText={errorText}\n fdKey={fdKey}\n fullWidth={fullWidth}\n helperText={helperText}\n label={label}\n placeholder={placeholder}\n required={required}\n slotProps={{\n input: {\n ...params.InputProps,\n endAdornment: (\n <>\n {loading ? renderLoadingSpinner() : null}\n {params.InputProps.endAdornment}\n </>\n ),\n startAdornment: (\n <>\n {isSearch ? renderSearchIcon() : null}\n {params.InputProps.startAdornment}\n </>\n ),\n },\n htmlInput: {\n ...params.inputProps,\n },\n }}\n />\n );\n\n const renderMenuOption = (\n optionProps: React.HTMLAttributes<HTMLLIElement> & { key: React.Key },\n option: AutocompleteOption,\n state: AutocompleteRenderOptionState,\n ): JSX.Element => {\n const { key, onClick, ...rest } = optionProps;\n\n if (multiple) {\n return (\n <MenuItem\n {...rest}\n key={key}\n checked={state.selected}\n label={option.label}\n onCheckedChange={(_checked, e) => {\n onClick?.(e as React.MouseEvent<HTMLLIElement, MouseEvent>);\n }}\n type=\"checkbox\"\n />\n );\n }\n\n return (\n <MenuItem\n {...rest}\n key={key}\n label={option.label}\n onClick={onClick}\n selected={state.selected}\n type=\"text\"\n />\n );\n };\n\n const renderSelectedTags = (\n selected: AutocompleteOption[],\n getTagProps: AutocompleteRenderGetTagProps,\n ): React.ReactNode =>\n selected.map((tag, index) => {\n const { key, onDelete, ...tagProps } = getTagProps({ index });\n const rawLabel = typeof tag === 'string' ? tag : tag.label;\n const label = truncateWithEllipsis(rawLabel, 13, { includeEllipsisInLimit: true });\n const isTruncated = label.endsWith('...');\n const tagKey = key ?? `tag-${typeof tag === 'string' ? tag : tag.value}`;\n\n if (isTruncated) {\n return (\n <MuiTooltip key={tagKey} arrow placement=\"top\" title={rawLabel}>\n <span {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n </MuiTooltip>\n );\n }\n\n return (\n <span key={tagKey} {...tagProps}>\n <Tag dismissAriaLabel={translations.dismissTagAriaLabel} label={label} onDismiss={onDelete} />\n </span>\n );\n });\n\n const handlePopupIndicatorClick = (event: React.MouseEvent): void => {\n if (disabled) {\n return;\n }\n\n event.stopPropagation();\n toggleOpen();\n };\n\n const handleChange = (\n event: React.SyntheticEvent,\n value: AutocompleteValue<AutocompleteOption, boolean, boolean, boolean>,\n reason: AutocompleteChangeReason,\n details?: AutocompleteChangeDetails<AutocompleteOption>,\n ): void => {\n if (!onChange) {\n return;\n }\n onChange(event, value, reason, details);\n };\n\n const handleInputChange = (\n event: React.SyntheticEvent,\n value: string,\n reason: AutocompleteInputChangeReason,\n ): void => {\n if (!onInputChange) {\n return;\n }\n onInputChange(event, value, reason);\n };\n\n /**\n * Returns all options without filtering for search mode.\n * Disables MUI's built-in client-side filtering when type is 'search'.\n */\n const disableClientFilter = (opts: AutocompleteOption[]): AutocompleteOption[] => opts;\n\n /**\n * Extracts the display label from an option.\n * Supports both object options and freeSolo string values.\n */\n const getOptionLabel = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : option.label;\n };\n\n /**\n * Provides a stable unique key for each option to optimize DOM reconciliation.\n */\n const getOptionKey = (option: AutocompleteOption | string): string => {\n return typeof option === 'string' ? option : String(option.value);\n };\n\n /**\n * Determines equality between an option and the current value.\n * Handles both freeSolo string values and object options to prevent selection glitches.\n */\n const getIsOptionEqualToValue = (a: AutocompleteOption, b: AutocompleteOption): boolean => {\n if (b === null || b === undefined) {\n return false;\n }\n return typeof b === 'string' ? a.label === b || String(a.value) === b : a.value === b.value;\n };\n\n /**\n * Normalizes the value prop for MUI Autocomplete.\n * Handles both single and multiple selection modes, ensuring the value\n * format matches the mode (array for multiple, single value/null for single).\n *\n * @returns Normalized value compatible with MUI Autocomplete\n */\n const getValue = (): AutocompleteProps['value'] => {\n // Multiple value selection\n if (multiple) {\n if (Array.isArray(value)) return value;\n if (value === null || value === undefined) return [];\n return [value as AutocompleteOption];\n }\n\n // Single value selection\n if (Array.isArray(value)) return value.length > 0 ? value[0] : null;\n return value ?? null;\n };\n\n return (\n <StyledAutocomplete\n ref={rootRef}\n className={className}\n clearIcon={<CancelIcon size=\"md\" />}\n clearText={translations.clearTextAriaLabel}\n disableClearable={disabled || loading}\n disableCloseOnSelect={multiple}\n disabled={disabled}\n filterOptions={isSearch ? disableClientFilter : undefined}\n forcePopupIcon={isCombobox}\n freeSolo={isSearch}\n fullWidth={fullWidth}\n getOptionKey={getOptionKey}\n getOptionLabel={getOptionLabel}\n isOptionEqualToValue={getIsOptionEqualToValue}\n limitTags={limitTags}\n loading={loading}\n loadingText={translations.loadingText}\n multiple={multiple}\n noOptionsText={translations.noOptionsText}\n onChange={handleChange}\n onClose={() => toggleOpen(false)}\n onInputChange={handleInputChange}\n onOpen={() => toggleOpen(true)}\n open={isOpen}\n openText={translations.openPopupAriaLabel}\n options={options}\n popupIcon={isSearch ? null : renderPopupIcon()}\n renderInput={renderInputField}\n renderOption={renderMenuOption}\n renderTags={renderSelectedTags}\n slotProps={{\n popupIndicator: {\n onClick: handlePopupIndicatorClick,\n },\n }}\n value={getValue()}\n />\n );\n};\n\nexport default Autocomplete;\n"],"names":["StyledAutocomplete","styled","shouldForwardProp","prop","fullWidth","width","StyledAdornment","Box","includes","theme","clickable","disabled","placement","alignItems","cursor","display","justifyContent","padding","pointerEvents","paddingRight","spacing","color","palette","semantic","icon","StyledLoadingSpinnerContainer","Autocomplete","className","errorText","fdKey","helperText","label","loading","multiple","placeholder","onChange","onInputChange","options","required","translations","type","value","isOpen","setIsOpen","useState","isSearch","isCombobox","rootRef","limitTags","useDynamicLimitTags","chipMax","gap","horizontalPadding","reservedPx","useEffect","toggleOpen","open","undefined","prevOpen","_jsx","ref","clearIcon","CancelIcon","size","clearText","clearTextAriaLabel","disableClearable","disableCloseOnSelect","filterOptions","opts","forcePopupIcon","freeSolo","getOptionKey","option","String","getOptionLabel","isOptionEqualToValue","a","b","loadingText","noOptionsText","event","reason","details","onClose","onOpen","openText","openPopupAriaLabel","popupIcon","children","ArrowUp01Icon","ArrowDown01Icon","renderInput","params","TextField","slotProps","input","InputProps","endAdornment","_jsxs","_Fragment","CircularProgress","startAdornment","SearchIcon","htmlInput","inputProps","renderOption","optionProps","state","key","onClick","rest","_createElement","MenuItem","checked","selected","onCheckedChange","_checked","e","renderTags","getTagProps","map","tag","index","onDelete","tagProps","rawLabel","truncateWithEllipsis","includeEllipsisInLimit","isTruncated","endsWith","tagKey","MuiTooltip","arrow","title","Tag","dismissAriaLabel","dismissTagAriaLabel","onDismiss","popupIndicator","stopPropagation","Array","isArray","length"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime"),r=require("react"),t=require("@mui/material/Box/Box"),i=require("@mui/material/Stack/Stack"),a=require("@mui/material/styles/styled"),l=require("@mui/material/styles/useTheme"),n=require("../../../atoms/Button/index.cjs.js"),s=require("@mui/material/FormHelperText"),o=require("@mui/material/Typography"),c=require("../../../../icons/CancelCircle/index.cjs.js"),u=require("../../../../icons/Upload/index.cjs.js");const d=a(t,{shouldForwardProp:e=>"hasError"!==e})((({theme:e,hasError:r})=>({flex:"1",display:"flex",flexDirection:"column",height:"100%",boxSizing:"border-box",alignItems:"center",justifyContent:"center",padding:e.spacing(4),borderRadius:e.radius["radius-8"],border:r?`1px solid ${e.palette.semantic.stroke["stroke-error-weak"]}`:`1px dashed ${e.palette.semantic.stroke["stroke-strong"]}`,background:r?e.palette.semantic.fill["fill-error-weak"]:e.palette.semantic.fill["fill-weaker"],cursor:"pointer",transition:"all 0.2s ease-in-out",[e.breakpoints.only("mobile")]:{justifyContent:"center",gap:e.spacing(4)}}))),p=a("input")({clip:"rect(0 0 0 0)",clipPath:"inset(50%)",height:1,overflow:"hidden",position:"absolute",bottom:0,left:0,whiteSpace:"nowrap",width:1}),m=a(t,{shouldForwardProp:e=>"hasError"!==e})((({hasError:e,theme:r})=>({display:"flex",flexDirection:"column",width:"48px",height:"48px",padding:"0px",justifyContent:"center",alignItems:"center",borderRadius:r.radius["radius-32"],backgroundColor:e?r.palette.semantic.fill["fill-error-weak"]:r.palette.semantic.fill["fill-primary-weak"]}))),f=a(n.Button)({alignSelf:"center"});module.exports=({handleUpload:t,multiple:a,maxFiles:n,maxFileSize:h,allowedFileTypes:x=[],disabled:g=!1,dragActiveText:b="Drop files here",dragInactiveText:k="Drag and drop files here",browseButtonText:y="Browse files",invalidFileTypesText:j="File type is not allowed. Allowed types: {allowedFileTypes}",invalidFileSizeText:w="File size is too large. Maximum size: {maxFileSize}",invalidFileCountText:C="Too many files selected. Maximum allowed: {maxFiles}"})=>{const v=l(),[F,T]=r.useState(!1),D=r.useRef(null),[q,S]=r.useState(null),z=r.useCallback((e=>{if(h&&e.size>h)return S(w),!1;const r=e.name.split(".").pop()?.toLowerCase();return!(x.length>0&&r&&!x.includes(r))||(S(j),!1)}),[h,x]),E=r.useCallback((e=>{const r=Array.from(e),i=[];n&&r.length>n?S(C):(r.forEach((e=>{z(e)&&i.push(e)})),i.length>0&&(S(null),t(i)))}),[n,z,t]),P=r.useCallback((e=>{e.preventDefault(),e.stopPropagation(),g||T(!0)}),[g]),B=r.useCallback((e=>{e.preventDefault(),e.stopPropagation(),g||T(!1)}),[g]),I=r.useCallback((e=>{e.preventDefault(),e.stopPropagation()}),[]),A=r.useCallback((e=>{if(e.preventDefault(),e.stopPropagation(),g)return;T(!1);const r=e.dataTransfer.files;r&&r.length>0&&E(r)}),[g,E]),R=r.useCallback((e=>{if(g)return;const r=e.target.files;r&&r.length>0&&E(r),D.current&&(D.current.value="")}),[g,E]),L=r.useCallback((e=>{e.preventDefault(),e.stopPropagation(),D.current?.click()}),[]),M=r.useCallback((()=>{g||D.current?.click()}),[g]);return e.jsxs(e.Fragment,{children:[!!q&&e.jsxs(s,{error:!0,children:[e.jsx(c,{}),q]}),e.jsxs(d,{"data-testid":"file-drop-zone",hasError:!!q,onClick:g?void 0:M,onDragEnter:P,onDragLeave:B,onDragOver:I,onDrop:A,children:[e.jsx(p,{ref:D,"data-testid":"file-upload-input",disabled:g,multiple:a,onChange:R,type:"file"}),e.jsxs(i,{alignItems:"center",direction:"column",gap:v.spacing(2),justifyContent:"center",width:"100%",children:[e.jsx(m,{hasError:!!q,children:e.jsx(u,{color:q?v.palette.semantic.icon["icon-error"]:v.palette.semantic.icon["icon-primary"]})}),e.jsx(o,{variant:"b1Strong",children:F?b:k}),e.jsx(f,{disabled:g,fdKey:"file-upload-btn",onClick:g?void 0:L,variant:"secondary",children:y})]})]})]})};
2
+ //# sourceMappingURL=FileDropZone.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileDropZone.cjs.js","sources":["../../../../../src/components/organisms/FileUpload/components/FileDropZone.tsx"],"sourcesContent":["import React, { useCallback,useRef, useState } from 'react';\n\nimport Box from '@mui/material/Box/Box';\nimport Stack from '@mui/material/Stack/Stack';\nimport styled from '@mui/material/styles/styled';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport FlipdishButton from '@fd/components/atoms/Button';\nimport FlipdishFormHelperText from '@fd/components/atoms/FormHelperText';\nimport FlipdishTypography from '@fd/components/atoms/Typography';\nimport CancelCircleIcon from '@fd/icons/CancelCircle';\nimport UploadIcon from '@fd/icons/Upload';\n\nconst StyledUploadAreaContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'hasError',\n})<{ hasError?: boolean }>(({ theme, hasError }) => ({\n flex: '1',\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n boxSizing: 'border-box',\n alignItems: 'center',\n justifyContent: 'center',\n padding: theme.spacing(4),\n borderRadius: theme.radius['radius-8'],\n border: hasError\n ? `1px solid ${theme.palette.semantic.stroke['stroke-error-weak']}`\n : `1px dashed ${theme.palette.semantic.stroke['stroke-strong']}`,\n background: hasError\n ? theme.palette.semantic.fill['fill-error-weak']\n : theme.palette.semantic.fill['fill-weaker'],\n cursor: 'pointer',\n transition: 'all 0.2s ease-in-out',\n [theme.breakpoints.only('mobile')]: {\n justifyContent: 'center',\n gap: theme.spacing(4),\n },\n}));\n\nconst VisuallyHiddenInput = styled('input')({\n clip: 'rect(0 0 0 0)',\n clipPath: 'inset(50%)',\n height: 1,\n overflow: 'hidden',\n position: 'absolute',\n bottom: 0,\n left: 0,\n whiteSpace: 'nowrap',\n width: 1,\n});\n\nconst StyledIconContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'hasError',\n})<{ hasError?: boolean }>(({ hasError, theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n width: '48px',\n height: '48px',\n padding: '0px',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: theme.radius['radius-32'],\n backgroundColor: hasError ? theme.palette.semantic.fill['fill-error-weak'] : theme.palette.semantic.fill['fill-primary-weak'],\n}));\n\nconst StyledButton = styled(FlipdishButton)({\n alignSelf: 'center',\n});\n\n/**\n * Props for the FileDropZone component.\n * Provides a drag-and-drop file upload interface with validation.\n */\nexport interface FileDropZoneProps {\n /**\n * Callback function invoked when files are successfully validated and ready to upload.\n * @param files - Array of File objects to be uploaded\n */\n handleUpload: (files: File[]) => void;\n\n /**\n * Whether multiple files can be selected at once.\n * @default true\n */\n multiple?: boolean;\n\n /**\n * Maximum number of files that can be selected in a single operation.\n * If exceeded, an error message will be displayed.\n * @default undefined (no limit)\n */\n maxFiles?: number;\n\n /**\n * Maximum file size allowed in bytes.\n * Files exceeding this size will be rejected with an error message.\n * Example: 10 * 1024 * 1024 for 10MB\n * @default undefined (no limit)\n */\n maxFileSize?: number;\n\n /**\n * Array of allowed file extensions (without dots).\n * Files with extensions not in this list will be rejected.\n * Example: ['jpg', 'png', 'pdf']\n * @default [] (all file types allowed)\n */\n allowedFileTypes?: string[];\n\n /**\n * Whether the file drop zone is disabled.\n * When true, prevents all file selection and drag-and-drop interactions.\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Text displayed when files are being dragged over the drop zone.\n * @default 'Drop files here'\n */\n dragActiveText?: string;\n\n /**\n * Text displayed when no files are being dragged (idle state).\n * @default 'Drag and drop files here'\n */\n dragInactiveText?: string;\n\n /**\n * Text displayed on the browse/upload button.\n * @default 'Browse files'\n */\n browseButtonText?: string;\n\n /**\n * Text displayed when a file type is not allowed.\n * @default 'File type is not allowed. Allowed types: {allowedFileTypes}'\n */\n invalidFileTypesText?: string;\n\n /**\n * Text displayed when a file size is too large.\n * @default 'File size is too large. Maximum size: {maxFileSize}'\n */\n invalidFileSizeText?: string;\n\n /**\n * Text displayed when too many files are selected.\n * @default 'Too many files selected. Maximum allowed: {maxFiles}'\n */\n invalidFileCountText?: string;\n}\n\nconst FileDropZone: React.FC<FileDropZoneProps> = ({\n handleUpload,\n multiple,\n maxFiles,\n maxFileSize,\n allowedFileTypes = [],\n disabled = false,\n dragActiveText = 'Drop files here',\n dragInactiveText = 'Drag and drop files here',\n browseButtonText = 'Browse files',\n invalidFileTypesText = 'File type is not allowed. Allowed types: {allowedFileTypes}',\n invalidFileSizeText = 'File size is too large. Maximum size: {maxFileSize}',\n invalidFileCountText = 'Too many files selected. Maximum allowed: {maxFiles}',\n}) => {\n const theme = useTheme();\n const [isDragActive, setIsDragActive] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n const validateFile = useCallback((file: File): boolean => {\n // Check file size\n if (maxFileSize && file.size > maxFileSize) {\n setError(invalidFileSizeText);\n return false;\n }\n\n // Check file type\n const fileExtension = file.name.split('.').pop()?.toLowerCase();\n if (allowedFileTypes.length > 0 && fileExtension && !allowedFileTypes.includes(fileExtension)) {\n setError(invalidFileTypesText);\n return false;\n }\n\n return true;\n }, [maxFileSize, allowedFileTypes]);\n\n const processFiles = useCallback( (files: File[] | FileList) => {\n const fileArray = Array.from(files);\n const validFiles: File[] = [];\n\n // Check max files limit\n if (maxFiles && fileArray.length > maxFiles) {\n setError(invalidFileCountText);\n return;\n }\n\n // Validate each file\n fileArray.forEach(file => {\n if (validateFile(file)) {\n validFiles.push(file);\n }\n });\n\n if (validFiles.length > 0) {\n setError(null);\n handleUpload(validFiles);\n }\n }, [maxFiles, validateFile, handleUpload]);\n\n const handleDragEnter = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragActive(true);\n }, [disabled]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragActive(false);\n }, [disabled]);\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n }, []);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragActive(false);\n\n const files = e.dataTransfer.files;\n if (files && files.length > 0) {\n processFiles(files);\n }\n }, [disabled, processFiles]);\n\n const handleFileInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return;\n\n const files = e.target.files;\n if (files && files.length > 0) {\n processFiles(files);\n }\n\n // Reset input value to allow selecting the same file again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n }, [disabled, processFiles]);\n\n const handleButtonClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n event.stopPropagation();\n fileInputRef.current?.click();\n }, []);\n\n const handleContainerClick = useCallback(() => {\n if (disabled) return;\n\n fileInputRef.current?.click();\n }, [disabled]);\n\n return (\n <>\n {!!error && (\n <FlipdishFormHelperText error>\n <CancelCircleIcon />\n {error}\n </FlipdishFormHelperText>\n )}\n <StyledUploadAreaContainer data-testid=\"file-drop-zone\"\n hasError={!!error}\n onClick={disabled ? undefined : handleContainerClick}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n >\n <VisuallyHiddenInput\n ref={fileInputRef}\n data-testid=\"file-upload-input\"\n disabled={disabled}\n multiple={multiple}\n onChange={handleFileInputChange}\n type=\"file\"\n />\n\n <Stack alignItems=\"center\" direction=\"column\" gap={theme.spacing(2)} justifyContent=\"center\" width=\"100%\">\n <StyledIconContainer\n hasError={!!error}\n >\n <UploadIcon color={error ? theme.palette.semantic.icon['icon-error'] : theme.palette.semantic.icon['icon-primary']} />\n </StyledIconContainer>\n\n <FlipdishTypography variant=\"b1Strong\">\n {isDragActive ? dragActiveText : dragInactiveText}\n </FlipdishTypography>\n\n <StyledButton\n disabled={disabled}\n fdKey=\"file-upload-btn\"\n onClick={disabled ? undefined : handleButtonClick}\n variant=\"secondary\"\n >\n {browseButtonText}\n </StyledButton>\n </Stack>\n </StyledUploadAreaContainer>\n </>\n );\n};\n\nexport default FileDropZone;\n"],"names":["StyledUploadAreaContainer","styled","Box","shouldForwardProp","prop","theme","hasError","flex","display","flexDirection","height","boxSizing","alignItems","justifyContent","padding","spacing","borderRadius","radius","border","palette","semantic","stroke","background","fill","cursor","transition","breakpoints","only","gap","VisuallyHiddenInput","clip","clipPath","overflow","position","bottom","left","whiteSpace","width","StyledIconContainer","backgroundColor","StyledButton","FlipdishButton","alignSelf","handleUpload","multiple","maxFiles","maxFileSize","allowedFileTypes","disabled","dragActiveText","dragInactiveText","browseButtonText","invalidFileTypesText","invalidFileSizeText","invalidFileCountText","useTheme","isDragActive","setIsDragActive","useState","fileInputRef","useRef","error","setError","validateFile","useCallback","file","size","fileExtension","name","split","pop","toLowerCase","length","includes","processFiles","files","fileArray","Array","from","validFiles","forEach","push","handleDragEnter","e","preventDefault","stopPropagation","handleDragLeave","handleDragOver","handleDrop","dataTransfer","handleFileInputChange","target","current","value","handleButtonClick","event","click","handleContainerClick","_jsxs","FlipdishFormHelperText","_jsx","CancelCircleIcon","onClick","undefined","onDragEnter","onDragLeave","onDragOver","onDrop","children","ref","onChange","type","Stack","direction","UploadIcon","color","icon","FlipdishTypography","variant","fdKey"],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import react__default from 'react';
2
+
3
+ /**
4
+ * Props for the FileDropZone component.
5
+ * Provides a drag-and-drop file upload interface with validation.
6
+ */
7
+ interface FileDropZoneProps {
8
+ /**
9
+ * Callback function invoked when files are successfully validated and ready to upload.
10
+ * @param files - Array of File objects to be uploaded
11
+ */
12
+ handleUpload: (files: File[]) => void;
13
+ /**
14
+ * Whether multiple files can be selected at once.
15
+ * @default true
16
+ */
17
+ multiple?: boolean;
18
+ /**
19
+ * Maximum number of files that can be selected in a single operation.
20
+ * If exceeded, an error message will be displayed.
21
+ * @default undefined (no limit)
22
+ */
23
+ maxFiles?: number;
24
+ /**
25
+ * Maximum file size allowed in bytes.
26
+ * Files exceeding this size will be rejected with an error message.
27
+ * Example: 10 * 1024 * 1024 for 10MB
28
+ * @default undefined (no limit)
29
+ */
30
+ maxFileSize?: number;
31
+ /**
32
+ * Array of allowed file extensions (without dots).
33
+ * Files with extensions not in this list will be rejected.
34
+ * Example: ['jpg', 'png', 'pdf']
35
+ * @default [] (all file types allowed)
36
+ */
37
+ allowedFileTypes?: string[];
38
+ /**
39
+ * Whether the file drop zone is disabled.
40
+ * When true, prevents all file selection and drag-and-drop interactions.
41
+ * @default false
42
+ */
43
+ disabled?: boolean;
44
+ /**
45
+ * Text displayed when files are being dragged over the drop zone.
46
+ * @default 'Drop files here'
47
+ */
48
+ dragActiveText?: string;
49
+ /**
50
+ * Text displayed when no files are being dragged (idle state).
51
+ * @default 'Drag and drop files here'
52
+ */
53
+ dragInactiveText?: string;
54
+ /**
55
+ * Text displayed on the browse/upload button.
56
+ * @default 'Browse files'
57
+ */
58
+ browseButtonText?: string;
59
+ /**
60
+ * Text displayed when a file type is not allowed.
61
+ * @default 'File type is not allowed. Allowed types: {allowedFileTypes}'
62
+ */
63
+ invalidFileTypesText?: string;
64
+ /**
65
+ * Text displayed when a file size is too large.
66
+ * @default 'File size is too large. Maximum size: {maxFileSize}'
67
+ */
68
+ invalidFileSizeText?: string;
69
+ /**
70
+ * Text displayed when too many files are selected.
71
+ * @default 'Too many files selected. Maximum allowed: {maxFiles}'
72
+ */
73
+ invalidFileCountText?: string;
74
+ }
75
+ declare const FileDropZone: react__default.FC<FileDropZoneProps>;
76
+
77
+ export { FileDropZone as default };
78
+ export type { FileDropZoneProps };
@@ -0,0 +1,2 @@
1
+ import{jsxs as e,Fragment as r,jsx as t}from"react/jsx-runtime";import{useState as i,useRef as o,useCallback as a}from"react";import l from"@mui/material/Box/Box";import n from"@mui/material/Stack/Stack";import s from"@mui/material/styles/styled";import p from"@mui/material/styles/useTheme";import{Button as d}from"../../../atoms/Button/index.js";import c from"@mui/material/FormHelperText";import m from"@mui/material/Typography";import u from"../../../../icons/CancelCircle/index.js";import f from"../../../../icons/Upload/index.js";const h=s(l,{shouldForwardProp:e=>"hasError"!==e})((({theme:e,hasError:r})=>({flex:"1",display:"flex",flexDirection:"column",height:"100%",boxSizing:"border-box",alignItems:"center",justifyContent:"center",padding:e.spacing(4),borderRadius:e.radius["radius-8"],border:r?`1px solid ${e.palette.semantic.stroke["stroke-error-weak"]}`:`1px dashed ${e.palette.semantic.stroke["stroke-strong"]}`,background:r?e.palette.semantic.fill["fill-error-weak"]:e.palette.semantic.fill["fill-weaker"],cursor:"pointer",transition:"all 0.2s ease-in-out",[e.breakpoints.only("mobile")]:{justifyContent:"center",gap:e.spacing(4)}}))),g=s("input")({clip:"rect(0 0 0 0)",clipPath:"inset(50%)",height:1,overflow:"hidden",position:"absolute",bottom:0,left:0,whiteSpace:"nowrap",width:1}),x=s(l,{shouldForwardProp:e=>"hasError"!==e})((({hasError:e,theme:r})=>({display:"flex",flexDirection:"column",width:"48px",height:"48px",padding:"0px",justifyContent:"center",alignItems:"center",borderRadius:r.radius["radius-32"],backgroundColor:e?r.palette.semantic.fill["fill-error-weak"]:r.palette.semantic.fill["fill-primary-weak"]}))),y=s(d)({alignSelf:"center"}),w=({handleUpload:l,multiple:s,maxFiles:d,maxFileSize:w,allowedFileTypes:b=[],disabled:k=!1,dragActiveText:v="Drop files here",dragInactiveText:F="Drag and drop files here",browseButtonText:T="Browse files",invalidFileTypesText:D="File type is not allowed. Allowed types: {allowedFileTypes}",invalidFileSizeText:C="File size is too large. Maximum size: {maxFileSize}",invalidFileCountText:S="Too many files selected. Maximum allowed: {maxFiles}"})=>{const j=p(),[z,E]=i(!1),P=o(null),[B,I]=i(null),A=a((e=>{if(w&&e.size>w)return I(C),!1;const r=e.name.split(".").pop()?.toLowerCase();return!(b.length>0&&r&&!b.includes(r))||(I(D),!1)}),[w,b]),L=a((e=>{const r=Array.from(e),t=[];d&&r.length>d?I(S):(r.forEach((e=>{A(e)&&t.push(e)})),t.length>0&&(I(null),l(t)))}),[d,A,l]),M=a((e=>{e.preventDefault(),e.stopPropagation(),k||E(!0)}),[k]),R=a((e=>{e.preventDefault(),e.stopPropagation(),k||E(!1)}),[k]),U=a((e=>{e.preventDefault(),e.stopPropagation()}),[]),$=a((e=>{if(e.preventDefault(),e.stopPropagation(),k)return;E(!1);const r=e.dataTransfer.files;r&&r.length>0&&L(r)}),[k,L]),H=a((e=>{if(k)return;const r=e.target.files;r&&r.length>0&&L(r),P.current&&(P.current.value="")}),[k,L]),K=a((e=>{e.preventDefault(),e.stopPropagation(),P.current?.click()}),[]),O=a((()=>{k||P.current?.click()}),[k]);return e(r,{children:[!!B&&e(c,{error:!0,children:[t(u,{}),B]}),e(h,{"data-testid":"file-drop-zone",hasError:!!B,onClick:k?void 0:O,onDragEnter:M,onDragLeave:R,onDragOver:U,onDrop:$,children:[t(g,{ref:P,"data-testid":"file-upload-input",disabled:k,multiple:s,onChange:H,type:"file"}),e(n,{alignItems:"center",direction:"column",gap:j.spacing(2),justifyContent:"center",width:"100%",children:[t(x,{hasError:!!B,children:t(f,{color:B?j.palette.semantic.icon["icon-error"]:j.palette.semantic.icon["icon-primary"]})}),t(m,{variant:"b1Strong",children:z?v:F}),t(y,{disabled:k,fdKey:"file-upload-btn",onClick:k?void 0:K,variant:"secondary",children:T})]})]})]})};export{w as default};
2
+ //# sourceMappingURL=FileDropZone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileDropZone.js","sources":["../../../../../src/components/organisms/FileUpload/components/FileDropZone.tsx"],"sourcesContent":["import React, { useCallback,useRef, useState } from 'react';\n\nimport Box from '@mui/material/Box/Box';\nimport Stack from '@mui/material/Stack/Stack';\nimport styled from '@mui/material/styles/styled';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport FlipdishButton from '@fd/components/atoms/Button';\nimport FlipdishFormHelperText from '@fd/components/atoms/FormHelperText';\nimport FlipdishTypography from '@fd/components/atoms/Typography';\nimport CancelCircleIcon from '@fd/icons/CancelCircle';\nimport UploadIcon from '@fd/icons/Upload';\n\nconst StyledUploadAreaContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'hasError',\n})<{ hasError?: boolean }>(({ theme, hasError }) => ({\n flex: '1',\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n boxSizing: 'border-box',\n alignItems: 'center',\n justifyContent: 'center',\n padding: theme.spacing(4),\n borderRadius: theme.radius['radius-8'],\n border: hasError\n ? `1px solid ${theme.palette.semantic.stroke['stroke-error-weak']}`\n : `1px dashed ${theme.palette.semantic.stroke['stroke-strong']}`,\n background: hasError\n ? theme.palette.semantic.fill['fill-error-weak']\n : theme.palette.semantic.fill['fill-weaker'],\n cursor: 'pointer',\n transition: 'all 0.2s ease-in-out',\n [theme.breakpoints.only('mobile')]: {\n justifyContent: 'center',\n gap: theme.spacing(4),\n },\n}));\n\nconst VisuallyHiddenInput = styled('input')({\n clip: 'rect(0 0 0 0)',\n clipPath: 'inset(50%)',\n height: 1,\n overflow: 'hidden',\n position: 'absolute',\n bottom: 0,\n left: 0,\n whiteSpace: 'nowrap',\n width: 1,\n});\n\nconst StyledIconContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'hasError',\n})<{ hasError?: boolean }>(({ hasError, theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n width: '48px',\n height: '48px',\n padding: '0px',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: theme.radius['radius-32'],\n backgroundColor: hasError ? theme.palette.semantic.fill['fill-error-weak'] : theme.palette.semantic.fill['fill-primary-weak'],\n}));\n\nconst StyledButton = styled(FlipdishButton)({\n alignSelf: 'center',\n});\n\n/**\n * Props for the FileDropZone component.\n * Provides a drag-and-drop file upload interface with validation.\n */\nexport interface FileDropZoneProps {\n /**\n * Callback function invoked when files are successfully validated and ready to upload.\n * @param files - Array of File objects to be uploaded\n */\n handleUpload: (files: File[]) => void;\n\n /**\n * Whether multiple files can be selected at once.\n * @default true\n */\n multiple?: boolean;\n\n /**\n * Maximum number of files that can be selected in a single operation.\n * If exceeded, an error message will be displayed.\n * @default undefined (no limit)\n */\n maxFiles?: number;\n\n /**\n * Maximum file size allowed in bytes.\n * Files exceeding this size will be rejected with an error message.\n * Example: 10 * 1024 * 1024 for 10MB\n * @default undefined (no limit)\n */\n maxFileSize?: number;\n\n /**\n * Array of allowed file extensions (without dots).\n * Files with extensions not in this list will be rejected.\n * Example: ['jpg', 'png', 'pdf']\n * @default [] (all file types allowed)\n */\n allowedFileTypes?: string[];\n\n /**\n * Whether the file drop zone is disabled.\n * When true, prevents all file selection and drag-and-drop interactions.\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Text displayed when files are being dragged over the drop zone.\n * @default 'Drop files here'\n */\n dragActiveText?: string;\n\n /**\n * Text displayed when no files are being dragged (idle state).\n * @default 'Drag and drop files here'\n */\n dragInactiveText?: string;\n\n /**\n * Text displayed on the browse/upload button.\n * @default 'Browse files'\n */\n browseButtonText?: string;\n\n /**\n * Text displayed when a file type is not allowed.\n * @default 'File type is not allowed. Allowed types: {allowedFileTypes}'\n */\n invalidFileTypesText?: string;\n\n /**\n * Text displayed when a file size is too large.\n * @default 'File size is too large. Maximum size: {maxFileSize}'\n */\n invalidFileSizeText?: string;\n\n /**\n * Text displayed when too many files are selected.\n * @default 'Too many files selected. Maximum allowed: {maxFiles}'\n */\n invalidFileCountText?: string;\n}\n\nconst FileDropZone: React.FC<FileDropZoneProps> = ({\n handleUpload,\n multiple,\n maxFiles,\n maxFileSize,\n allowedFileTypes = [],\n disabled = false,\n dragActiveText = 'Drop files here',\n dragInactiveText = 'Drag and drop files here',\n browseButtonText = 'Browse files',\n invalidFileTypesText = 'File type is not allowed. Allowed types: {allowedFileTypes}',\n invalidFileSizeText = 'File size is too large. Maximum size: {maxFileSize}',\n invalidFileCountText = 'Too many files selected. Maximum allowed: {maxFiles}',\n}) => {\n const theme = useTheme();\n const [isDragActive, setIsDragActive] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n const validateFile = useCallback((file: File): boolean => {\n // Check file size\n if (maxFileSize && file.size > maxFileSize) {\n setError(invalidFileSizeText);\n return false;\n }\n\n // Check file type\n const fileExtension = file.name.split('.').pop()?.toLowerCase();\n if (allowedFileTypes.length > 0 && fileExtension && !allowedFileTypes.includes(fileExtension)) {\n setError(invalidFileTypesText);\n return false;\n }\n\n return true;\n }, [maxFileSize, allowedFileTypes]);\n\n const processFiles = useCallback( (files: File[] | FileList) => {\n const fileArray = Array.from(files);\n const validFiles: File[] = [];\n\n // Check max files limit\n if (maxFiles && fileArray.length > maxFiles) {\n setError(invalidFileCountText);\n return;\n }\n\n // Validate each file\n fileArray.forEach(file => {\n if (validateFile(file)) {\n validFiles.push(file);\n }\n });\n\n if (validFiles.length > 0) {\n setError(null);\n handleUpload(validFiles);\n }\n }, [maxFiles, validateFile, handleUpload]);\n\n const handleDragEnter = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragActive(true);\n }, [disabled]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragActive(false);\n }, [disabled]);\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n }, []);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragActive(false);\n\n const files = e.dataTransfer.files;\n if (files && files.length > 0) {\n processFiles(files);\n }\n }, [disabled, processFiles]);\n\n const handleFileInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return;\n\n const files = e.target.files;\n if (files && files.length > 0) {\n processFiles(files);\n }\n\n // Reset input value to allow selecting the same file again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n }, [disabled, processFiles]);\n\n const handleButtonClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {\n event.preventDefault();\n event.stopPropagation();\n fileInputRef.current?.click();\n }, []);\n\n const handleContainerClick = useCallback(() => {\n if (disabled) return;\n\n fileInputRef.current?.click();\n }, [disabled]);\n\n return (\n <>\n {!!error && (\n <FlipdishFormHelperText error>\n <CancelCircleIcon />\n {error}\n </FlipdishFormHelperText>\n )}\n <StyledUploadAreaContainer data-testid=\"file-drop-zone\"\n hasError={!!error}\n onClick={disabled ? undefined : handleContainerClick}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n >\n <VisuallyHiddenInput\n ref={fileInputRef}\n data-testid=\"file-upload-input\"\n disabled={disabled}\n multiple={multiple}\n onChange={handleFileInputChange}\n type=\"file\"\n />\n\n <Stack alignItems=\"center\" direction=\"column\" gap={theme.spacing(2)} justifyContent=\"center\" width=\"100%\">\n <StyledIconContainer\n hasError={!!error}\n >\n <UploadIcon color={error ? theme.palette.semantic.icon['icon-error'] : theme.palette.semantic.icon['icon-primary']} />\n </StyledIconContainer>\n\n <FlipdishTypography variant=\"b1Strong\">\n {isDragActive ? dragActiveText : dragInactiveText}\n </FlipdishTypography>\n\n <StyledButton\n disabled={disabled}\n fdKey=\"file-upload-btn\"\n onClick={disabled ? undefined : handleButtonClick}\n variant=\"secondary\"\n >\n {browseButtonText}\n </StyledButton>\n </Stack>\n </StyledUploadAreaContainer>\n </>\n );\n};\n\nexport default FileDropZone;\n"],"names":["StyledUploadAreaContainer","styled","Box","shouldForwardProp","prop","theme","hasError","flex","display","flexDirection","height","boxSizing","alignItems","justifyContent","padding","spacing","borderRadius","radius","border","palette","semantic","stroke","background","fill","cursor","transition","breakpoints","only","gap","VisuallyHiddenInput","clip","clipPath","overflow","position","bottom","left","whiteSpace","width","StyledIconContainer","backgroundColor","StyledButton","FlipdishButton","alignSelf","FileDropZone","handleUpload","multiple","maxFiles","maxFileSize","allowedFileTypes","disabled","dragActiveText","dragInactiveText","browseButtonText","invalidFileTypesText","invalidFileSizeText","invalidFileCountText","useTheme","isDragActive","setIsDragActive","useState","fileInputRef","useRef","error","setError","validateFile","useCallback","file","size","fileExtension","name","split","pop","toLowerCase","length","includes","processFiles","files","fileArray","Array","from","validFiles","forEach","push","handleDragEnter","e","preventDefault","stopPropagation","handleDragLeave","handleDragOver","handleDrop","dataTransfer","handleFileInputChange","target","current","value","handleButtonClick","event","click","handleContainerClick","_jsxs","FlipdishFormHelperText","_jsx","CancelCircleIcon","onClick","undefined","onDragEnter","onDragLeave","onDragOver","onDrop","children","ref","onChange","type","Stack","direction","UploadIcon","color","icon","FlipdishTypography","variant","fdKey"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime");require("react");var r=require("@mui/material/Box"),t=require("@mui/material/Stack"),i=require("@mui/material/styles/styled"),a=require("@mui/material/styles/useTheme"),s=require("../../../atoms/IconButton/index.cjs.js"),n=require("../../../atoms/LinearProgress/index.cjs.js"),o=require("@mui/material/Typography"),l=require("../../../../icons/Cancel/index.cjs.js"),c=require("./FileThumbnail.cjs.js");const d=i(r,{shouldForwardProp:e=>"hasError"!==e})((({theme:e,hasError:r})=>({display:"flex",alignItems:"center",width:"100%",gap:e.spacing(2),padding:e.spacing(2),borderBottom:`1px solid ${e.palette.semantic.stroke["stroke-weak"]}`,backgroundColor:r?e.palette.semantic.fill["fill-error-weak"]:e.palette.semantic.background["background-raised"]}))),u=i(r)((()=>({display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",width:"100%"}))),m=i(o)((()=>({overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}))),x=i(n.LinearProgress)({width:"250px"});module.exports=({file:r,error:i=null,isUploading:n=!1,onRemove:p})=>{const h=a();return e.jsxs(d,{hasError:!!i,children:[e.jsx(c,{file:r}),e.jsxs(t,{direction:"row",flex:1,gap:h.spacing(1),children:[e.jsxs(t,{direction:"column",width:"100%",children:[e.jsx(m,{color:h.palette.semantic.text["text-strong"],variant:"captionWeak",children:r.name}),e.jsx(o,{color:h.palette.semantic.text["text-weak"],variant:"captionWeak",children:(e=>{if(0===e)return"0 Bytes";const r=Math.floor(Math.log(e)/Math.log(1024));return`${Math.round(e/Math.pow(1024,r)*100)/100} ${["B","KB","MB","GB"][r]}`})(r.size)})]}),e.jsxs(u,{children:[i&&e.jsx(o,{color:h.palette.semantic.text["text-error"],variant:"captionWeak",children:i}),n&&e.jsx(x,{})]})]}),e.jsx(s.IconButton,{"aria-label":"Remove file",onClick:()=>{p(r)},size:"small",tone:"neutral",variant:"tertiary",children:e.jsx(l,{})})]})};
2
+ //# sourceMappingURL=FileItem.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileItem.cjs.js","sources":["../../../../../src/components/organisms/FileUpload/components/FileItem.tsx"],"sourcesContent":["import React from 'react';\n\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport styled from '@mui/material/styles/styled';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport IconButton from '@fd/components/atoms/IconButton';\nimport LinearProgress from '@fd/components/atoms/LinearProgress';\nimport Typography from '@fd/components/atoms/Typography';\nimport CancelIcon from '@fd/icons/Cancel';\n\nimport FileThumbnail from './FileThumbnail';\n\nconst StyledFileItemContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'hasError',\n})<{ hasError?: boolean }>(({ theme, hasError }) => ({\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n gap: theme.spacing(2),\n padding: theme.spacing(2),\n borderBottom: `1px solid ${theme.palette.semantic.stroke['stroke-weak']}`,\n backgroundColor: hasError\n ? theme.palette.semantic.fill['fill-error-weak']\n : theme.palette.semantic.background['background-raised'],\n}));\n\nconst StyledFileItemContent = styled(Box)(() => ({\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'flex-end',\n width: '100%',\n}));\nconst StyledFileName = styled(Typography)(() => ({\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n}));\n\nconst StyledLinearProgress = styled(LinearProgress)({\n width: '250px',\n});\n\nexport interface FileItemProps {\n file: File ;\n error?: string | null;\n isUploading?: boolean;\n onRemove: (file: File) => void;\n}\n\nconst FileItem: React.FC<FileItemProps> = ({\n file,\n error = null,\n isUploading = false,\n onRemove\n}) => {\n const theme = useTheme();\n\n const handleRemoveClick = (): void => {\n onRemove(file);\n };\n\n const formatFileSize = (bytes: number): string => {\n if (bytes === 0) return '0 Bytes';\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${Math.round(bytes / Math.pow(k, i) * 100) / 100} ${sizes[i]}`;\n };\n\n return (\n <StyledFileItemContainer hasError={!!error}>\n <FileThumbnail file={file} />\n\n <Stack direction=\"row\" flex={1} gap={theme.spacing(1)}>\n <Stack direction=\"column\" width=\"100%\">\n <StyledFileName\n color={theme.palette.semantic.text['text-strong']}\n variant=\"captionWeak\"\n >\n {file.name}\n </StyledFileName>\n <Typography color={theme.palette.semantic.text['text-weak']} variant=\"captionWeak\">\n {formatFileSize(file.size)}\n </Typography>\n </Stack>\n\n <StyledFileItemContent>\n {error &&\n <Typography color={theme.palette.semantic.text['text-error']} variant=\"captionWeak\">\n {error}\n </Typography>\n }\n {isUploading && <StyledLinearProgress />}\n </StyledFileItemContent>\n </Stack>\n\n <IconButton\n aria-label=\"Remove file\"\n onClick={handleRemoveClick}\n size=\"small\"\n tone=\"neutral\"\n variant=\"tertiary\"\n >\n <CancelIcon />\n </IconButton>\n </StyledFileItemContainer>\n );\n};\n\nexport default FileItem;\n"],"names":["StyledFileItemContainer","styled","Box","shouldForwardProp","prop","theme","hasError","display","alignItems","width","gap","spacing","padding","borderBottom","palette","semantic","stroke","backgroundColor","fill","background","StyledFileItemContent","flexDirection","justifyContent","StyledFileName","Typography","overflow","textOverflow","whiteSpace","StyledLinearProgress","LinearProgress","file","error","isUploading","onRemove","useTheme","_jsxs","children","_jsx","FileThumbnail","Stack","direction","flex","color","text","variant","name","bytes","i","Math","floor","log","round","pow","formatFileSize","size","IconButton","onClick","tone","CancelIcon"],"mappings":"kcAcA,MAAMA,EAA0BC,EAAOC,EAAK,CAC1CC,kBAAoBC,GAAkB,aAATA,GADCH,EAEL,EAAGI,QAAOC,eAAU,CAC7CC,QAAS,OACTC,WAAY,SACZC,MAAO,OACPC,IAAKL,EAAMM,QAAQ,GACnBC,QAASP,EAAMM,QAAQ,GACvBE,aAAc,aAAaR,EAAMS,QAAQC,SAASC,OAAO,iBACzDC,gBAAiBX,EACbD,EAAMS,QAAQC,SAASG,KAAK,mBAC5Bb,EAAMS,QAAQC,SAASI,WAAW,yBAGlCC,EAAwBnB,EAAOC,EAAPD,EAAY,KAAA,CACxCM,QAAS,OACTc,cAAe,MACfb,WAAY,SACZc,eAAgB,WAChBb,MAAO,WAEHc,EAAiBtB,EAAOuB,EAAPvB,EAAmB,KAAA,CACxCwB,SAAU,SACVC,aAAc,WACdC,WAAY,aAGRC,EAAuB3B,EAAO4B,EAAAA,eAAP5B,CAAuB,CAClDQ,MAAO,yBAUiC,EACxCqB,OACAC,QAAQ,KACRC,eAAc,EACdC,eAEA,MAAM5B,EAAQ6B,IAcd,OACEC,EAAAA,KAACnC,EAAuB,CAACM,WAAYyB,EAAKK,SAAA,CACxCC,EAAAA,IAACC,EAAa,CAACR,KAAMA,IAErBK,EAAAA,KAACI,EAAK,CAACC,UAAU,MAAMC,KAAM,EAAG/B,IAAKL,EAAMM,QAAQ,GAAEyB,SAAA,CACnDD,EAAAA,KAACI,EAAK,CAAEC,UAAU,SAAS/B,MAAM,OAAM2B,SAAA,CACrCC,MAACd,EAAc,CACbmB,MAAOrC,EAAMS,QAAQC,SAAS4B,KAAK,eACnCC,QAAQ,cAAaR,SAEpBN,EAAKe,OAERR,EAAAA,IAACb,EAAU,CAACkB,MAAOrC,EAAMS,QAAQC,SAAS4B,KAAK,aAAcC,QAAQ,cAAaR,SApBnE,CAACU,IACtB,GAAc,IAAVA,EAAa,MAAO,UACxB,MAEMC,EAAIC,KAAKC,MAAMD,KAAKE,IAAIJ,GAASE,KAAKE,IAFlC,OAGV,MAAO,GAAGF,KAAKG,MAAML,EAAQE,KAAKI,IAHxB,KAG+BL,GAAK,KAAO,OAFvC,CAAC,IAAK,KAAM,KAAM,MAEkCA,MAgBzDM,CAAevB,EAAKwB,WAIzBnB,EAAAA,KAACf,EAAqB,CAAAgB,SAAA,CACrBL,GACCM,MAACb,EAAU,CAACkB,MAAOrC,EAAMS,QAAQC,SAAS4B,KAAK,cAAeC,QAAQ,cAAaR,SAChFL,IAGJC,GAAeK,EAAAA,IAACT,EAAoB,UAIvCS,EAAAA,IAACkB,EAAAA,WAAU,CAAA,aACE,cACXC,QAzCoB,KACxBvB,EAASH,IAyCLwB,KAAK,QACLG,KAAK,UACLb,QAAQ,WAAUR,SAElBC,EAAAA,IAACqB,EAAU,CAAA"}
@@ -0,0 +1,12 @@
1
+ import react__default from 'react';
2
+
3
+ interface FileItemProps {
4
+ file: File;
5
+ error?: string | null;
6
+ isUploading?: boolean;
7
+ onRemove: (file: File) => void;
8
+ }
9
+ declare const FileItem: react__default.FC<FileItemProps>;
10
+
11
+ export { FileItem as default };
12
+ export type { FileItemProps };
@@ -0,0 +1,2 @@
1
+ import{jsxs as e,jsx as t}from"react/jsx-runtime";import"react";import r from"@mui/material/Box";import i from"@mui/material/Stack";import o from"@mui/material/styles/styled";import a from"@mui/material/styles/useTheme";import{IconButton as n}from"../../../atoms/IconButton/index.js";import{LinearProgress as l}from"../../../atoms/LinearProgress/index.js";import m from"@mui/material/Typography";import s from"../../../../icons/Cancel/index.js";import c from"./FileThumbnail.js";const p=o(r,{shouldForwardProp:e=>"hasError"!==e})((({theme:e,hasError:t})=>({display:"flex",alignItems:"center",width:"100%",gap:e.spacing(2),padding:e.spacing(2),borderBottom:`1px solid ${e.palette.semantic.stroke["stroke-weak"]}`,backgroundColor:t?e.palette.semantic.fill["fill-error-weak"]:e.palette.semantic.background["background-raised"]}))),d=o(r)((()=>({display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"flex-end",width:"100%"}))),h=o(m)((()=>({overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}))),f=o(l)({width:"250px"}),u=({file:r,error:o=null,isUploading:l=!1,onRemove:u})=>{const x=a();return e(p,{hasError:!!o,children:[t(c,{file:r}),e(i,{direction:"row",flex:1,gap:x.spacing(1),children:[e(i,{direction:"column",width:"100%",children:[t(h,{color:x.palette.semantic.text["text-strong"],variant:"captionWeak",children:r.name}),t(m,{color:x.palette.semantic.text["text-weak"],variant:"captionWeak",children:(e=>{if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return`${Math.round(e/Math.pow(1024,t)*100)/100} ${["B","KB","MB","GB"][t]}`})(r.size)})]}),e(d,{children:[o&&t(m,{color:x.palette.semantic.text["text-error"],variant:"captionWeak",children:o}),l&&t(f,{})]})]}),t(n,{"aria-label":"Remove file",onClick:()=>{u(r)},size:"small",tone:"neutral",variant:"tertiary",children:t(s,{})})]})};export{u as default};
2
+ //# sourceMappingURL=FileItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileItem.js","sources":["../../../../../src/components/organisms/FileUpload/components/FileItem.tsx"],"sourcesContent":["import React from 'react';\n\nimport Box from '@mui/material/Box';\nimport Stack from '@mui/material/Stack';\nimport styled from '@mui/material/styles/styled';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport IconButton from '@fd/components/atoms/IconButton';\nimport LinearProgress from '@fd/components/atoms/LinearProgress';\nimport Typography from '@fd/components/atoms/Typography';\nimport CancelIcon from '@fd/icons/Cancel';\n\nimport FileThumbnail from './FileThumbnail';\n\nconst StyledFileItemContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'hasError',\n})<{ hasError?: boolean }>(({ theme, hasError }) => ({\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n gap: theme.spacing(2),\n padding: theme.spacing(2),\n borderBottom: `1px solid ${theme.palette.semantic.stroke['stroke-weak']}`,\n backgroundColor: hasError\n ? theme.palette.semantic.fill['fill-error-weak']\n : theme.palette.semantic.background['background-raised'],\n}));\n\nconst StyledFileItemContent = styled(Box)(() => ({\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'flex-end',\n width: '100%',\n}));\nconst StyledFileName = styled(Typography)(() => ({\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n}));\n\nconst StyledLinearProgress = styled(LinearProgress)({\n width: '250px',\n});\n\nexport interface FileItemProps {\n file: File ;\n error?: string | null;\n isUploading?: boolean;\n onRemove: (file: File) => void;\n}\n\nconst FileItem: React.FC<FileItemProps> = ({\n file,\n error = null,\n isUploading = false,\n onRemove\n}) => {\n const theme = useTheme();\n\n const handleRemoveClick = (): void => {\n onRemove(file);\n };\n\n const formatFileSize = (bytes: number): string => {\n if (bytes === 0) return '0 Bytes';\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${Math.round(bytes / Math.pow(k, i) * 100) / 100} ${sizes[i]}`;\n };\n\n return (\n <StyledFileItemContainer hasError={!!error}>\n <FileThumbnail file={file} />\n\n <Stack direction=\"row\" flex={1} gap={theme.spacing(1)}>\n <Stack direction=\"column\" width=\"100%\">\n <StyledFileName\n color={theme.palette.semantic.text['text-strong']}\n variant=\"captionWeak\"\n >\n {file.name}\n </StyledFileName>\n <Typography color={theme.palette.semantic.text['text-weak']} variant=\"captionWeak\">\n {formatFileSize(file.size)}\n </Typography>\n </Stack>\n\n <StyledFileItemContent>\n {error &&\n <Typography color={theme.palette.semantic.text['text-error']} variant=\"captionWeak\">\n {error}\n </Typography>\n }\n {isUploading && <StyledLinearProgress />}\n </StyledFileItemContent>\n </Stack>\n\n <IconButton\n aria-label=\"Remove file\"\n onClick={handleRemoveClick}\n size=\"small\"\n tone=\"neutral\"\n variant=\"tertiary\"\n >\n <CancelIcon />\n </IconButton>\n </StyledFileItemContainer>\n );\n};\n\nexport default FileItem;\n"],"names":["StyledFileItemContainer","styled","Box","shouldForwardProp","prop","theme","hasError","display","alignItems","width","gap","spacing","padding","borderBottom","palette","semantic","stroke","backgroundColor","fill","background","StyledFileItemContent","flexDirection","justifyContent","StyledFileName","Typography","overflow","textOverflow","whiteSpace","StyledLinearProgress","LinearProgress","FileItem","file","error","isUploading","onRemove","useTheme","_jsxs","children","_jsx","FileThumbnail","Stack","direction","flex","color","text","variant","name","bytes","i","Math","floor","log","round","pow","formatFileSize","size","IconButton","onClick","tone","CancelIcon"],"mappings":"+dAcA,MAAMA,EAA0BC,EAAOC,EAAK,CAC1CC,kBAAoBC,GAAkB,aAATA,GADCH,EAEL,EAAGI,QAAOC,eAAU,CAC7CC,QAAS,OACTC,WAAY,SACZC,MAAO,OACPC,IAAKL,EAAMM,QAAQ,GACnBC,QAASP,EAAMM,QAAQ,GACvBE,aAAc,aAAaR,EAAMS,QAAQC,SAASC,OAAO,iBACzDC,gBAAiBX,EACbD,EAAMS,QAAQC,SAASG,KAAK,mBAC5Bb,EAAMS,QAAQC,SAASI,WAAW,yBAGlCC,EAAwBnB,EAAOC,EAAPD,EAAY,KAAA,CACxCM,QAAS,OACTc,cAAe,MACfb,WAAY,SACZc,eAAgB,WAChBb,MAAO,WAEHc,EAAiBtB,EAAOuB,EAAPvB,EAAmB,KAAA,CACxCwB,SAAU,SACVC,aAAc,WACdC,WAAY,aAGRC,EAAuB3B,EAAO4B,EAAP5B,CAAuB,CAClDQ,MAAO,UAUHqB,EAAoC,EACxCC,OACAC,QAAQ,KACRC,eAAc,EACdC,eAEA,MAAM7B,EAAQ8B,IAcd,OACEC,EAACpC,EAAuB,CAACM,WAAY0B,EAAKK,SAAA,CACxCC,EAACC,EAAa,CAACR,KAAMA,IAErBK,EAACI,EAAK,CAACC,UAAU,MAAMC,KAAM,EAAGhC,IAAKL,EAAMM,QAAQ,GAAE0B,SAAA,CACnDD,EAACI,EAAK,CAAEC,UAAU,SAAShC,MAAM,OAAM4B,SAAA,CACrCC,EAACf,EAAc,CACboB,MAAOtC,EAAMS,QAAQC,SAAS6B,KAAK,eACnCC,QAAQ,cAAaR,SAEpBN,EAAKe,OAERR,EAACd,EAAU,CAACmB,MAAOtC,EAAMS,QAAQC,SAAS6B,KAAK,aAAcC,QAAQ,cAAaR,SApBnE,CAACU,IACtB,GAAc,IAAVA,EAAa,MAAO,UACxB,MAEMC,EAAIC,KAAKC,MAAMD,KAAKE,IAAIJ,GAASE,KAAKE,IAFlC,OAGV,MAAO,GAAGF,KAAKG,MAAML,EAAQE,KAAKI,IAHxB,KAG+BL,GAAK,KAAO,OAFvC,CAAC,IAAK,KAAM,KAAM,MAEkCA,MAgBzDM,CAAevB,EAAKwB,WAIzBnB,EAAChB,EAAqB,CAAAiB,SAAA,CACrBL,GACCM,EAACd,EAAU,CAACmB,MAAOtC,EAAMS,QAAQC,SAAS6B,KAAK,cAAeC,QAAQ,cAAaR,SAChFL,IAGJC,GAAeK,EAACV,EAAoB,UAIvCU,EAACkB,EAAU,CAAA,aACE,cACXC,QAzCoB,KACxBvB,EAASH,IAyCLwB,KAAK,QACLG,KAAK,UACLb,QAAQ,WAAUR,SAElBC,EAACqB,EAAU,CAAA"}
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime"),t=require("react"),i=require("@mui/material/Box"),r=require("@mui/material/styles/styled"),s=require("@mui/material/styles/useTheme"),l=require("../../../../icons/File/index.cjs.js");const a=r(i)((({theme:e})=>({width:"64px",height:"64px",borderRadius:e.radius["radius-4"],overflow:"hidden",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:e.palette.semantic.fill["fill-weak"],flexShrink:0}))),n=r(i)((({theme:e})=>({display:"flex",flexDirection:"column",width:"48px",height:"48px",padding:"0px",justifyContent:"center",alignItems:"center",borderRadius:e.radius["radius-32"],backgroundColor:e.palette.semantic.fill["fill-weak"]}))),c=r("img")({width:"100%",height:"100%",objectFit:"cover"});module.exports=({file:i})=>{const r=s(),[o,u]=t.useState(null),[d,m]=t.useState(!1);return t.useEffect((()=>{const e=i.type.startsWith("image/");if(m(e),e){const e=URL.createObjectURL(i);return u(e),()=>{URL.revokeObjectURL(e)}}}),[i]),d&&o?e.jsx(a,{children:e.jsx(c,{alt:i.name,src:o})}):e.jsx(n,{children:e.jsx(l,{color:r.palette.semantic.icon["icon-strong"]})})};
2
+ //# sourceMappingURL=FileThumbnail.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileThumbnail.cjs.js","sources":["../../../../../src/components/organisms/FileUpload/components/FileThumbnail.tsx"],"sourcesContent":["import React, { useEffect,useState } from 'react';\n\nimport Box from '@mui/material/Box';\nimport styled from '@mui/material/styles/styled';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport FileIcon from '@fd/icons/File';\n\nconst StyledThumbnailContainer = styled(Box)(({ theme }) => ({\n width: '64px',\n height: '64px',\n borderRadius: theme.radius['radius-4'],\n overflow: 'hidden',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme.palette.semantic.fill['fill-weak'],\n flexShrink: 0,\n}));\n\nconst StyledIconContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n width: '48px',\n height: '48px',\n padding: '0px',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: theme.radius['radius-32'],\n backgroundColor: theme.palette.semantic.fill['fill-weak']\n}));\n\nconst StyledImage = styled('img')({\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n});\n\nexport interface FileThumbnailProps {\n file: File;\n}\n\nconst FileThumbnail: React.FC<FileThumbnailProps> = ({ file }) => {\n const theme = useTheme();\n const [imageUrl, setImageUrl] = useState<string | null>(null);\n const [isImage, setIsImage] = useState(false);\n\n useEffect(() => {\n // Check if the file is an image\n const fileType = file.type;\n const isImageFile = fileType.startsWith('image/');\n setIsImage(isImageFile);\n\n if (isImageFile) {\n // Create object URL for image preview\n const url = URL.createObjectURL(file);\n setImageUrl(url);\n\n // Cleanup function to revoke object URL\n return (): void => {\n URL.revokeObjectURL(url);\n };\n }\n\n return undefined;\n }, [file]);\n\n return (\n isImage && imageUrl ? (\n <StyledThumbnailContainer>\n <StyledImage alt={file.name} src={imageUrl} />\n </StyledThumbnailContainer>\n ) :\n (\n <StyledIconContainer>\n <FileIcon\n color={theme.palette.semantic.icon['icon-strong']}\n />\n </StyledIconContainer>\n )\n\n );\n};\n\nexport default FileThumbnail;\n"],"names":["StyledThumbnailContainer","styled","Box","theme","width","height","borderRadius","radius","overflow","display","alignItems","justifyContent","backgroundColor","palette","semantic","fill","flexShrink","StyledIconContainer","flexDirection","padding","StyledImage","objectFit","file","useTheme","imageUrl","setImageUrl","useState","isImage","setIsImage","useEffect","isImageFile","type","startsWith","url","URL","createObjectURL","revokeObjectURL","_jsx","children","alt","name","src","FileIcon","color","icon"],"mappings":"uOAQA,MAAMA,EAA2BC,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CACrDC,MAAO,OACPC,OAAQ,OACRC,aAAcH,EAAMI,OAAO,YAC3BC,SAAU,SACVC,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBC,gBAAiBT,EAAMU,QAAQC,SAASC,KAAK,aAC7CC,WAAY,MAGRC,EAAsBhB,EAAOC,EAAPD,EAAY,EAAKE,YAAO,CAClDM,QAAS,OACTS,cAAe,SACfd,MAAO,OACPC,OAAQ,OACRc,QAAS,MACTR,eAAgB,SAChBD,WAAY,SACZJ,aAAcH,EAAMI,OAAO,aAC3BK,gBAAkBT,EAAMU,QAAQC,SAASC,KAAK,iBAG1CK,EAAcnB,EAAO,MAAPA,CAAc,CAChCG,MAAO,OACPC,OAAQ,OACRgB,UAAW,yBAOuC,EAAGC,WACrD,MAAMnB,EAAQoB,KACPC,EAAUC,GAAeC,EAAAA,SAAwB,OACjDC,EAASC,GAAcF,EAAAA,UAAS,GAsBvC,OApBAG,EAAAA,WAAU,KAER,MACMC,EADWR,EAAKS,KACOC,WAAW,UAGxC,GAFAJ,EAAWE,GAEPA,EAAa,CAEf,MAAMG,EAAMC,IAAIC,gBAAgBb,GAIhC,OAHAG,EAAYQ,GAGL,KACLC,IAAIE,gBAAgBH,GAExB,IAGC,CAACX,IAGFK,GAAWH,EACTa,EAAAA,IAACrC,EAAwB,CAAAsC,SACvBD,EAAAA,IAACjB,EAAW,CAACmB,IAAKjB,EAAKkB,KAAMC,IAAKjB,MAIlCa,EAAAA,IAACpB,EAAmB,CAAAqB,SAClBD,EAAAA,IAACK,EAAQ,CACTC,MAAOxC,EAAMU,QAAQC,SAAS8B,KAAK"}
@@ -0,0 +1,9 @@
1
+ import react__default from 'react';
2
+
3
+ interface FileThumbnailProps {
4
+ file: File;
5
+ }
6
+ declare const FileThumbnail: react__default.FC<FileThumbnailProps>;
7
+
8
+ export { FileThumbnail as default };
9
+ export type { FileThumbnailProps };
@@ -0,0 +1,2 @@
1
+ import{jsx as e}from"react/jsx-runtime";import{useState as t,useEffect as i}from"react";import r from"@mui/material/Box";import o from"@mui/material/styles/styled";import l from"@mui/material/styles/useTheme";import a from"../../../../icons/File/index.js";const n=o(r)((({theme:e})=>({width:"64px",height:"64px",borderRadius:e.radius["radius-4"],overflow:"hidden",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:e.palette.semantic.fill["fill-weak"],flexShrink:0}))),s=o(r)((({theme:e})=>({display:"flex",flexDirection:"column",width:"48px",height:"48px",padding:"0px",justifyContent:"center",alignItems:"center",borderRadius:e.radius["radius-32"],backgroundColor:e.palette.semantic.fill["fill-weak"]}))),m=o("img")({width:"100%",height:"100%",objectFit:"cover"}),c=({file:r})=>{const o=l(),[c,d]=t(null),[f,u]=t(!1);return i((()=>{const e=r.type.startsWith("image/");if(u(e),e){const e=URL.createObjectURL(r);return d(e),()=>{URL.revokeObjectURL(e)}}}),[r]),f&&c?e(n,{children:e(m,{alt:r.name,src:c})}):e(s,{children:e(a,{color:o.palette.semantic.icon["icon-strong"]})})};export{c as default};
2
+ //# sourceMappingURL=FileThumbnail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileThumbnail.js","sources":["../../../../../src/components/organisms/FileUpload/components/FileThumbnail.tsx"],"sourcesContent":["import React, { useEffect,useState } from 'react';\n\nimport Box from '@mui/material/Box';\nimport styled from '@mui/material/styles/styled';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport FileIcon from '@fd/icons/File';\n\nconst StyledThumbnailContainer = styled(Box)(({ theme }) => ({\n width: '64px',\n height: '64px',\n borderRadius: theme.radius['radius-4'],\n overflow: 'hidden',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme.palette.semantic.fill['fill-weak'],\n flexShrink: 0,\n}));\n\nconst StyledIconContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n width: '48px',\n height: '48px',\n padding: '0px',\n justifyContent: 'center',\n alignItems: 'center',\n borderRadius: theme.radius['radius-32'],\n backgroundColor: theme.palette.semantic.fill['fill-weak']\n}));\n\nconst StyledImage = styled('img')({\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n});\n\nexport interface FileThumbnailProps {\n file: File;\n}\n\nconst FileThumbnail: React.FC<FileThumbnailProps> = ({ file }) => {\n const theme = useTheme();\n const [imageUrl, setImageUrl] = useState<string | null>(null);\n const [isImage, setIsImage] = useState(false);\n\n useEffect(() => {\n // Check if the file is an image\n const fileType = file.type;\n const isImageFile = fileType.startsWith('image/');\n setIsImage(isImageFile);\n\n if (isImageFile) {\n // Create object URL for image preview\n const url = URL.createObjectURL(file);\n setImageUrl(url);\n\n // Cleanup function to revoke object URL\n return (): void => {\n URL.revokeObjectURL(url);\n };\n }\n\n return undefined;\n }, [file]);\n\n return (\n isImage && imageUrl ? (\n <StyledThumbnailContainer>\n <StyledImage alt={file.name} src={imageUrl} />\n </StyledThumbnailContainer>\n ) :\n (\n <StyledIconContainer>\n <FileIcon\n color={theme.palette.semantic.icon['icon-strong']}\n />\n </StyledIconContainer>\n )\n\n );\n};\n\nexport default FileThumbnail;\n"],"names":["StyledThumbnailContainer","styled","Box","theme","width","height","borderRadius","radius","overflow","display","alignItems","justifyContent","backgroundColor","palette","semantic","fill","flexShrink","StyledIconContainer","flexDirection","padding","StyledImage","objectFit","FileThumbnail","file","useTheme","imageUrl","setImageUrl","useState","isImage","setIsImage","useEffect","isImageFile","type","startsWith","url","URL","createObjectURL","revokeObjectURL","_jsx","children","alt","name","src","FileIcon","color","icon"],"mappings":"gQAQA,MAAMA,EAA2BC,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CACrDC,MAAO,OACPC,OAAQ,OACRC,aAAcH,EAAMI,OAAO,YAC3BC,SAAU,SACVC,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBC,gBAAiBT,EAAMU,QAAQC,SAASC,KAAK,aAC7CC,WAAY,MAGRC,EAAsBhB,EAAOC,EAAPD,EAAY,EAAKE,YAAO,CAClDM,QAAS,OACTS,cAAe,SACfd,MAAO,OACPC,OAAQ,OACRc,QAAS,MACTR,eAAgB,SAChBD,WAAY,SACZJ,aAAcH,EAAMI,OAAO,aAC3BK,gBAAkBT,EAAMU,QAAQC,SAASC,KAAK,iBAG1CK,EAAcnB,EAAO,MAAPA,CAAc,CAChCG,MAAO,OACPC,OAAQ,OACRgB,UAAW,UAOPC,EAA8C,EAAGC,WACrD,MAAMpB,EAAQqB,KACPC,EAAUC,GAAeC,EAAwB,OACjDC,EAASC,GAAcF,GAAS,GAsBvC,OApBAG,GAAU,KAER,MACMC,EADWR,EAAKS,KACOC,WAAW,UAGxC,GAFAJ,EAAWE,GAEPA,EAAa,CAEf,MAAMG,EAAMC,IAAIC,gBAAgBb,GAIhC,OAHAG,EAAYQ,GAGL,KACLC,IAAIE,gBAAgBH,GAExB,IAGC,CAACX,IAGFK,GAAWH,EACTa,EAACtC,EAAwB,CAAAuC,SACvBD,EAAClB,EAAW,CAACoB,IAAKjB,EAAKkB,KAAMC,IAAKjB,MAIlCa,EAACrB,EAAmB,CAAAsB,SAClBD,EAACK,EAAQ,CACTC,MAAOzC,EAAMU,QAAQC,SAAS+B,KAAK"}
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime"),i=require("react"),l=require("@mui/material/Stack/Stack"),a=require("@mui/material/styles/useTheme"),t=require("@mui/material/Typography"),s=require("./components/FileDropZone.cjs.js"),n=require("./components/FileItem.cjs.js");module.exports=o=>{const r=a(),{label:m="Upload File",files:d,allowedFileTypes:p,maxFiles:x,maxFileSize:c,onUpload:u,onRemove:F,multiple:T=!0,dragActiveText:g,dragInactiveText:v,browseButtonText:j,invalidFileTypesText:y="File type is not allowed. Allowed types: {allowedFileTypes}",invalidFileSizeText:f="File size is too large. Maximum size: {maxFileSize}",invalidFileCountText:h="Too many files selected. Maximum allowed: {maxFiles}"}=o,[w,S]=i.useState(d),q=e=>{w.some((i=>i.file===e))&&S((i=>i.filter((i=>i.file!==e)))),F(e)};return e.jsxs(e.Fragment,{children:[e.jsx(t,{paddingBottom:r.spacing(1),variant:"b1Weak",children:m}),e.jsxs(l,{direction:"column",flex:1,gap:r.spacing(1),children:[e.jsx(s,{allowedFileTypes:p,browseButtonText:j,dragActiveText:g,dragInactiveText:v,handleUpload:e=>{const i=e.map((e=>({file:e,isUploading:!0})));S((e=>[...e,...i])),u(e)},invalidFileCountText:h,invalidFileSizeText:f,invalidFileTypesText:y,maxFiles:x,maxFileSize:c,multiple:T}),w.length>0&&e.jsx(l,{direction:"column",gap:r.spacing(1.5),children:w.map((i=>e.jsx(n,{file:i.file,isUploading:i.isUploading,onRemove:q},i.file.name)))})]})]})};
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../../src/components/organisms/FileUpload/index.tsx"],"sourcesContent":["import React, { useState } from 'react';\n\nimport Stack from '@mui/material/Stack/Stack';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport Typography from '@fd/components/atoms/Typography';\n\nimport FileDropZone from './components/FileDropZone';\nimport FileItem from './components/FileItem';\n\nexport interface FileUploadState {\n file: File;\n isUploading?: boolean;\n}\n\nexport interface FileUploadProps {\n /**\n * Label text displayed above the file upload area.\n * @default 'Upload File'\n */\n label?: string;\n\n /**\n * Array of file upload states (file object with optional isUploading flag).\n */\n files: FileUploadState[];\n\n /**\n * Array of allowed file extensions (without dots).\n * Example: ['jpg', 'png', 'pdf']\n */\n allowedFileTypes: string[];\n\n /**\n * Whether multiple files can be selected at once.\n * @default true\n */\n multiple?: boolean;\n\n /**\n * Maximum number of files that can be uploaded.\n */\n maxFiles: number;\n\n /**\n * Maximum file size allowed in bytes.\n * Example: 10 * 1024 * 1024 for 10MB\n */\n maxFileSize: number;\n\n /**\n * Callback fired when files are uploaded.\n */\n onUpload: (files: File[]) => void;\n\n /**\n * Callback fired when a file is removed.\n */\n onRemove: (file: File) => void;\n\n /**\n * Text displayed when files are being dragged over the drop zone.\n * @default 'Drop files here'\n */\n dragActiveText?: string;\n\n /**\n * Text displayed when no files are being dragged (idle state).\n * @default 'Drag and drop files here'\n */\n dragInactiveText?: string;\n\n /**\n * Text displayed on the browse/upload button.\n * @default 'Browse files'\n */\n browseButtonText?: string;\n\n /**\n * Text displayed when a file type is not allowed.\n * @default 'File type is not allowed. Allowed types: {allowedFileTypes}'\n */\n invalidFileTypesText?: string;\n\n /**\n * Text displayed when a file size is too large.\n * @default 'File size is too large. Maximum size: {maxFileSize}'\n */\n invalidFileSizeText?: string;\n\n /**\n * Text displayed when too many files are selected.\n * @default 'Too many files selected. Maximum allowed: {maxFiles}'\n */\n invalidFileCountText?: string;\n}\n\nconst FileUpload: React.FC<FileUploadProps> = (props) => {\n const theme = useTheme();\n const {\n label = 'Upload File',\n files,\n allowedFileTypes,\n maxFiles,\n maxFileSize,\n onUpload,\n onRemove,\n multiple = true,\n dragActiveText,\n dragInactiveText,\n browseButtonText,\n invalidFileTypesText = 'File type is not allowed. Allowed types: {allowedFileTypes}',\n invalidFileSizeText = 'File size is too large. Maximum size: {maxFileSize}',\n invalidFileCountText = 'Too many files selected. Maximum allowed: {maxFiles}',\n } = props;\n\n // Local state for files being uploaded\n const [uploadingFiles, setUploadingFiles] = useState<FileUploadState[]>(files);\n\n const handleUpload = (newFiles: File[]): void => {\n // Add new files to local uploading state\n const newUploadingFiles: FileUploadState[] = newFiles.map((file) => ({\n file,\n isUploading: true,\n }));\n\n setUploadingFiles((prev) => [...prev, ...newUploadingFiles]);\n\n // Call parent's upload handler\n onUpload(newFiles);\n };\n\n const handleRemoveFile = (fileToRemove: File): void => {\n // Check if file is in local uploading state\n const isUploadingFile = uploadingFiles.some((state) => state.file === fileToRemove);\n\n if (isUploadingFile) {\n // Remove from local state\n setUploadingFiles((prev) => prev.filter((state) => state.file !== fileToRemove));\n }\n\n // Always notify parent\n onRemove(fileToRemove);\n };\n\n return (\n <>\n <Typography paddingBottom={theme.spacing(1)} variant=\"b1Weak\">{label}</Typography>\n <Stack direction=\"column\" flex={1} gap={theme.spacing(1)}>\n <FileDropZone\n allowedFileTypes={allowedFileTypes}\n browseButtonText={browseButtonText}\n dragActiveText={dragActiveText}\n dragInactiveText={dragInactiveText}\n handleUpload={handleUpload}\n invalidFileCountText={invalidFileCountText}\n invalidFileSizeText={invalidFileSizeText}\n invalidFileTypesText={invalidFileTypesText}\n maxFiles={maxFiles}\n maxFileSize={maxFileSize}\n multiple={multiple}\n />\n\n {uploadingFiles.length > 0 && (\n <Stack direction=\"column\" gap={theme.spacing(1.5)}>\n {uploadingFiles.map((fileState) => (\n <FileItem\n key={fileState.file.name}\n file={fileState.file}\n isUploading={fileState.isUploading}\n onRemove={handleRemoveFile}\n />\n ))}\n </Stack>\n )}\n </Stack>\n </>\n );\n};\n\nexport default FileUpload;"],"names":["props","theme","useTheme","label","files","allowedFileTypes","maxFiles","maxFileSize","onUpload","onRemove","multiple","dragActiveText","dragInactiveText","browseButtonText","invalidFileTypesText","invalidFileSizeText","invalidFileCountText","uploadingFiles","setUploadingFiles","useState","handleRemoveFile","fileToRemove","some","state","file","prev","filter","_jsxs","_Fragment","children","_jsx","Typography","paddingBottom","spacing","variant","Stack","direction","flex","gap","FileDropZone","handleUpload","newFiles","newUploadingFiles","map","isUploading","length","fileState","FileItem","name"],"mappings":"kSAiG+CA,IAC7C,MAAMC,EAAQC,KACRC,MACJA,EAAQ,cAAaC,MACrBA,EAAKC,iBACLA,EAAgBC,SAChBA,EAAQC,YACRA,EAAWC,SACXA,EAAQC,SACRA,EAAQC,SACRA,GAAW,EAAIC,eACfA,EAAcC,iBACdA,EAAgBC,iBAChBA,EAAgBC,qBAChBA,EAAuB,8DAA6DC,oBACpFA,EAAsB,sDAAqDC,qBAC3EA,EAAuB,wDACrBhB,GAGGiB,EAAgBC,GAAqBC,EAAAA,SAA4Bf,GAelEgB,EAAoBC,IAEAJ,EAAeK,MAAMC,GAAUA,EAAMC,OAASH,KAIpEH,GAAmBO,GAASA,EAAKC,QAAQH,GAAUA,EAAMC,OAASH,MAIpEZ,EAASY,IAGX,OACEM,EAAAA,KAAAC,WAAA,CAAAC,SAAA,CACEC,MAACC,GAAWC,cAAe/B,EAAMgC,QAAQ,GAAIC,QAAQ,SAAQL,SAAE1B,IAC/DwB,EAAAA,KAACQ,EAAK,CAACC,UAAU,SAASC,KAAM,EAAGC,IAAKrC,EAAMgC,QAAQ,GAAEJ,SAAA,CACtDC,MAACS,EAAY,CACXlC,iBAAkBA,EAClBQ,iBAAkBA,EAClBF,eAAgBA,EAChBC,iBAAkBA,EAClB4B,aAnCcC,IAEpB,MAAMC,EAAuCD,EAASE,KAAKnB,IAAI,CAC7DA,OACAoB,aAAa,MAGf1B,GAAmBO,GAAS,IAAIA,KAASiB,KAGzClC,EAASiC,IA0BHzB,qBAAsBA,EACtBD,oBAAqBA,EACrBD,qBAAsBA,EACtBR,SAAUA,EACVC,YAAaA,EACbG,SAAUA,IAGXO,EAAe4B,OAAS,GACvBf,MAACK,GAAMC,UAAU,SAASE,IAAKrC,EAAMgC,QAAQ,KAAIJ,SAC9CZ,EAAe0B,KAAKG,GACnBhB,EAAAA,IAACiB,EAAQ,CAEPvB,KAAMsB,EAAUtB,KAChBoB,YAAaE,EAAUF,YACvBnC,SAAUW,GAHL0B,EAAUtB,KAAKwB"}
@@ -0,0 +1,78 @@
1
+ import react__default from 'react';
2
+
3
+ interface FileUploadState {
4
+ file: File;
5
+ isUploading?: boolean;
6
+ }
7
+ interface FileUploadProps {
8
+ /**
9
+ * Label text displayed above the file upload area.
10
+ * @default 'Upload File'
11
+ */
12
+ label?: string;
13
+ /**
14
+ * Array of file upload states (file object with optional isUploading flag).
15
+ */
16
+ files: FileUploadState[];
17
+ /**
18
+ * Array of allowed file extensions (without dots).
19
+ * Example: ['jpg', 'png', 'pdf']
20
+ */
21
+ allowedFileTypes: string[];
22
+ /**
23
+ * Whether multiple files can be selected at once.
24
+ * @default true
25
+ */
26
+ multiple?: boolean;
27
+ /**
28
+ * Maximum number of files that can be uploaded.
29
+ */
30
+ maxFiles: number;
31
+ /**
32
+ * Maximum file size allowed in bytes.
33
+ * Example: 10 * 1024 * 1024 for 10MB
34
+ */
35
+ maxFileSize: number;
36
+ /**
37
+ * Callback fired when files are uploaded.
38
+ */
39
+ onUpload: (files: File[]) => void;
40
+ /**
41
+ * Callback fired when a file is removed.
42
+ */
43
+ onRemove: (file: File) => void;
44
+ /**
45
+ * Text displayed when files are being dragged over the drop zone.
46
+ * @default 'Drop files here'
47
+ */
48
+ dragActiveText?: string;
49
+ /**
50
+ * Text displayed when no files are being dragged (idle state).
51
+ * @default 'Drag and drop files here'
52
+ */
53
+ dragInactiveText?: string;
54
+ /**
55
+ * Text displayed on the browse/upload button.
56
+ * @default 'Browse files'
57
+ */
58
+ browseButtonText?: string;
59
+ /**
60
+ * Text displayed when a file type is not allowed.
61
+ * @default 'File type is not allowed. Allowed types: {allowedFileTypes}'
62
+ */
63
+ invalidFileTypesText?: string;
64
+ /**
65
+ * Text displayed when a file size is too large.
66
+ * @default 'File size is too large. Maximum size: {maxFileSize}'
67
+ */
68
+ invalidFileSizeText?: string;
69
+ /**
70
+ * Text displayed when too many files are selected.
71
+ * @default 'Too many files selected. Maximum allowed: {maxFiles}'
72
+ */
73
+ invalidFileCountText?: string;
74
+ }
75
+ declare const FileUpload: react__default.FC<FileUploadProps>;
76
+
77
+ export { FileUpload as default };
78
+ export type { FileUploadProps, FileUploadState };
@@ -0,0 +1,2 @@
1
+ import{jsxs as e,Fragment as i,jsx as l}from"react/jsx-runtime";import{useState as t}from"react";import a from"@mui/material/Stack/Stack";import o from"@mui/material/styles/useTheme";import m from"@mui/material/Typography";import n from"./components/FileDropZone.js";import r from"./components/FileItem.js";const s=s=>{const p=o(),{label:d="Upload File",files:c,allowedFileTypes:x,maxFiles:F,maxFileSize:T,onUpload:f,onRemove:u,multiple:g=!0,dragActiveText:v,dragInactiveText:y,browseButtonText:h,invalidFileTypesText:w="File type is not allowed. Allowed types: {allowedFileTypes}",invalidFileSizeText:z="File size is too large. Maximum size: {maxFileSize}",invalidFileCountText:S="Too many files selected. Maximum allowed: {maxFiles}"}=s,[U,b]=t(c),j=e=>{U.some((i=>i.file===e))&&b((i=>i.filter((i=>i.file!==e)))),u(e)};return e(i,{children:[l(m,{paddingBottom:p.spacing(1),variant:"b1Weak",children:d}),e(a,{direction:"column",flex:1,gap:p.spacing(1),children:[l(n,{allowedFileTypes:x,browseButtonText:h,dragActiveText:v,dragInactiveText:y,handleUpload:e=>{const i=e.map((e=>({file:e,isUploading:!0})));b((e=>[...e,...i])),f(e)},invalidFileCountText:S,invalidFileSizeText:z,invalidFileTypesText:w,maxFiles:F,maxFileSize:T,multiple:g}),U.length>0&&l(a,{direction:"column",gap:p.spacing(1.5),children:U.map((e=>l(r,{file:e.file,isUploading:e.isUploading,onRemove:j},e.file.name)))})]})]})};export{s as default};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/organisms/FileUpload/index.tsx"],"sourcesContent":["import React, { useState } from 'react';\n\nimport Stack from '@mui/material/Stack/Stack';\nimport useTheme from '@mui/material/styles/useTheme';\n\nimport Typography from '@fd/components/atoms/Typography';\n\nimport FileDropZone from './components/FileDropZone';\nimport FileItem from './components/FileItem';\n\nexport interface FileUploadState {\n file: File;\n isUploading?: boolean;\n}\n\nexport interface FileUploadProps {\n /**\n * Label text displayed above the file upload area.\n * @default 'Upload File'\n */\n label?: string;\n\n /**\n * Array of file upload states (file object with optional isUploading flag).\n */\n files: FileUploadState[];\n\n /**\n * Array of allowed file extensions (without dots).\n * Example: ['jpg', 'png', 'pdf']\n */\n allowedFileTypes: string[];\n\n /**\n * Whether multiple files can be selected at once.\n * @default true\n */\n multiple?: boolean;\n\n /**\n * Maximum number of files that can be uploaded.\n */\n maxFiles: number;\n\n /**\n * Maximum file size allowed in bytes.\n * Example: 10 * 1024 * 1024 for 10MB\n */\n maxFileSize: number;\n\n /**\n * Callback fired when files are uploaded.\n */\n onUpload: (files: File[]) => void;\n\n /**\n * Callback fired when a file is removed.\n */\n onRemove: (file: File) => void;\n\n /**\n * Text displayed when files are being dragged over the drop zone.\n * @default 'Drop files here'\n */\n dragActiveText?: string;\n\n /**\n * Text displayed when no files are being dragged (idle state).\n * @default 'Drag and drop files here'\n */\n dragInactiveText?: string;\n\n /**\n * Text displayed on the browse/upload button.\n * @default 'Browse files'\n */\n browseButtonText?: string;\n\n /**\n * Text displayed when a file type is not allowed.\n * @default 'File type is not allowed. Allowed types: {allowedFileTypes}'\n */\n invalidFileTypesText?: string;\n\n /**\n * Text displayed when a file size is too large.\n * @default 'File size is too large. Maximum size: {maxFileSize}'\n */\n invalidFileSizeText?: string;\n\n /**\n * Text displayed when too many files are selected.\n * @default 'Too many files selected. Maximum allowed: {maxFiles}'\n */\n invalidFileCountText?: string;\n}\n\nconst FileUpload: React.FC<FileUploadProps> = (props) => {\n const theme = useTheme();\n const {\n label = 'Upload File',\n files,\n allowedFileTypes,\n maxFiles,\n maxFileSize,\n onUpload,\n onRemove,\n multiple = true,\n dragActiveText,\n dragInactiveText,\n browseButtonText,\n invalidFileTypesText = 'File type is not allowed. Allowed types: {allowedFileTypes}',\n invalidFileSizeText = 'File size is too large. Maximum size: {maxFileSize}',\n invalidFileCountText = 'Too many files selected. Maximum allowed: {maxFiles}',\n } = props;\n\n // Local state for files being uploaded\n const [uploadingFiles, setUploadingFiles] = useState<FileUploadState[]>(files);\n\n const handleUpload = (newFiles: File[]): void => {\n // Add new files to local uploading state\n const newUploadingFiles: FileUploadState[] = newFiles.map((file) => ({\n file,\n isUploading: true,\n }));\n\n setUploadingFiles((prev) => [...prev, ...newUploadingFiles]);\n\n // Call parent's upload handler\n onUpload(newFiles);\n };\n\n const handleRemoveFile = (fileToRemove: File): void => {\n // Check if file is in local uploading state\n const isUploadingFile = uploadingFiles.some((state) => state.file === fileToRemove);\n\n if (isUploadingFile) {\n // Remove from local state\n setUploadingFiles((prev) => prev.filter((state) => state.file !== fileToRemove));\n }\n\n // Always notify parent\n onRemove(fileToRemove);\n };\n\n return (\n <>\n <Typography paddingBottom={theme.spacing(1)} variant=\"b1Weak\">{label}</Typography>\n <Stack direction=\"column\" flex={1} gap={theme.spacing(1)}>\n <FileDropZone\n allowedFileTypes={allowedFileTypes}\n browseButtonText={browseButtonText}\n dragActiveText={dragActiveText}\n dragInactiveText={dragInactiveText}\n handleUpload={handleUpload}\n invalidFileCountText={invalidFileCountText}\n invalidFileSizeText={invalidFileSizeText}\n invalidFileTypesText={invalidFileTypesText}\n maxFiles={maxFiles}\n maxFileSize={maxFileSize}\n multiple={multiple}\n />\n\n {uploadingFiles.length > 0 && (\n <Stack direction=\"column\" gap={theme.spacing(1.5)}>\n {uploadingFiles.map((fileState) => (\n <FileItem\n key={fileState.file.name}\n file={fileState.file}\n isUploading={fileState.isUploading}\n onRemove={handleRemoveFile}\n />\n ))}\n </Stack>\n )}\n </Stack>\n </>\n );\n};\n\nexport default FileUpload;"],"names":["FileUpload","props","theme","useTheme","label","files","allowedFileTypes","maxFiles","maxFileSize","onUpload","onRemove","multiple","dragActiveText","dragInactiveText","browseButtonText","invalidFileTypesText","invalidFileSizeText","invalidFileCountText","uploadingFiles","setUploadingFiles","useState","handleRemoveFile","fileToRemove","some","state","file","prev","filter","_jsxs","_Fragment","children","_jsx","Typography","paddingBottom","spacing","variant","Stack","direction","flex","gap","FileDropZone","handleUpload","newFiles","newUploadingFiles","map","isUploading","length","fileState","FileItem","name"],"mappings":"mTAiGA,MAAMA,EAAyCC,IAC7C,MAAMC,EAAQC,KACRC,MACJA,EAAQ,cAAaC,MACrBA,EAAKC,iBACLA,EAAgBC,SAChBA,EAAQC,YACRA,EAAWC,SACXA,EAAQC,SACRA,EAAQC,SACRA,GAAW,EAAIC,eACfA,EAAcC,iBACdA,EAAgBC,iBAChBA,EAAgBC,qBAChBA,EAAuB,8DAA6DC,oBACpFA,EAAsB,sDAAqDC,qBAC3EA,EAAuB,wDACrBhB,GAGGiB,EAAgBC,GAAqBC,EAA4Bf,GAelEgB,EAAoBC,IAEAJ,EAAeK,MAAMC,GAAUA,EAAMC,OAASH,KAIpEH,GAAmBO,GAASA,EAAKC,QAAQH,GAAUA,EAAMC,OAASH,MAIpEZ,EAASY,IAGX,OACEM,EAAAC,EAAA,CAAAC,SAAA,CACEC,EAACC,GAAWC,cAAe/B,EAAMgC,QAAQ,GAAIC,QAAQ,SAAQL,SAAE1B,IAC/DwB,EAACQ,EAAK,CAACC,UAAU,SAASC,KAAM,EAAGC,IAAKrC,EAAMgC,QAAQ,GAAEJ,SAAA,CACtDC,EAACS,EAAY,CACXlC,iBAAkBA,EAClBQ,iBAAkBA,EAClBF,eAAgBA,EAChBC,iBAAkBA,EAClB4B,aAnCcC,IAEpB,MAAMC,EAAuCD,EAASE,KAAKnB,IAAI,CAC7DA,OACAoB,aAAa,MAGf1B,GAAmBO,GAAS,IAAIA,KAASiB,KAGzClC,EAASiC,IA0BHzB,qBAAsBA,EACtBD,oBAAqBA,EACrBD,qBAAsBA,EACtBR,SAAUA,EACVC,YAAaA,EACbG,SAAUA,IAGXO,EAAe4B,OAAS,GACvBf,EAACK,GAAMC,UAAU,SAASE,IAAKrC,EAAMgC,QAAQ,KAAIJ,SAC9CZ,EAAe0B,KAAKG,GACnBhB,EAACiB,EAAQ,CAEPvB,KAAMsB,EAAUtB,KAChBoB,YAAaE,EAAUF,YACvBnC,SAAUW,GAHL0B,EAAUtB,KAAKwB"}
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime"),t=require("@mui/material/Box/Box"),r=require("@mui/material/styles/styled"),i=require("@mui/material/TableCell/TableCell"),l=require("@mui/material/Typography");const a=r(i)((()=>({padding:0}))),n=r(l)((({theme:e})=>({color:e.palette.semantic.text["text-weak"]}))),s=r(l)((({theme:e})=>({color:e.palette.semantic.text["text-strong"]}))),o=r(t,{shouldForwardProp:e=>"isColumn"!==e})((({theme:e,isColumn:t})=>({display:"flex",flexDirection:t?"column":"row",height:"80px",paddingRight:e.spacing(3),alignItems:t?"flex-start":"center",justifyContent:t?"center":"flex-start",gap:0,flexShrink:0})));module.exports=({type:t,title:r,description:i})=>e.jsx(a,{children:(()=>{switch(t){case"Term":return e.jsx(o,{children:e.jsx(s,{variant:"body1",children:r})});case"Description":return e.jsx(o,{children:e.jsx(n,{variant:"body1",children:i})});case"SecondaryText":return e.jsxs(o,{isColumn:!0,children:[e.jsx(s,{variant:"body1",children:r}),e.jsx(n,{variant:"body1",children:i})]});default:return null}})()});
2
+ //# sourceMappingURL=SummaryListCell.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SummaryListCell.cjs.js","sources":["../../../../../src/components/organisms/SummaryList/components/SummaryListCell.tsx"],"sourcesContent":["import Box from '@mui/material/Box/Box';\nimport styled from '@mui/material/styles/styled';\nimport TableCell from '@mui/material/TableCell/TableCell';\n\nimport Typography from '@fd/components/atoms/Typography';\n\n/**\n * Props for the SummaryListColumnItem component.\n * Supports three display types: Term (title only), Description (description only), and SecondaryText (title with description).\n */\nexport type SummaryListColumnItemProps =\n | { type: 'Description'; description: string; title?: never }\n | { type: 'SecondaryText'; title: string; description: string }\n | { type: 'Term'; title: string; description?: never };\n\nconst StyledCell = styled(TableCell)(() => ({\n padding: 0,\n}));\n\nconst StyledDescription = styled(Typography)(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n}));\n\nconst StyledTitle = styled(Typography)(({ theme }) => ({\n color: theme.palette.semantic.text['text-strong'],\n}));\n\nconst StyledLeftLayoutContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'isColumn',\n})<{ isColumn?: boolean }>(({ theme, isColumn }) => ({\n display: 'flex',\n flexDirection: isColumn ? 'column' : 'row',\n height: '80px',\n paddingRight: theme.spacing(3),\n alignItems: isColumn ? 'flex-start' : 'center',\n justifyContent: isColumn ? 'center' : 'flex-start',\n gap: 0,\n flexShrink: 0,\n}));\n\n/**\n * A cell component for the SummaryList that renders different content types.\n * Supports three variants: Term (title only), Description (description only), and SecondaryText (title with description).\n *\n * @param props - The component props\n * @returns The rendered cell component\n */\nconst SummaryListColumnItem: React.FC<SummaryListColumnItemProps> = ({ type, title, description }) => {\n /**\n * Returns the appropriate content based on the cell type.\n * Renders different layouts for Term, Description, and SecondaryText types.\n *\n * @returns The rendered content node for the cell\n */\n const getContent = (): React.ReactNode => {\n switch (type) {\n case 'Term':\n return (\n <StyledLeftLayoutContainer>\n <StyledTitle variant=\"body1\">{title}</StyledTitle>\n </StyledLeftLayoutContainer>\n );\n case 'Description':\n return (\n <StyledLeftLayoutContainer>\n <StyledDescription variant=\"body1\">{description}</StyledDescription>\n </StyledLeftLayoutContainer>\n );\n case 'SecondaryText':\n return (\n <StyledLeftLayoutContainer isColumn>\n <StyledTitle variant=\"body1\">{title}</StyledTitle>\n <StyledDescription variant=\"body1\">{description}</StyledDescription>\n </StyledLeftLayoutContainer>\n );\n default:\n return null;\n }\n };\n return <StyledCell>{getContent()}</StyledCell>;\n};\nexport default SummaryListColumnItem;\n"],"names":["StyledCell","styled","TableCell","padding","StyledDescription","Typography","theme","color","palette","semantic","text","StyledTitle","StyledLeftLayoutContainer","Box","shouldForwardProp","prop","isColumn","display","flexDirection","height","paddingRight","spacing","alignItems","justifyContent","gap","flexShrink","type","title","description","_jsx","children","variant","_jsxs","getContent"],"mappings":"iNAeA,MAAMA,EAAaC,EAAOC,EAAPD,EAAkB,KAAA,CACnCE,QAAS,MAGLC,EAAoBH,EAAOI,EAAPJ,EAAmB,EAAGK,YAAO,CACrDC,MAAOD,EAAME,QAAQC,SAASC,KAAK,iBAG/BC,EAAcV,EAAOI,EAAPJ,EAAmB,EAAGK,YAAO,CAC/CC,MAAOD,EAAME,QAAQC,SAASC,KAAK,mBAG/BE,EAA4BX,EAAOY,EAAK,CAC5CC,kBAAoBC,GAAkB,aAATA,GADGd,EAEP,EAAGK,QAAOU,eAAU,CAC7CC,QAAS,OACTC,cAAeF,EAAW,SAAW,MACrCG,OAAQ,OACRC,aAAcd,EAAMe,QAAQ,GAC5BC,WAAYN,EAAW,aAAe,SACtCO,eAAgBP,EAAW,SAAW,aACtCQ,IAAK,EACLC,WAAY,qBAUsD,EAAGC,OAAMC,QAAOC,iBAgC3EC,EAAAA,IAAC7B,EAAU,CAAA8B,SAzBC,MACjB,OAAQJ,GACN,IAAK,OACH,OACEG,MAACjB,EAAyB,CAAAkB,SACxBD,EAAAA,IAAClB,EAAW,CAACoB,QAAQ,QAAOD,SAAEH,MAGpC,IAAK,cACH,OACEE,MAACjB,EAAyB,CAAAkB,SACxBD,EAAAA,IAACzB,EAAiB,CAAC2B,QAAQ,QAAOD,SAAEF,MAG1C,IAAK,gBACH,OACEI,OAACpB,EAAyB,CAACI,UAAQ,EAAAc,SAAA,CACjCD,MAAClB,EAAW,CAACoB,QAAQ,QAAOD,SAAEH,IAC9BE,MAACzB,EAAiB,CAAC2B,QAAQ,QAAOD,SAAEF,OAG1C,QACE,OAAO,OAGOK"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Props for the SummaryListColumnItem component.
3
+ * Supports three display types: Term (title only), Description (description only), and SecondaryText (title with description).
4
+ */
5
+ type SummaryListColumnItemProps = {
6
+ type: 'Description';
7
+ description: string;
8
+ title?: never;
9
+ } | {
10
+ type: 'SecondaryText';
11
+ title: string;
12
+ description: string;
13
+ } | {
14
+ type: 'Term';
15
+ title: string;
16
+ description?: never;
17
+ };
18
+ /**
19
+ * A cell component for the SummaryList that renders different content types.
20
+ * Supports three variants: Term (title only), Description (description only), and SecondaryText (title with description).
21
+ *
22
+ * @param props - The component props
23
+ * @returns The rendered cell component
24
+ */
25
+ declare const SummaryListColumnItem: React.FC<SummaryListColumnItemProps>;
26
+
27
+ export { SummaryListColumnItem as default };
28
+ export type { SummaryListColumnItemProps };
@@ -0,0 +1,2 @@
1
+ import{jsx as e,jsxs as t}from"react/jsx-runtime";import r from"@mui/material/Box/Box";import i from"@mui/material/styles/styled";import a from"@mui/material/TableCell/TableCell";import l from"@mui/material/Typography";const n=i(a)((()=>({padding:0}))),o=i(l)((({theme:e})=>({color:e.palette.semantic.text["text-weak"]}))),m=i(l)((({theme:e})=>({color:e.palette.semantic.text["text-strong"]}))),c=i(r,{shouldForwardProp:e=>"isColumn"!==e})((({theme:e,isColumn:t})=>({display:"flex",flexDirection:t?"column":"row",height:"80px",paddingRight:e.spacing(3),alignItems:t?"flex-start":"center",justifyContent:t?"center":"flex-start",gap:0,flexShrink:0}))),s=({type:r,title:i,description:a})=>e(n,{children:(()=>{switch(r){case"Term":return e(c,{children:e(m,{variant:"body1",children:i})});case"Description":return e(c,{children:e(o,{variant:"body1",children:a})});case"SecondaryText":return t(c,{isColumn:!0,children:[e(m,{variant:"body1",children:i}),e(o,{variant:"body1",children:a})]});default:return null}})()});export{s as default};
2
+ //# sourceMappingURL=SummaryListCell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SummaryListCell.js","sources":["../../../../../src/components/organisms/SummaryList/components/SummaryListCell.tsx"],"sourcesContent":["import Box from '@mui/material/Box/Box';\nimport styled from '@mui/material/styles/styled';\nimport TableCell from '@mui/material/TableCell/TableCell';\n\nimport Typography from '@fd/components/atoms/Typography';\n\n/**\n * Props for the SummaryListColumnItem component.\n * Supports three display types: Term (title only), Description (description only), and SecondaryText (title with description).\n */\nexport type SummaryListColumnItemProps =\n | { type: 'Description'; description: string; title?: never }\n | { type: 'SecondaryText'; title: string; description: string }\n | { type: 'Term'; title: string; description?: never };\n\nconst StyledCell = styled(TableCell)(() => ({\n padding: 0,\n}));\n\nconst StyledDescription = styled(Typography)(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n}));\n\nconst StyledTitle = styled(Typography)(({ theme }) => ({\n color: theme.palette.semantic.text['text-strong'],\n}));\n\nconst StyledLeftLayoutContainer = styled(Box, {\n shouldForwardProp: (prop) => prop !== 'isColumn',\n})<{ isColumn?: boolean }>(({ theme, isColumn }) => ({\n display: 'flex',\n flexDirection: isColumn ? 'column' : 'row',\n height: '80px',\n paddingRight: theme.spacing(3),\n alignItems: isColumn ? 'flex-start' : 'center',\n justifyContent: isColumn ? 'center' : 'flex-start',\n gap: 0,\n flexShrink: 0,\n}));\n\n/**\n * A cell component for the SummaryList that renders different content types.\n * Supports three variants: Term (title only), Description (description only), and SecondaryText (title with description).\n *\n * @param props - The component props\n * @returns The rendered cell component\n */\nconst SummaryListColumnItem: React.FC<SummaryListColumnItemProps> = ({ type, title, description }) => {\n /**\n * Returns the appropriate content based on the cell type.\n * Renders different layouts for Term, Description, and SecondaryText types.\n *\n * @returns The rendered content node for the cell\n */\n const getContent = (): React.ReactNode => {\n switch (type) {\n case 'Term':\n return (\n <StyledLeftLayoutContainer>\n <StyledTitle variant=\"body1\">{title}</StyledTitle>\n </StyledLeftLayoutContainer>\n );\n case 'Description':\n return (\n <StyledLeftLayoutContainer>\n <StyledDescription variant=\"body1\">{description}</StyledDescription>\n </StyledLeftLayoutContainer>\n );\n case 'SecondaryText':\n return (\n <StyledLeftLayoutContainer isColumn>\n <StyledTitle variant=\"body1\">{title}</StyledTitle>\n <StyledDescription variant=\"body1\">{description}</StyledDescription>\n </StyledLeftLayoutContainer>\n );\n default:\n return null;\n }\n };\n return <StyledCell>{getContent()}</StyledCell>;\n};\nexport default SummaryListColumnItem;\n"],"names":["StyledCell","styled","TableCell","padding","StyledDescription","Typography","theme","color","palette","semantic","text","StyledTitle","StyledLeftLayoutContainer","Box","shouldForwardProp","prop","isColumn","display","flexDirection","height","paddingRight","spacing","alignItems","justifyContent","gap","flexShrink","SummaryListColumnItem","type","title","description","_jsx","children","variant","_jsxs","getContent"],"mappings":"2NAeA,MAAMA,EAAaC,EAAOC,EAAPD,EAAkB,KAAA,CACnCE,QAAS,MAGLC,EAAoBH,EAAOI,EAAPJ,EAAmB,EAAGK,YAAO,CACrDC,MAAOD,EAAME,QAAQC,SAASC,KAAK,iBAG/BC,EAAcV,EAAOI,EAAPJ,EAAmB,EAAGK,YAAO,CAC/CC,MAAOD,EAAME,QAAQC,SAASC,KAAK,mBAG/BE,EAA4BX,EAAOY,EAAK,CAC5CC,kBAAoBC,GAAkB,aAATA,GADGd,EAEP,EAAGK,QAAOU,eAAU,CAC7CC,QAAS,OACTC,cAAeF,EAAW,SAAW,MACrCG,OAAQ,OACRC,aAAcd,EAAMe,QAAQ,GAC5BC,WAAYN,EAAW,aAAe,SACtCO,eAAgBP,EAAW,SAAW,aACtCQ,IAAK,EACLC,WAAY,MAURC,EAA8D,EAAGC,OAAMC,QAAOC,iBAgC3EC,EAAC9B,EAAU,CAAA+B,SAzBC,MACjB,OAAQJ,GACN,IAAK,OACH,OACEG,EAAClB,EAAyB,CAAAmB,SACxBD,EAACnB,EAAW,CAACqB,QAAQ,QAAOD,SAAEH,MAGpC,IAAK,cACH,OACEE,EAAClB,EAAyB,CAAAmB,SACxBD,EAAC1B,EAAiB,CAAC4B,QAAQ,QAAOD,SAAEF,MAG1C,IAAK,gBACH,OACEI,EAACrB,EAAyB,CAACI,UAAQ,EAAAe,SAAA,CACjCD,EAACnB,EAAW,CAACqB,QAAQ,QAAOD,SAAEH,IAC9BE,EAAC1B,EAAiB,CAAC4B,QAAQ,QAAOD,SAAEF,OAG1C,QACE,OAAO,OAGOK"}
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("react/jsx-runtime"),r=require("@mui/material/TableRow/TableRow");module.exports=({children:i})=>e.jsx(r,{children:i});
2
+ //# sourceMappingURL=SummaryListRow.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SummaryListRow.cjs.js","sources":["../../../../../src/components/organisms/SummaryList/components/SummaryListRow.tsx"],"sourcesContent":["import TableRow from '@mui/material/TableRow/TableRow';\n\n/**\n * Props for the SummaryListRow component.\n */\nexport interface SummaryListRowProps {\n /** The cell components to render within the row. */\n children: React.ReactNode;\n}\n\n/**\n * A row component for the SummaryList that wraps cell components.\n * Provides the table row structure for SummaryListCell components.\n *\n * @param props - The component props\n * @returns The rendered table row component\n */\nconst SummaryListRow: React.FC<SummaryListRowProps> = ({ children }) => {\n return <TableRow>{children}</TableRow>;\n};\n\nexport default SummaryListRow;\n"],"names":["children","_jsx","TableRow"],"mappings":"4GAiBsD,EAAGA,cAChDC,EAAAA,IAACC,EAAQ,CAAAF,SAAEA"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Props for the SummaryListRow component.
3
+ */
4
+ interface SummaryListRowProps {
5
+ /** The cell components to render within the row. */
6
+ children: React.ReactNode;
7
+ }
8
+ /**
9
+ * A row component for the SummaryList that wraps cell components.
10
+ * Provides the table row structure for SummaryListCell components.
11
+ *
12
+ * @param props - The component props
13
+ * @returns The rendered table row component
14
+ */
15
+ declare const SummaryListRow: React.FC<SummaryListRowProps>;
16
+
17
+ export { SummaryListRow as default };
18
+ export type { SummaryListRowProps };
@@ -0,0 +1,2 @@
1
+ import{jsx as r}from"react/jsx-runtime";import e from"@mui/material/TableRow/TableRow";const o=({children:o})=>r(e,{children:o});export{o as default};
2
+ //# sourceMappingURL=SummaryListRow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SummaryListRow.js","sources":["../../../../../src/components/organisms/SummaryList/components/SummaryListRow.tsx"],"sourcesContent":["import TableRow from '@mui/material/TableRow/TableRow';\n\n/**\n * Props for the SummaryListRow component.\n */\nexport interface SummaryListRowProps {\n /** The cell components to render within the row. */\n children: React.ReactNode;\n}\n\n/**\n * A row component for the SummaryList that wraps cell components.\n * Provides the table row structure for SummaryListCell components.\n *\n * @param props - The component props\n * @returns The rendered table row component\n */\nconst SummaryListRow: React.FC<SummaryListRowProps> = ({ children }) => {\n return <TableRow>{children}</TableRow>;\n};\n\nexport default SummaryListRow;\n"],"names":["SummaryListRow","children","_jsx","TableRow"],"mappings":"uFAiBA,MAAMA,EAAgD,EAAGC,cAChDC,EAACC,EAAQ,CAAAF,SAAEA"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),r=require("react"),t=require("@mui/material/Box/Box"),i=require("@mui/material/styles/styled"),s=require("@mui/material/Table/Table"),l=require("@mui/material/TableBody/TableBody"),o=require("./components/SummaryListCell.cjs.js"),a=require("./components/SummaryListRow.cjs.js");const d=i(t)((()=>({flex:1,overflow:"auto"}))),n=i(s,{shouldForwardProp:e=>"hideLastDivider"!==e})((({hideLastDivider:e})=>({tableLayout:"fixed",...e&&{"& tbody tr:last-child td, & tbody tr:last-child th":{borderBottom:"none"}}})));exports.SummaryListCell=o,exports.SummaryListRow=a,exports.default=({children:t,hideLastDivider:i,rows:s})=>{const u=r.useMemo((()=>s&&0!==s.length?s.map(((r,t)=>e.jsx(a,{children:r.cells.map(((r,t)=>e.jsx(o,{...r},((e,r)=>{const t="title"in e?e.title:"",i="description"in e?e.description:"";return`summary-cell-${e.type}-${t}-${i}-${r}`})(r,t))))},((e,r)=>`summary-row-${e.cells.map((e=>{const r="title"in e?e.title:"",t="description"in e?e.description:"";return`${e.type}-${r}-${t}`})).join("|")}-${r}`)(r,t)))):[]),[s]);return e.jsx(d,{children:e.jsx(n,{hideLastDivider:i,children:e.jsx(l,{children:s&&s.length>0?u:t})})})};
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../../src/components/organisms/SummaryList/index.tsx"],"sourcesContent":["import { useMemo } from 'react';\n\nimport Box from '@mui/material/Box/Box';\nimport styled from '@mui/material/styles/styled';\nimport Table from '@mui/material/Table/Table';\nimport TableBody from '@mui/material/TableBody/TableBody';\n\nimport SummaryListCell, { type SummaryListColumnItemProps } from './components/SummaryListCell';\nimport SummaryListRow from './components/SummaryListRow';\n\nconst StyledBox = styled(Box)(() => ({\n flex: 1,\n overflow: 'auto',\n}));\n\nconst StyledTable = styled(Table, {\n shouldForwardProp: (prop) => prop !== 'hideLastDivider',\n})<{ hideLastDivider?: boolean }>(({ hideLastDivider }) => ({\n tableLayout: 'fixed',\n ...(hideLastDivider && {\n '& tbody tr:last-child td, & tbody tr:last-child th': {\n borderBottom: 'none',\n },\n }),\n}));\n\n/**\n * Data structure for a single row in the SummaryList.\n */\nexport interface SummaryListRowData {\n /** Array of cell data for this row. */\n cells: SummaryListColumnItemProps[];\n}\n\n/**\n * Props for the SummaryList component.\n */\nexport interface SummaryListProps {\n /** The row components to render within the list. Used when rows prop is not provided. */\n children?: React.ReactNode;\n /** Hides the bottom border of the last row when true. */\n hideLastDivider?: boolean;\n /** Array of row data to render. Takes precedence over children when provided. */\n rows?: SummaryListRowData[];\n}\n\n/**\n * A summary list component that displays data in a structured table format.\n * Supports optional hiding of the last row divider for cleaner visual presentation.\n * Can be used with either a `rows` prop for data-driven rendering or `children` for custom rendering.\n *\n * @param props - The component props\n * @returns The rendered summary list component\n */\nconst SummaryList: React.FC<SummaryListProps> = ({ children, hideLastDivider, rows }) => {\n /**\n * Generates a unique key for a row based on its content.\n *\n * @param row - The row data\n * @param rowIndex - The index of the row in the array\n * @returns A unique key string\n */\n const getRowKey = (row: SummaryListRowData, rowIndex: number): string => {\n // Create a key from the row's cell content\n const cellContent = row.cells\n .map((cell) => {\n const title = 'title' in cell ? cell.title : '';\n const description = 'description' in cell ? cell.description : '';\n return `${cell.type}-${title}-${description}`;\n })\n .join('|');\n // Include index as fallback for uniqueness, but base it on content first\n return `summary-row-${cellContent}-${rowIndex}`;\n };\n\n /**\n * Generates a unique key for a cell based on its content.\n *\n * @param cell - The cell data\n * @param cellIndex - The index of the cell in the row\n * @returns A unique key string\n */\n const getCellKey = (cell: SummaryListColumnItemProps, cellIndex: number): string => {\n const title = 'title' in cell ? cell.title : '';\n const description = 'description' in cell ? cell.description : '';\n // Base key on content, with index as fallback\n return `summary-cell-${cell.type}-${title}-${description}-${cellIndex}`;\n };\n\n /**\n * Renders rows from the rows prop data.\n * Maps over the rows array and creates SummaryListRow and SummaryListCell components.\n *\n * @returns Array of rendered row components\n */\n const renderedRows = useMemo((): React.ReactNode[] => {\n if (!rows || rows.length === 0) {\n return [];\n }\n\n return rows.map((row, rowIndex) => (\n <SummaryListRow key={getRowKey(row, rowIndex)}>\n {row.cells.map((cell, cellIndex) => (\n <SummaryListCell key={getCellKey(cell, cellIndex)} {...cell} />\n ))}\n </SummaryListRow>\n ));\n }, [rows]);\n\n /**\n * Determines what content to render in the table body.\n * If rows are provided and have length > 0, render from rows.\n * Otherwise, render children (existing pattern).\n *\n * @returns The content to render in the table body\n */\n const renderContent = (): React.ReactNode => {\n if (rows && rows.length > 0) {\n return renderedRows;\n }\n\n return children;\n };\n\n return (\n <StyledBox>\n <StyledTable hideLastDivider={hideLastDivider}>\n <TableBody>{renderContent()}</TableBody>\n </StyledTable>\n </StyledBox>\n );\n};\n\nexport default SummaryList;\nexport { SummaryListCell, SummaryListRow };\n"],"names":["StyledBox","styled","Box","flex","overflow","StyledTable","Table","shouldForwardProp","prop","hideLastDivider","tableLayout","borderBottom","children","rows","renderedRows","useMemo","length","map","row","rowIndex","_jsx","SummaryListRow","cells","cell","cellIndex","SummaryListCell","title","description","type","getCellKey","join","getRowKey","TableBody"],"mappings":"6XAUA,MAAMA,EAAYC,EAAOC,EAAPD,EAAY,KAAA,CAC5BE,KAAM,EACNC,SAAU,WAGNC,EAAcJ,EAAOK,EAAO,CAChCC,kBAAoBC,GAAkB,oBAATA,GADXP,EAEc,EAAGQ,sBAAiB,CACpDC,YAAa,WACTD,GAAmB,CACrB,qDAAsD,CACpDE,aAAc,gFAiC4B,EAAGC,WAAUH,kBAAiBI,WAQ5E,MAiCMC,EAAeC,EAAAA,SAAQ,IACtBF,GAAwB,IAAhBA,EAAKG,OAIXH,EAAKI,KAAI,CAACC,EAAKC,IACpBC,EAAAA,IAACC,EAAc,CAAAT,SACZM,EAAII,MAAML,KAAI,CAACM,EAAMC,IACpBJ,MAACK,EAAe,IAAuCF,GArB5C,EAACA,EAAkCC,KACpD,MAAME,EAAQ,UAAWH,EAAOA,EAAKG,MAAQ,GACvCC,EAAc,gBAAiBJ,EAAOA,EAAKI,YAAc,GAE/D,MAAO,gBAAgBJ,EAAKK,QAAQF,KAASC,KAAeH,KAiBhCK,CAAWN,EAAMC,OAzC7B,EAACN,EAAyBC,IAUnC,eARaD,EAAII,MACrBL,KAAKM,IACJ,MAAMG,EAAQ,UAAWH,EAAOA,EAAKG,MAAQ,GACvCC,EAAc,gBAAiBJ,EAAOA,EAAKI,YAAc,GAC/D,MAAO,GAAGJ,EAAKK,QAAQF,KAASC,OAEjCG,KAAK,QAE6BX,IA6BdY,CAAUb,EAAKC,MAJ7B,IAUR,CAACN,IAiBJ,OACEO,EAAAA,IAACpB,EAAS,CAAAY,SACRQ,EAAAA,IAACf,EAAW,CAACI,gBAAiBA,WAC5BW,EAAAA,IAACY,YAVDnB,GAAQA,EAAKG,OAAS,EACjBF,EAGFF"}
@@ -0,0 +1,34 @@
1
+ import { SummaryListColumnItemProps } from './components/SummaryListCell.js';
2
+ export { default as SummaryListCell } from './components/SummaryListCell.js';
3
+ export { default as SummaryListRow } from './components/SummaryListRow.js';
4
+
5
+ /**
6
+ * Data structure for a single row in the SummaryList.
7
+ */
8
+ interface SummaryListRowData {
9
+ /** Array of cell data for this row. */
10
+ cells: SummaryListColumnItemProps[];
11
+ }
12
+ /**
13
+ * Props for the SummaryList component.
14
+ */
15
+ interface SummaryListProps {
16
+ /** The row components to render within the list. Used when rows prop is not provided. */
17
+ children?: React.ReactNode;
18
+ /** Hides the bottom border of the last row when true. */
19
+ hideLastDivider?: boolean;
20
+ /** Array of row data to render. Takes precedence over children when provided. */
21
+ rows?: SummaryListRowData[];
22
+ }
23
+ /**
24
+ * A summary list component that displays data in a structured table format.
25
+ * Supports optional hiding of the last row divider for cleaner visual presentation.
26
+ * Can be used with either a `rows` prop for data-driven rendering or `children` for custom rendering.
27
+ *
28
+ * @param props - The component props
29
+ * @returns The rendered summary list component
30
+ */
31
+ declare const SummaryList: React.FC<SummaryListProps>;
32
+
33
+ export { SummaryList as default };
34
+ export type { SummaryListProps, SummaryListRowData };
@@ -0,0 +1,2 @@
1
+ import{jsx as t}from"react/jsx-runtime";import{useMemo as r}from"react";import i from"@mui/material/Box/Box";import e from"@mui/material/styles/styled";import o from"@mui/material/Table/Table";import m from"@mui/material/TableBody/TableBody";import l from"./components/SummaryListCell.js";import a from"./components/SummaryListRow.js";const s=e(i)((()=>({flex:1,overflow:"auto"}))),d=e(o,{shouldForwardProp:t=>"hideLastDivider"!==t})((({hideLastDivider:t})=>({tableLayout:"fixed",...t&&{"& tbody tr:last-child td, & tbody tr:last-child th":{borderBottom:"none"}}}))),n=({children:i,hideLastDivider:e,rows:o})=>{const n=r((()=>o&&0!==o.length?o.map(((r,i)=>t(a,{children:r.cells.map(((r,i)=>t(l,{...r},((t,r)=>{const i="title"in t?t.title:"",e="description"in t?t.description:"";return`summary-cell-${t.type}-${i}-${e}-${r}`})(r,i))))},((t,r)=>`summary-row-${t.cells.map((t=>{const r="title"in t?t.title:"",i="description"in t?t.description:"";return`${t.type}-${r}-${i}`})).join("|")}-${r}`)(r,i)))):[]),[o]);return t(s,{children:t(d,{hideLastDivider:e,children:t(m,{children:o&&o.length>0?n:i})})})};export{l as SummaryListCell,a as SummaryListRow,n as default};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/organisms/SummaryList/index.tsx"],"sourcesContent":["import { useMemo } from 'react';\n\nimport Box from '@mui/material/Box/Box';\nimport styled from '@mui/material/styles/styled';\nimport Table from '@mui/material/Table/Table';\nimport TableBody from '@mui/material/TableBody/TableBody';\n\nimport SummaryListCell, { type SummaryListColumnItemProps } from './components/SummaryListCell';\nimport SummaryListRow from './components/SummaryListRow';\n\nconst StyledBox = styled(Box)(() => ({\n flex: 1,\n overflow: 'auto',\n}));\n\nconst StyledTable = styled(Table, {\n shouldForwardProp: (prop) => prop !== 'hideLastDivider',\n})<{ hideLastDivider?: boolean }>(({ hideLastDivider }) => ({\n tableLayout: 'fixed',\n ...(hideLastDivider && {\n '& tbody tr:last-child td, & tbody tr:last-child th': {\n borderBottom: 'none',\n },\n }),\n}));\n\n/**\n * Data structure for a single row in the SummaryList.\n */\nexport interface SummaryListRowData {\n /** Array of cell data for this row. */\n cells: SummaryListColumnItemProps[];\n}\n\n/**\n * Props for the SummaryList component.\n */\nexport interface SummaryListProps {\n /** The row components to render within the list. Used when rows prop is not provided. */\n children?: React.ReactNode;\n /** Hides the bottom border of the last row when true. */\n hideLastDivider?: boolean;\n /** Array of row data to render. Takes precedence over children when provided. */\n rows?: SummaryListRowData[];\n}\n\n/**\n * A summary list component that displays data in a structured table format.\n * Supports optional hiding of the last row divider for cleaner visual presentation.\n * Can be used with either a `rows` prop for data-driven rendering or `children` for custom rendering.\n *\n * @param props - The component props\n * @returns The rendered summary list component\n */\nconst SummaryList: React.FC<SummaryListProps> = ({ children, hideLastDivider, rows }) => {\n /**\n * Generates a unique key for a row based on its content.\n *\n * @param row - The row data\n * @param rowIndex - The index of the row in the array\n * @returns A unique key string\n */\n const getRowKey = (row: SummaryListRowData, rowIndex: number): string => {\n // Create a key from the row's cell content\n const cellContent = row.cells\n .map((cell) => {\n const title = 'title' in cell ? cell.title : '';\n const description = 'description' in cell ? cell.description : '';\n return `${cell.type}-${title}-${description}`;\n })\n .join('|');\n // Include index as fallback for uniqueness, but base it on content first\n return `summary-row-${cellContent}-${rowIndex}`;\n };\n\n /**\n * Generates a unique key for a cell based on its content.\n *\n * @param cell - The cell data\n * @param cellIndex - The index of the cell in the row\n * @returns A unique key string\n */\n const getCellKey = (cell: SummaryListColumnItemProps, cellIndex: number): string => {\n const title = 'title' in cell ? cell.title : '';\n const description = 'description' in cell ? cell.description : '';\n // Base key on content, with index as fallback\n return `summary-cell-${cell.type}-${title}-${description}-${cellIndex}`;\n };\n\n /**\n * Renders rows from the rows prop data.\n * Maps over the rows array and creates SummaryListRow and SummaryListCell components.\n *\n * @returns Array of rendered row components\n */\n const renderedRows = useMemo((): React.ReactNode[] => {\n if (!rows || rows.length === 0) {\n return [];\n }\n\n return rows.map((row, rowIndex) => (\n <SummaryListRow key={getRowKey(row, rowIndex)}>\n {row.cells.map((cell, cellIndex) => (\n <SummaryListCell key={getCellKey(cell, cellIndex)} {...cell} />\n ))}\n </SummaryListRow>\n ));\n }, [rows]);\n\n /**\n * Determines what content to render in the table body.\n * If rows are provided and have length > 0, render from rows.\n * Otherwise, render children (existing pattern).\n *\n * @returns The content to render in the table body\n */\n const renderContent = (): React.ReactNode => {\n if (rows && rows.length > 0) {\n return renderedRows;\n }\n\n return children;\n };\n\n return (\n <StyledBox>\n <StyledTable hideLastDivider={hideLastDivider}>\n <TableBody>{renderContent()}</TableBody>\n </StyledTable>\n </StyledBox>\n );\n};\n\nexport default SummaryList;\nexport { SummaryListCell, SummaryListRow };\n"],"names":["StyledBox","styled","Box","flex","overflow","StyledTable","Table","shouldForwardProp","prop","hideLastDivider","tableLayout","borderBottom","SummaryList","children","rows","renderedRows","useMemo","length","map","row","rowIndex","_jsx","SummaryListRow","cells","cell","cellIndex","SummaryListCell","title","description","type","getCellKey","join","getRowKey","TableBody"],"mappings":"+UAUA,MAAMA,EAAYC,EAAOC,EAAPD,EAAY,KAAA,CAC5BE,KAAM,EACNC,SAAU,WAGNC,EAAcJ,EAAOK,EAAO,CAChCC,kBAAoBC,GAAkB,oBAATA,GADXP,EAEc,EAAGQ,sBAAiB,CACpDC,YAAa,WACTD,GAAmB,CACrB,qDAAsD,CACpDE,aAAc,aAiCdC,EAA0C,EAAGC,WAAUJ,kBAAiBK,WAQ5E,MAiCMC,EAAeC,GAAQ,IACtBF,GAAwB,IAAhBA,EAAKG,OAIXH,EAAKI,KAAI,CAACC,EAAKC,IACpBC,EAACC,EAAc,CAAAT,SACZM,EAAII,MAAML,KAAI,CAACM,EAAMC,IACpBJ,EAACK,EAAe,IAAuCF,GArB5C,EAACA,EAAkCC,KACpD,MAAME,EAAQ,UAAWH,EAAOA,EAAKG,MAAQ,GACvCC,EAAc,gBAAiBJ,EAAOA,EAAKI,YAAc,GAE/D,MAAO,gBAAgBJ,EAAKK,QAAQF,KAASC,KAAeH,KAiBhCK,CAAWN,EAAMC,OAzC7B,EAACN,EAAyBC,IAUnC,eARaD,EAAII,MACrBL,KAAKM,IACJ,MAAMG,EAAQ,UAAWH,EAAOA,EAAKG,MAAQ,GACvCC,EAAc,gBAAiBJ,EAAOA,EAAKI,YAAc,GAC/D,MAAO,GAAGJ,EAAKK,QAAQF,KAASC,OAEjCG,KAAK,QAE6BX,IA6BdY,CAAUb,EAAKC,MAJ7B,IAUR,CAACN,IAiBJ,OACEO,EAACrB,EAAS,CAAAa,SACRQ,EAAChB,EAAW,CAACI,gBAAiBA,WAC5BY,EAACY,YAVDnB,GAAQA,EAAKG,OAAS,EACjBF,EAGFF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flipdish/portal-library",
3
- "version": "7.4.0",
3
+ "version": "7.5.1",
4
4
  "files": [
5
5
  "dist"
6
6
  ],