@flipdish/portal-library 7.3.5 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +6 -0
  2. package/dist/components/atoms/Avatar/index.cjs.js.map +1 -1
  3. package/dist/components/atoms/Avatar/index.js.map +1 -1
  4. package/dist/components/atoms/Button/buttonThemeOverrides.cjs.js.map +1 -1
  5. package/dist/components/atoms/Button/buttonThemeOverrides.js.map +1 -1
  6. package/dist/components/atoms/Radio/index.cjs.js.map +1 -1
  7. package/dist/components/atoms/Radio/index.js.map +1 -1
  8. package/dist/components/atoms/Switch/index.cjs.js.map +1 -1
  9. package/dist/components/atoms/Switch/index.d.ts +1 -1
  10. package/dist/components/atoms/Switch/index.js.map +1 -1
  11. package/dist/components/molecules/AlertGlobal/index.cjs.js.map +1 -1
  12. package/dist/components/molecules/AlertGlobal/index.js.map +1 -1
  13. package/dist/components/molecules/Autocomplete/index.cjs.js.map +1 -1
  14. package/dist/components/molecules/Autocomplete/index.d.ts +2 -0
  15. package/dist/components/molecules/Autocomplete/index.js.map +1 -1
  16. package/dist/components/molecules/EmptyState/index.cjs.js +2 -0
  17. package/dist/components/molecules/EmptyState/index.cjs.js.map +1 -0
  18. package/dist/components/molecules/EmptyState/index.d.ts +34 -0
  19. package/dist/components/molecules/EmptyState/index.js +2 -0
  20. package/dist/components/molecules/EmptyState/index.js.map +1 -0
  21. package/dist/components/molecules/RadioGroup/index.cjs.js.map +1 -1
  22. package/dist/components/molecules/RadioGroup/index.js.map +1 -1
  23. package/dist/components/organisms/FileUpload/components/FileDropZone.cjs.js +2 -0
  24. package/dist/components/organisms/FileUpload/components/FileDropZone.cjs.js.map +1 -0
  25. package/dist/components/organisms/FileUpload/components/FileDropZone.d.ts +78 -0
  26. package/dist/components/organisms/FileUpload/components/FileDropZone.js +2 -0
  27. package/dist/components/organisms/FileUpload/components/FileDropZone.js.map +1 -0
  28. package/dist/components/organisms/FileUpload/components/FileItem.cjs.js +2 -0
  29. package/dist/components/organisms/FileUpload/components/FileItem.cjs.js.map +1 -0
  30. package/dist/components/organisms/FileUpload/components/FileItem.d.ts +12 -0
  31. package/dist/components/organisms/FileUpload/components/FileItem.js +2 -0
  32. package/dist/components/organisms/FileUpload/components/FileItem.js.map +1 -0
  33. package/dist/components/organisms/FileUpload/components/FileThumbnail.cjs.js +2 -0
  34. package/dist/components/organisms/FileUpload/components/FileThumbnail.cjs.js.map +1 -0
  35. package/dist/components/organisms/FileUpload/components/FileThumbnail.d.ts +9 -0
  36. package/dist/components/organisms/FileUpload/components/FileThumbnail.js +2 -0
  37. package/dist/components/organisms/FileUpload/components/FileThumbnail.js.map +1 -0
  38. package/dist/components/organisms/FileUpload/index.cjs.js +2 -0
  39. package/dist/components/organisms/FileUpload/index.cjs.js.map +1 -0
  40. package/dist/components/organisms/FileUpload/index.d.ts +78 -0
  41. package/dist/components/organisms/FileUpload/index.js +2 -0
  42. package/dist/components/organisms/FileUpload/index.js.map +1 -0
  43. package/dist/components/organisms/SummaryList/components/SummaryListCell.cjs.js +2 -0
  44. package/dist/components/organisms/SummaryList/components/SummaryListCell.cjs.js.map +1 -0
  45. package/dist/components/organisms/SummaryList/components/SummaryListCell.d.ts +28 -0
  46. package/dist/components/organisms/SummaryList/components/SummaryListCell.js +2 -0
  47. package/dist/components/organisms/SummaryList/components/SummaryListCell.js.map +1 -0
  48. package/dist/components/organisms/SummaryList/components/SummaryListRow.cjs.js +2 -0
  49. package/dist/components/organisms/SummaryList/components/SummaryListRow.cjs.js.map +1 -0
  50. package/dist/components/organisms/SummaryList/components/SummaryListRow.d.ts +18 -0
  51. package/dist/components/organisms/SummaryList/components/SummaryListRow.js +2 -0
  52. package/dist/components/organisms/SummaryList/components/SummaryListRow.js.map +1 -0
  53. package/dist/components/organisms/SummaryList/index.cjs.js +2 -0
  54. package/dist/components/organisms/SummaryList/index.cjs.js.map +1 -0
  55. package/dist/components/organisms/SummaryList/index.d.ts +34 -0
  56. package/dist/components/organisms/SummaryList/index.js +2 -0
  57. package/dist/components/organisms/SummaryList/index.js.map +1 -0
  58. package/package.json +1 -1
@@ -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";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),t=require("@mui/material/Box"),i=require("@mui/material/styles"),l=require("@mui/material/Typography"),r=require("../../atoms/Button/index.cjs.js"),a=require("../../atoms/IconContainer/index.cjs.js"),n=require("../ButtonGroup/index.cjs.js");const s=i.styled(t,{shouldForwardProp:e=>"fullWidth"!==e})((({theme:e,fullWidth:t})=>({display:"flex",flexDirection:"column",alignItems:"flex-start",gap:e.spacing(3),width:t?"100%":"536px"}))),o=i.styled(t)((()=>({display:"flex",justifyContent:"flex-end",width:"100%"}))),d=i.styled(t)((({theme:e})=>({display:"flex",flexDirection:"column",alignItems:"flex-start",gap:e.spacing(1)}))),c=i.styled(l)((({theme:e})=>({color:e.palette.semantic.text["text-weak"]}))),u=({title:t,description:i,icon:u,actionButtons:x,fullWidth:m=!1})=>e.jsxs(s,{"data-testid":"empty-state-container",fullWidth:m,children:[u?e.jsx(a,{"data-testid":"empty-state-icon-container",icon:u,style:"filled",tone:"neutral"}):null,e.jsxs(d,{children:[e.jsx(l,{variant:"h3Strong",children:t}),e.jsx(c,{variant:"b1Weak",children:i})]}),(()=>{if(x)return Array.isArray(x)?e.jsx(n,{align:"right",layout:"horizontal",order:"default",size:"medium",children:x.map((t=>{const i=`action-button-${t.label.replace(/\s+/g,"-").toLowerCase()}`;return e.jsx(r.Button,{fdKey:t.label,onClick:t.onClick,variant:t.variant,children:t.label},i)}))}):e.jsx(o,{children:e.jsx(r.Button,{fdKey:x.label,onClick:x.onClick,variant:x.variant,children:x.label})})})()]});exports.EmptyState=u,exports.default=u;
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../../src/components/molecules/EmptyState/index.tsx"],"sourcesContent":["import Box from '@mui/material/Box';\nimport { styled } from '@mui/material/styles';\nimport Typography from '@mui/material/Typography';\n\nimport Button from '@fd/components/atoms/Button';\nimport type { ButtonType } from '@fd/components/atoms/Button/getButtonStyles';\nimport IconContainer from '@fd/components/atoms/IconContainer';\n\nimport ButtonGroup from '../ButtonGroup';\n\n/** Action button configuration */\nexport interface ActionButton {\n /** Text label for the action button */\n label: string;\n /** Visual variant of the action button */\n variant?: ButtonType;\n /** Callback function when the action button is clicked */\n onClick: () => void;\n}\n\n/** EmptyState component props */\nexport interface EmptyStateProps {\n /** Main heading text of the empty state */\n title: string;\n /** Detailed description text of the empty state */\n description: string;\n /** Icon to display */\n icon?: React.ReactNode;\n /** Action buttons to display */\n actionButtons?: ActionButton | ActionButton[];\n /** Whether the empty state should be full width */\n fullWidth?: boolean;\n}\n\nconst StyledContainer = styled(Box, { shouldForwardProp: (prop) => prop !== 'fullWidth' })<{\n fullWidth?: boolean;\n}>(({ theme, fullWidth }) => ({\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n gap: theme.spacing(3),\n width: fullWidth ? '100%' : '536px',\n}));\n\nconst StyledOneActionButtonContainer = styled(Box)(() => ({\n display: 'flex',\n justifyContent: 'flex-end',\n width: '100%',\n}));\n\nconst StyledTextContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n gap: theme.spacing(1),\n}));\n\nconst StyledDescription = styled(Typography)(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n}));\n\n/**\n * EmptyState component is used when a table, list, or chart has no data to display.\n *\n * @param props - The component props\n * @returns The empty state component\n */\n\nexport const EmptyState = ({\n title,\n description,\n icon,\n actionButtons,\n fullWidth = false,\n}: EmptyStateProps): JSX.Element => {\n const renderActions = (): React.ReactNode => {\n if (!actionButtons) {\n return undefined;\n }\n\n if (Array.isArray(actionButtons)) {\n return (\n <ButtonGroup align=\"right\" layout=\"horizontal\" order=\"default\" size=\"medium\">\n {actionButtons.map((button) => {\n const key = `action-button-${button.label.replace(/\\s+/g, '-').toLowerCase()}`;\n\n return (\n <Button key={key} fdKey={button.label} onClick={button.onClick} variant={button.variant}>\n {button.label}\n </Button>\n );\n })}\n </ButtonGroup>\n );\n }\n\n return (\n <StyledOneActionButtonContainer>\n <Button fdKey={actionButtons.label} onClick={actionButtons.onClick} variant={actionButtons.variant}>\n {actionButtons.label}\n </Button>\n </StyledOneActionButtonContainer>\n );\n };\n\n const renderIcon = (): React.ReactNode => {\n if (!icon) {\n return null;\n }\n\n return (\n <IconContainer data-testid=\"empty-state-icon-container\" icon={icon} style=\"filled\" tone=\"neutral\" />\n );\n };\n\n return (\n <StyledContainer data-testid=\"empty-state-container\" fullWidth={fullWidth}>\n {renderIcon()}\n <StyledTextContainer>\n <Typography variant=\"h3Strong\">{title}</Typography>\n <StyledDescription variant=\"b1Weak\">{description}</StyledDescription>\n </StyledTextContainer>\n {renderActions()}\n </StyledContainer>\n );\n};\n\nexport default EmptyState;\n"],"names":["StyledContainer","styled","Box","shouldForwardProp","prop","theme","fullWidth","display","flexDirection","alignItems","gap","spacing","width","StyledOneActionButtonContainer","justifyContent","StyledTextContainer","StyledDescription","Typography","color","palette","semantic","text","EmptyState","title","description","icon","actionButtons","_jsxs","children","_jsx","IconContainer","style","tone","variant","Array","isArray","ButtonGroup","align","layout","order","size","map","button","key","label","replace","toLowerCase","Button","fdKey","onClick","renderActions"],"mappings":"wVAkCA,MAAMA,EAAkBC,EAAAA,OAAOC,EAAK,CAAEC,kBAAoBC,GAAkB,cAATA,GAA3CH,EAErB,EAAGI,QAAOC,gBAAW,CACtBC,QAAS,OACTC,cAAe,SACfC,WAAY,aACZC,IAAKL,EAAMM,QAAQ,GACnBC,MAAON,EAAY,OAAS,YAGxBO,EAAiCZ,EAAAA,OAAOC,EAAPD,EAAY,KAAA,CACjDM,QAAS,OACTO,eAAgB,WAChBF,MAAO,WAGHG,EAAsBd,EAAAA,OAAOC,EAAPD,EAAY,EAAGI,YAAO,CAChDE,QAAS,OACTC,cAAe,SACfC,WAAY,aACZC,IAAKL,EAAMM,QAAQ,OAGfK,EAAoBf,EAAAA,OAAOgB,EAAPhB,EAAmB,EAAGI,YAAO,CACrDa,MAAOb,EAAMc,QAAQC,SAASC,KAAK,iBAUxBC,EAAa,EACxBC,QACAC,cACAC,OACAC,gBACApB,aAAY,KA2CVqB,EAAAA,KAAC3B,iBAA4B,wBAAwBM,UAAWA,EAASsB,SAAA,CAVpEH,EAKHI,MAACC,iBAA0B,6BAA6BL,KAAMA,EAAMM,MAAM,SAASC,KAAK,YAJjF,KAWPL,EAAAA,KAACZ,EAAmB,CAAAa,SAAA,CAClBC,EAAAA,IAACZ,EAAU,CAACgB,QAAQ,oBAAYV,IAChCM,EAAAA,IAACb,GAAkBiB,QAAQ,SAAQL,SAAEJ,OA7CrB,MACpB,GAAKE,EAIL,OAAIQ,MAAMC,QAAQT,GAEdG,EAAAA,IAACO,EAAW,CAACC,MAAM,QAAQC,OAAO,aAAaC,MAAM,UAAUC,KAAK,SAAQZ,SACzEF,EAAce,KAAKC,IAClB,MAAMC,EAAM,iBAAiBD,EAAOE,MAAMC,QAAQ,OAAQ,KAAKC,gBAE/D,OACEjB,EAAAA,IAACkB,EAAAA,OAAM,CAAWC,MAAON,EAAOE,MAAOK,QAASP,EAAOO,QAAShB,QAASS,EAAOT,QAAOL,SACpFc,EAAOE,OADGD,QAUrBd,EAAAA,IAAChB,EAA8B,CAAAe,SAC7BC,EAAAA,IAACkB,SAAM,CAACC,MAAOtB,EAAckB,MAAOK,QAASvB,EAAcuB,QAAShB,QAASP,EAAcO,QAAOL,SAC/FF,EAAckB,WAuBlBM"}
@@ -0,0 +1,34 @@
1
+ import { ButtonType } from '@fd/components/atoms/Button/getButtonStyles';
2
+
3
+ /** Action button configuration */
4
+ interface ActionButton {
5
+ /** Text label for the action button */
6
+ label: string;
7
+ /** Visual variant of the action button */
8
+ variant?: ButtonType;
9
+ /** Callback function when the action button is clicked */
10
+ onClick: () => void;
11
+ }
12
+ /** EmptyState component props */
13
+ interface EmptyStateProps {
14
+ /** Main heading text of the empty state */
15
+ title: string;
16
+ /** Detailed description text of the empty state */
17
+ description: string;
18
+ /** Icon to display */
19
+ icon?: React.ReactNode;
20
+ /** Action buttons to display */
21
+ actionButtons?: ActionButton | ActionButton[];
22
+ /** Whether the empty state should be full width */
23
+ fullWidth?: boolean;
24
+ }
25
+ /**
26
+ * EmptyState component is used when a table, list, or chart has no data to display.
27
+ *
28
+ * @param props - The component props
29
+ * @returns The empty state component
30
+ */
31
+ declare const EmptyState: ({ title, description, icon, actionButtons, fullWidth, }: EmptyStateProps) => JSX.Element;
32
+
33
+ export { EmptyState, EmptyState as default };
34
+ export type { ActionButton, EmptyStateProps };
@@ -0,0 +1,2 @@
1
+ import{jsxs as t,jsx as e}from"react/jsx-runtime";import i from"@mui/material/Box";import{styled as a}from"@mui/material/styles";import l from"@mui/material/Typography";import{Button as r}from"../../atoms/Button/index.js";import n from"../../atoms/IconContainer/index.js";import o from"../ButtonGroup/index.js";const m=a(i,{shouldForwardProp:t=>"fullWidth"!==t})((({theme:t,fullWidth:e})=>({display:"flex",flexDirection:"column",alignItems:"flex-start",gap:t.spacing(3),width:e?"100%":"536px"}))),d=a(i)((()=>({display:"flex",justifyContent:"flex-end",width:"100%"}))),s=a(i)((({theme:t})=>({display:"flex",flexDirection:"column",alignItems:"flex-start",gap:t.spacing(1)}))),c=a(l)((({theme:t})=>({color:t.palette.semantic.text["text-weak"]}))),f=({title:i,description:a,icon:f,actionButtons:p,fullWidth:u=!1})=>t(m,{"data-testid":"empty-state-container",fullWidth:u,children:[f?e(n,{"data-testid":"empty-state-icon-container",icon:f,style:"filled",tone:"neutral"}):null,t(s,{children:[e(l,{variant:"h3Strong",children:i}),e(c,{variant:"b1Weak",children:a})]}),(()=>{if(p)return Array.isArray(p)?e(o,{align:"right",layout:"horizontal",order:"default",size:"medium",children:p.map((t=>{const i=`action-button-${t.label.replace(/\s+/g,"-").toLowerCase()}`;return e(r,{fdKey:t.label,onClick:t.onClick,variant:t.variant,children:t.label},i)}))}):e(d,{children:e(r,{fdKey:p.label,onClick:p.onClick,variant:p.variant,children:p.label})})})()]});export{f as EmptyState,f as default};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/molecules/EmptyState/index.tsx"],"sourcesContent":["import Box from '@mui/material/Box';\nimport { styled } from '@mui/material/styles';\nimport Typography from '@mui/material/Typography';\n\nimport Button from '@fd/components/atoms/Button';\nimport type { ButtonType } from '@fd/components/atoms/Button/getButtonStyles';\nimport IconContainer from '@fd/components/atoms/IconContainer';\n\nimport ButtonGroup from '../ButtonGroup';\n\n/** Action button configuration */\nexport interface ActionButton {\n /** Text label for the action button */\n label: string;\n /** Visual variant of the action button */\n variant?: ButtonType;\n /** Callback function when the action button is clicked */\n onClick: () => void;\n}\n\n/** EmptyState component props */\nexport interface EmptyStateProps {\n /** Main heading text of the empty state */\n title: string;\n /** Detailed description text of the empty state */\n description: string;\n /** Icon to display */\n icon?: React.ReactNode;\n /** Action buttons to display */\n actionButtons?: ActionButton | ActionButton[];\n /** Whether the empty state should be full width */\n fullWidth?: boolean;\n}\n\nconst StyledContainer = styled(Box, { shouldForwardProp: (prop) => prop !== 'fullWidth' })<{\n fullWidth?: boolean;\n}>(({ theme, fullWidth }) => ({\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n gap: theme.spacing(3),\n width: fullWidth ? '100%' : '536px',\n}));\n\nconst StyledOneActionButtonContainer = styled(Box)(() => ({\n display: 'flex',\n justifyContent: 'flex-end',\n width: '100%',\n}));\n\nconst StyledTextContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n gap: theme.spacing(1),\n}));\n\nconst StyledDescription = styled(Typography)(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n}));\n\n/**\n * EmptyState component is used when a table, list, or chart has no data to display.\n *\n * @param props - The component props\n * @returns The empty state component\n */\n\nexport const EmptyState = ({\n title,\n description,\n icon,\n actionButtons,\n fullWidth = false,\n}: EmptyStateProps): JSX.Element => {\n const renderActions = (): React.ReactNode => {\n if (!actionButtons) {\n return undefined;\n }\n\n if (Array.isArray(actionButtons)) {\n return (\n <ButtonGroup align=\"right\" layout=\"horizontal\" order=\"default\" size=\"medium\">\n {actionButtons.map((button) => {\n const key = `action-button-${button.label.replace(/\\s+/g, '-').toLowerCase()}`;\n\n return (\n <Button key={key} fdKey={button.label} onClick={button.onClick} variant={button.variant}>\n {button.label}\n </Button>\n );\n })}\n </ButtonGroup>\n );\n }\n\n return (\n <StyledOneActionButtonContainer>\n <Button fdKey={actionButtons.label} onClick={actionButtons.onClick} variant={actionButtons.variant}>\n {actionButtons.label}\n </Button>\n </StyledOneActionButtonContainer>\n );\n };\n\n const renderIcon = (): React.ReactNode => {\n if (!icon) {\n return null;\n }\n\n return (\n <IconContainer data-testid=\"empty-state-icon-container\" icon={icon} style=\"filled\" tone=\"neutral\" />\n );\n };\n\n return (\n <StyledContainer data-testid=\"empty-state-container\" fullWidth={fullWidth}>\n {renderIcon()}\n <StyledTextContainer>\n <Typography variant=\"h3Strong\">{title}</Typography>\n <StyledDescription variant=\"b1Weak\">{description}</StyledDescription>\n </StyledTextContainer>\n {renderActions()}\n </StyledContainer>\n );\n};\n\nexport default EmptyState;\n"],"names":["StyledContainer","styled","Box","shouldForwardProp","prop","theme","fullWidth","display","flexDirection","alignItems","gap","spacing","width","StyledOneActionButtonContainer","justifyContent","StyledTextContainer","StyledDescription","Typography","color","palette","semantic","text","EmptyState","title","description","icon","actionButtons","_jsxs","children","_jsx","IconContainer","style","tone","variant","Array","isArray","ButtonGroup","align","layout","order","size","map","button","key","label","replace","toLowerCase","Button","fdKey","onClick","renderActions"],"mappings":"uTAkCA,MAAMA,EAAkBC,EAAOC,EAAK,CAAEC,kBAAoBC,GAAkB,cAATA,GAA3CH,EAErB,EAAGI,QAAOC,gBAAW,CACtBC,QAAS,OACTC,cAAe,SACfC,WAAY,aACZC,IAAKL,EAAMM,QAAQ,GACnBC,MAAON,EAAY,OAAS,YAGxBO,EAAiCZ,EAAOC,EAAPD,EAAY,KAAA,CACjDM,QAAS,OACTO,eAAgB,WAChBF,MAAO,WAGHG,EAAsBd,EAAOC,EAAPD,EAAY,EAAGI,YAAO,CAChDE,QAAS,OACTC,cAAe,SACfC,WAAY,aACZC,IAAKL,EAAMM,QAAQ,OAGfK,EAAoBf,EAAOgB,EAAPhB,EAAmB,EAAGI,YAAO,CACrDa,MAAOb,EAAMc,QAAQC,SAASC,KAAK,iBAUxBC,EAAa,EACxBC,QACAC,cACAC,OACAC,gBACApB,aAAY,KA2CVqB,EAAC3B,iBAA4B,wBAAwBM,UAAWA,EAASsB,SAAA,CAVpEH,EAKHI,EAACC,iBAA0B,6BAA6BL,KAAMA,EAAMM,MAAM,SAASC,KAAK,YAJjF,KAWPL,EAACZ,EAAmB,CAAAa,SAAA,CAClBC,EAACZ,EAAU,CAACgB,QAAQ,oBAAYV,IAChCM,EAACb,GAAkBiB,QAAQ,SAAQL,SAAEJ,OA7CrB,MACpB,GAAKE,EAIL,OAAIQ,MAAMC,QAAQT,GAEdG,EAACO,EAAW,CAACC,MAAM,QAAQC,OAAO,aAAaC,MAAM,UAAUC,KAAK,SAAQZ,SACzEF,EAAce,KAAKC,IAClB,MAAMC,EAAM,iBAAiBD,EAAOE,MAAMC,QAAQ,OAAQ,KAAKC,gBAE/D,OACEjB,EAACkB,EAAM,CAAWC,MAAON,EAAOE,MAAOK,QAASP,EAAOO,QAAShB,QAASS,EAAOT,QAAOL,SACpFc,EAAOE,OADGD,QAUrBd,EAAChB,EAA8B,CAAAe,SAC7BC,EAACkB,EAAM,CAACC,MAAOtB,EAAckB,MAAOK,QAASvB,EAAcuB,QAAShB,QAASP,EAAcO,QAAOL,SAC/FF,EAAckB,WAuBlBM"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../../../src/components/molecules/RadioGroup/index.tsx"],"sourcesContent":["import React, { isValidElement, type ReactElement } from 'react';\n\nimport MuiFormHelperText from '@mui/material/FormHelperText';\nimport FormLabel from '@mui/material/FormLabel';\nimport MuiRadioGroup, { type RadioGroupProps as MuiRadioGroupProps } from '@mui/material/RadioGroup';\nimport { styled } from '@mui/material/styles';\n\nimport Box from '@fd/components/atoms/Box';\nimport { type FormControlLabelProps } from '@fd/components/molecules/FormControlLabel';\nimport CancelCircleIcon from '@fd/icons/CancelCircle';\n\nconst StyledBox = styled(Box)(({ theme }) => ({\n ...({ marginTop: theme.spacing(2), marginBottom: 0 }),\n}));\n\nconst StyledLabelContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n marginBottom: theme.spacing(0.5),\n}));\n\nconst ConditionalContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(0.5),\n borderLeft: `4px solid ${theme.palette.semantic.stroke['stroke-weak']}`,\n marginTop: theme.spacing(2),\n marginLeft: theme.spacing(1.75),\n paddingLeft: theme.spacing(3.25),\n}));\n\nconst WeakText = styled('span')(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n marginLeft: theme.spacing(0.5),\n}));\n\n/** Props for a radio group */\nexport interface RadioGroupProps\n extends Omit<MuiRadioGroupProps, 'name' | 'onChange' | 'value'> {\n /** Identifier applied to the radio group; also used for the radio name attribute. */\n fdKey?: string;\n /** Currently selected option value; use null or undefined when no option should be selected. */\n value?: string | null;\n /** Callback fired whenever the selection changes with the event and newly selected value. */\n onChange: (event: React.ChangeEvent<HTMLInputElement>, value: string) => void;\n /** Label rendered above the group; accepts plain text or a custom node. */\n label: React.ReactNode;\n /** Marks the radio group as required and surfaces the required indicator. */\n required?: boolean;\n /** Helper text displayed below the label to provide additional guidance. */\n helperText?: string;\n /** Error message shown below the helper text and marks the inputs as invalid. */\n errorText?: string;\n /** The children of the radio group. */\n children?: React.ReactNode;\n}\n\n/**\n * RadioGroup component is a wrapper component for a radio group.\n *\n * @param fdKey - Identifier applied to the radio group; also used for the radio name attribute.\n * @param value - Currently selected option value; use null or undefined when no option should be selected.\n * @param onChange - Callback fired whenever the selection changes with the event and newly selected value.\n * @param label - Label rendered above the group; accepts plain text or a custom node.\n * @param required - Marks the radio group as required and surfaces the required indicator.\n * @param helperText - Helper text displayed below the label to provide additional guidance.\n * @param errorText - Error message shown below the helper text and marks the inputs as invalid.\n * @param children - The children of the radio group.\n * @param groupProps - Additional props to pass to the underlying MUI RadioGroup component.\n * @returns The RadioGroup component.\n */\n\nexport const RadioGroup = ({\n fdKey = 'radio-default',\n value = '',\n onChange,\n label,\n required = false,\n helperText,\n errorText,\n children,\n ...groupProps\n}: RadioGroupProps): React.ReactElement => {\n\n const labelId = `${fdKey}-label`;\n const helperId = helperText ? `${fdKey}-helper` : undefined;\n const errorId = errorText ? `${fdKey}-error` : undefined;\n const describedBy = [helperId, errorId].filter(Boolean).join(' ') || undefined;\n\n const handleChange: MuiRadioGroupProps['onChange'] = (e, newVal) => onChange(e, newVal);\n\n return (\n <Box>\n <StyledLabelContainer>\n <FormLabel id={labelId}>\n {label} {required && <WeakText>{' *'}</WeakText>}\n </FormLabel>\n {helperText ? (\n <MuiFormHelperText id={helperId}>\n {helperText}\n </MuiFormHelperText>\n ) : null}\n\n {!!errorText && (\n <MuiFormHelperText error id={errorId}>\n <CancelCircleIcon size={\"md\"} />\n {errorText}\n </MuiFormHelperText>\n )}\n\n </StyledLabelContainer>\n\n <MuiRadioGroup\n aria-describedby={describedBy}\n aria-invalid={errorText ? true : undefined}\n aria-labelledby={labelId}\n name={fdKey}\n onChange={handleChange}\n value={value}\n {...groupProps}\n >\n {React.Children.map(children, (child) => {\n if (!isValidElement(child)) {\n return child;\n }\n\n // Check if child has FormControlLabel-specific props (value and control)\n // This ensures we only process FormControlLabel components, not arbitrary elements like <Box>\n const childProps = child.props as Partial<FormControlLabelProps>;\n if (!('value' in childProps) || !('control' in childProps)) {\n return child;\n }\n\n const formControlLabelProps = childProps as FormControlLabelProps;\n const radioValue = typeof formControlLabelProps.value === 'string' ? formControlLabelProps.value : String(formControlLabelProps.value ?? '');\n const isSelected = value === radioValue;\n\n // Extract Radio component from FormControlLabel's control prop\n const radioControl = formControlLabelProps.control;\n\n const optionConditionalChildren = formControlLabelProps.children;\n\n // Clone the Radio component with updated children and checked state\n const updatedRadio = isValidElement(radioControl)\n ? React.cloneElement(radioControl, {\n ...(radioControl.props as Record<string, unknown>),\n checked: isSelected,\n error: !!errorText,\n name: fdKey,\n } as Partial<unknown>)\n : radioControl;\n\n return (\n <StyledBox key={radioValue}>\n {React.cloneElement(child as ReactElement<FormControlLabelProps>, {\n ...formControlLabelProps,\n children: undefined,\n control: updatedRadio,\n })}\n\n {/* conditional block under the selected option */}\n {isSelected && optionConditionalChildren && (\n <ConditionalContainer>\n {optionConditionalChildren}\n </ConditionalContainer>\n )}\n </StyledBox>\n );\n })}\n </MuiRadioGroup>\n </Box>\n );\n}\n\nexport default RadioGroup;"],"names":["StyledBox","styled","Box","theme","marginTop","spacing","marginBottom","StyledLabelContainer","display","flexDirection","ConditionalContainer","gap","borderLeft","palette","semantic","stroke","marginLeft","paddingLeft","WeakText","color","text","RadioGroup","fdKey","value","onChange","label","required","helperText","errorText","children","groupProps","labelId","helperId","undefined","errorId","describedBy","filter","Boolean","join","_jsxs","FormLabel","id","_jsx","MuiFormHelperText","error","CancelCircleIcon","size","MuiRadioGroup","name","e","newVal","React","Children","map","child","isValidElement","childProps","props","formControlLabelProps","radioValue","String","isSelected","radioControl","control","optionConditionalChildren","updatedRadio","cloneElement","checked"],"mappings":"oZAWA,MAAMA,EAAYC,EAAAA,OAAOC,EAAPD,EAAY,EAAGE,YAAO,CAChCC,UAAWD,EAAME,QAAQ,GAAIC,aAAc,MAG7CC,EAAuBN,EAAAA,OAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfH,aAAcH,EAAME,QAAQ,QAGxBK,EAAuBT,EAAAA,OAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfE,IAAKR,EAAME,QAAQ,IACnBO,WAAY,aAAaT,EAAMU,QAAQC,SAASC,OAAO,iBACvDX,UAAWD,EAAME,QAAQ,GACzBW,WAAYb,EAAME,QAAQ,MAC1BY,YAAad,EAAME,QAAQ,UAGvBa,EAAWjB,EAAAA,OAAO,OAAPA,EAAe,EAAGE,YAAO,CACxCgB,MAAOhB,EAAMU,QAAQC,SAASM,KAAK,aACnCJ,WAAYb,EAAME,QAAQ,QAuCfgB,EAAa,EACxBC,QAAQ,gBACRC,QAAQ,GACRC,WACAC,QACAC,YAAW,EACXC,aACAC,YACAC,cACGC,MAGH,MAAMC,EAAU,GAAGT,UACbU,EAAWL,EAAa,GAAGL,gBAAiBW,EAC5CC,EAAUN,EAAY,GAAGN,eAAgBW,EACzCE,EAAc,CAACH,EAAUE,GAASE,OAAOC,SAASC,KAAK,WAAQL,EAIrE,OACEM,EAAAA,KAACrC,EAAG,CAAA2B,SAAA,CACFU,EAAAA,KAAChC,EAAoB,CAAAsB,SAAA,CACnBU,EAAAA,KAACC,EAAS,CAACC,GAAIV,EAAOF,SAAA,CACnBJ,EAAK,IAAGC,GAAYgB,MAACxB,EAAQ,CAAAW,SAAE,UAEjCF,EACCe,EAAAA,IAACC,EAAiB,CAACF,GAAIT,EAAQH,SAC5BF,IAED,OAEDC,GACDW,EAAAA,KAACI,EAAiB,CAACC,OAAK,EAACH,GAAIP,EAAOL,SAAA,CAClCa,EAAAA,IAACG,EAAgB,CAACC,KAAM,OACvBlB,QAMPc,EAAAA,IAACK,EAAa,CAAA,mBACMZ,EAAW,iBACfP,QAAmBK,oBAChBF,EACjBiB,KAAM1B,EACNE,SA5B+C,CAACyB,EAAGC,IAAW1B,EAASyB,EAAGC,GA6B1E3B,MAAOA,KACHO,EAAUD,SAEbsB,EAAMC,SAASC,IAAIxB,GAAWyB,IAC7B,IAAKC,EAAAA,eAAeD,GAClB,OAAOA,EAKT,MAAME,EAAaF,EAAMG,MACzB,KAAM,UAAWD,MAAiB,YAAaA,GAC7C,OAAOF,EAGT,MAAMI,EAAwBF,EACxBG,EAAoD,iBAAhCD,EAAsBnC,MAAqBmC,EAAsBnC,MAAQqC,OAAOF,EAAsBnC,OAAS,IACnIsC,EAAatC,IAAUoC,EAGvBG,EAAeJ,EAAsBK,QAErCC,EAA4BN,EAAsB7B,SAGlDoC,EAAeV,EAAAA,eAAeO,GAChCX,EAAMe,aAAaJ,EAAc,IAC7BA,EAAaL,MACjBU,QAASN,EACTjB,QAAShB,EACToB,KAAM1B,IAENwC,EAEJ,OACEvB,EAAAA,KAACvC,EAAS,CAAA6B,SAAA,CACPsB,EAAMe,aAAaZ,EAA8C,IAC7DI,EACH7B,cAAUI,EACV8B,QAASE,IAIVJ,GAAcG,GACbtB,EAAAA,IAAChC,EAAoB,CAAAmB,SAClBmC,MAVSL"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../../src/components/molecules/RadioGroup/index.tsx"],"sourcesContent":["import React, { isValidElement, type ReactElement } from 'react';\n\nimport MuiFormHelperText from '@mui/material/FormHelperText';\nimport FormLabel from '@mui/material/FormLabel';\nimport MuiRadioGroup, { type RadioGroupProps as MuiRadioGroupProps } from '@mui/material/RadioGroup';\nimport { styled } from '@mui/material/styles';\n\nimport Box from '@fd/components/atoms/Box';\nimport { type FormControlLabelProps } from '@fd/components/molecules/FormControlLabel';\nimport CancelCircleIcon from '@fd/icons/CancelCircle';\n\nconst StyledBox = styled(Box)(({ theme }) => ({\n ...{ marginTop: theme.spacing(2), marginBottom: 0 },\n}));\n\nconst StyledLabelContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n marginBottom: theme.spacing(0.5),\n}));\n\nconst ConditionalContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(0.5),\n borderLeft: `4px solid ${theme.palette.semantic.stroke['stroke-weak']}`,\n marginTop: theme.spacing(2),\n marginLeft: theme.spacing(1.75),\n paddingLeft: theme.spacing(3.25),\n}));\n\nconst WeakText = styled('span')(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n marginLeft: theme.spacing(0.5),\n}));\n\n/** Props for a radio group */\nexport interface RadioGroupProps extends Omit<MuiRadioGroupProps, 'name' | 'onChange' | 'value'> {\n /** Identifier applied to the radio group; also used for the radio name attribute. */\n fdKey?: string;\n /** Currently selected option value; use null or undefined when no option should be selected. */\n value?: string | null;\n /** Callback fired whenever the selection changes with the event and newly selected value. */\n onChange: (event: React.ChangeEvent<HTMLInputElement>, value: string) => void;\n /** Label rendered above the group; accepts plain text or a custom node. */\n label: React.ReactNode;\n /** Marks the radio group as required and surfaces the required indicator. */\n required?: boolean;\n /** Helper text displayed below the label to provide additional guidance. */\n helperText?: string;\n /** Error message shown below the helper text and marks the inputs as invalid. */\n errorText?: string;\n /** The children of the radio group. */\n children?: React.ReactNode;\n}\n\n/**\n * RadioGroup component is a wrapper component for a radio group.\n *\n * @param fdKey - Identifier applied to the radio group; also used for the radio name attribute.\n * @param value - Currently selected option value; use null or undefined when no option should be selected.\n * @param onChange - Callback fired whenever the selection changes with the event and newly selected value.\n * @param label - Label rendered above the group; accepts plain text or a custom node.\n * @param required - Marks the radio group as required and surfaces the required indicator.\n * @param helperText - Helper text displayed below the label to provide additional guidance.\n * @param errorText - Error message shown below the helper text and marks the inputs as invalid.\n * @param children - The children of the radio group.\n * @param groupProps - Additional props to pass to the underlying MUI RadioGroup component.\n * @returns The RadioGroup component.\n */\n\nexport const RadioGroup = ({\n fdKey = 'radio-default',\n value = '',\n onChange,\n label,\n required = false,\n helperText,\n errorText,\n children,\n ...groupProps\n}: RadioGroupProps): React.ReactElement => {\n const labelId = `${fdKey}-label`;\n const helperId = helperText ? `${fdKey}-helper` : undefined;\n const errorId = errorText ? `${fdKey}-error` : undefined;\n const describedBy = [helperId, errorId].filter(Boolean).join(' ') || undefined;\n\n const handleChange: MuiRadioGroupProps['onChange'] = (e, newVal) => onChange(e, newVal);\n\n return (\n <Box>\n <StyledLabelContainer>\n <FormLabel id={labelId}>\n {label} {required && <WeakText>{' *'}</WeakText>}\n </FormLabel>\n {helperText ? <MuiFormHelperText id={helperId}>{helperText}</MuiFormHelperText> : null}\n\n {!!errorText && (\n <MuiFormHelperText error id={errorId}>\n <CancelCircleIcon size={'md'} />\n {errorText}\n </MuiFormHelperText>\n )}\n </StyledLabelContainer>\n\n <MuiRadioGroup\n aria-describedby={describedBy}\n aria-invalid={errorText ? true : undefined}\n aria-labelledby={labelId}\n name={fdKey}\n onChange={handleChange}\n value={value}\n {...groupProps}\n >\n {React.Children.map(children, (child) => {\n if (!isValidElement(child)) {\n return child;\n }\n\n // Check if child has FormControlLabel-specific props (value and control)\n // This ensures we only process FormControlLabel components, not arbitrary elements like <Box>\n const childProps = child.props as Partial<FormControlLabelProps>;\n if (!('value' in childProps) || !('control' in childProps)) {\n return child;\n }\n\n const formControlLabelProps = childProps as FormControlLabelProps;\n const radioValue =\n typeof formControlLabelProps.value === 'string'\n ? formControlLabelProps.value\n : String(formControlLabelProps.value ?? '');\n const isSelected = value === radioValue;\n\n // Extract Radio component from FormControlLabel's control prop\n const radioControl = formControlLabelProps.control;\n\n const optionConditionalChildren = formControlLabelProps.children;\n\n // Clone the Radio component with updated children and checked state\n const updatedRadio = isValidElement(radioControl)\n ? React.cloneElement(radioControl, {\n ...(radioControl.props as Record<string, unknown>),\n checked: isSelected,\n error: !!errorText,\n name: fdKey,\n } as Partial<unknown>)\n : radioControl;\n\n return (\n <StyledBox key={radioValue}>\n {React.cloneElement(child as ReactElement<FormControlLabelProps>, {\n ...formControlLabelProps,\n children: undefined,\n control: updatedRadio,\n })}\n\n {/* conditional block under the selected option */}\n {isSelected && optionConditionalChildren && (\n <ConditionalContainer>{optionConditionalChildren}</ConditionalContainer>\n )}\n </StyledBox>\n );\n })}\n </MuiRadioGroup>\n </Box>\n );\n};\n\nexport default RadioGroup;\n"],"names":["StyledBox","styled","Box","theme","marginTop","spacing","marginBottom","StyledLabelContainer","display","flexDirection","ConditionalContainer","gap","borderLeft","palette","semantic","stroke","marginLeft","paddingLeft","WeakText","color","text","RadioGroup","fdKey","value","onChange","label","required","helperText","errorText","children","groupProps","labelId","helperId","undefined","errorId","describedBy","filter","Boolean","join","_jsxs","FormLabel","id","_jsx","MuiFormHelperText","error","CancelCircleIcon","size","MuiRadioGroup","name","e","newVal","React","Children","map","child","isValidElement","childProps","props","formControlLabelProps","radioValue","String","isSelected","radioControl","control","optionConditionalChildren","updatedRadio","cloneElement","checked"],"mappings":"oZAWA,MAAMA,EAAYC,EAAAA,OAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjCC,UAAWD,EAAME,QAAQ,GAAIC,aAAc,MAG5CC,EAAuBN,EAAAA,OAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfH,aAAcH,EAAME,QAAQ,QAGxBK,EAAuBT,EAAAA,OAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfE,IAAKR,EAAME,QAAQ,IACnBO,WAAY,aAAaT,EAAMU,QAAQC,SAASC,OAAO,iBACvDX,UAAWD,EAAME,QAAQ,GACzBW,WAAYb,EAAME,QAAQ,MAC1BY,YAAad,EAAME,QAAQ,UAGvBa,EAAWjB,EAAAA,OAAO,OAAPA,EAAe,EAAGE,YAAO,CACxCgB,MAAOhB,EAAMU,QAAQC,SAASM,KAAK,aACnCJ,WAAYb,EAAME,QAAQ,QAsCfgB,EAAa,EACxBC,QAAQ,gBACRC,QAAQ,GACRC,WACAC,QACAC,YAAW,EACXC,aACAC,YACAC,cACGC,MAEH,MAAMC,EAAU,GAAGT,UACbU,EAAWL,EAAa,GAAGL,gBAAiBW,EAC5CC,EAAUN,EAAY,GAAGN,eAAgBW,EACzCE,EAAc,CAACH,EAAUE,GAASE,OAAOC,SAASC,KAAK,WAAQL,EAIrE,OACEM,EAAAA,KAACrC,EAAG,CAAA2B,SAAA,CACFU,EAAAA,KAAChC,EAAoB,CAAAsB,SAAA,CACnBU,EAAAA,KAACC,EAAS,CAACC,GAAIV,EAAOF,SAAA,CACnBJ,EAAK,IAAGC,GAAYgB,MAACxB,EAAQ,CAAAW,SAAE,UAEjCF,EAAae,EAAAA,IAACC,EAAiB,CAACF,GAAIT,EAAQH,SAAGF,IAAkC,OAE/EC,GACDW,EAAAA,KAACI,EAAiB,CAACC,OAAK,EAACH,GAAIP,YAC3BQ,EAAAA,IAACG,EAAgB,CAACC,KAAM,OACvBlB,QAKPc,EAAAA,IAACK,EAAa,CAAA,mBACMZ,EAAW,iBACfP,QAAmBK,EAAS,kBACzBF,EACjBiB,KAAM1B,EACNE,SAvB+C,CAACyB,EAAGC,IAAW1B,EAASyB,EAAGC,GAwB1E3B,MAAOA,KACHO,EAAUD,SAEbsB,EAAMC,SAASC,IAAIxB,GAAWyB,IAC7B,IAAKC,EAAAA,eAAeD,GAClB,OAAOA,EAKT,MAAME,EAAaF,EAAMG,MACzB,KAAM,UAAWD,MAAiB,YAAaA,GAC7C,OAAOF,EAGT,MAAMI,EAAwBF,EACxBG,EACmC,iBAAhCD,EAAsBnC,MACzBmC,EAAsBnC,MACtBqC,OAAOF,EAAsBnC,OAAS,IACtCsC,EAAatC,IAAUoC,EAGvBG,EAAeJ,EAAsBK,QAErCC,EAA4BN,EAAsB7B,SAGlDoC,EAAeV,EAAAA,eAAeO,GAChCX,EAAMe,aAAaJ,EAAc,IAC3BA,EAAaL,MACjBU,QAASN,EACTjB,QAAShB,EACToB,KAAM1B,IAERwC,EAEJ,OACEvB,EAAAA,KAACvC,EAAS,CAAA6B,SAAA,CACPsB,EAAMe,aAAaZ,EAA8C,IAC7DI,EACH7B,cAAUI,EACV8B,QAASE,IAIVJ,GAAcG,GACbtB,EAAAA,IAAChC,EAAoB,CAAAmB,SAAEmC,MATXL"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/molecules/RadioGroup/index.tsx"],"sourcesContent":["import React, { isValidElement, type ReactElement } from 'react';\n\nimport MuiFormHelperText from '@mui/material/FormHelperText';\nimport FormLabel from '@mui/material/FormLabel';\nimport MuiRadioGroup, { type RadioGroupProps as MuiRadioGroupProps } from '@mui/material/RadioGroup';\nimport { styled } from '@mui/material/styles';\n\nimport Box from '@fd/components/atoms/Box';\nimport { type FormControlLabelProps } from '@fd/components/molecules/FormControlLabel';\nimport CancelCircleIcon from '@fd/icons/CancelCircle';\n\nconst StyledBox = styled(Box)(({ theme }) => ({\n ...({ marginTop: theme.spacing(2), marginBottom: 0 }),\n}));\n\nconst StyledLabelContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n marginBottom: theme.spacing(0.5),\n}));\n\nconst ConditionalContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(0.5),\n borderLeft: `4px solid ${theme.palette.semantic.stroke['stroke-weak']}`,\n marginTop: theme.spacing(2),\n marginLeft: theme.spacing(1.75),\n paddingLeft: theme.spacing(3.25),\n}));\n\nconst WeakText = styled('span')(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n marginLeft: theme.spacing(0.5),\n}));\n\n/** Props for a radio group */\nexport interface RadioGroupProps\n extends Omit<MuiRadioGroupProps, 'name' | 'onChange' | 'value'> {\n /** Identifier applied to the radio group; also used for the radio name attribute. */\n fdKey?: string;\n /** Currently selected option value; use null or undefined when no option should be selected. */\n value?: string | null;\n /** Callback fired whenever the selection changes with the event and newly selected value. */\n onChange: (event: React.ChangeEvent<HTMLInputElement>, value: string) => void;\n /** Label rendered above the group; accepts plain text or a custom node. */\n label: React.ReactNode;\n /** Marks the radio group as required and surfaces the required indicator. */\n required?: boolean;\n /** Helper text displayed below the label to provide additional guidance. */\n helperText?: string;\n /** Error message shown below the helper text and marks the inputs as invalid. */\n errorText?: string;\n /** The children of the radio group. */\n children?: React.ReactNode;\n}\n\n/**\n * RadioGroup component is a wrapper component for a radio group.\n *\n * @param fdKey - Identifier applied to the radio group; also used for the radio name attribute.\n * @param value - Currently selected option value; use null or undefined when no option should be selected.\n * @param onChange - Callback fired whenever the selection changes with the event and newly selected value.\n * @param label - Label rendered above the group; accepts plain text or a custom node.\n * @param required - Marks the radio group as required and surfaces the required indicator.\n * @param helperText - Helper text displayed below the label to provide additional guidance.\n * @param errorText - Error message shown below the helper text and marks the inputs as invalid.\n * @param children - The children of the radio group.\n * @param groupProps - Additional props to pass to the underlying MUI RadioGroup component.\n * @returns The RadioGroup component.\n */\n\nexport const RadioGroup = ({\n fdKey = 'radio-default',\n value = '',\n onChange,\n label,\n required = false,\n helperText,\n errorText,\n children,\n ...groupProps\n}: RadioGroupProps): React.ReactElement => {\n\n const labelId = `${fdKey}-label`;\n const helperId = helperText ? `${fdKey}-helper` : undefined;\n const errorId = errorText ? `${fdKey}-error` : undefined;\n const describedBy = [helperId, errorId].filter(Boolean).join(' ') || undefined;\n\n const handleChange: MuiRadioGroupProps['onChange'] = (e, newVal) => onChange(e, newVal);\n\n return (\n <Box>\n <StyledLabelContainer>\n <FormLabel id={labelId}>\n {label} {required && <WeakText>{' *'}</WeakText>}\n </FormLabel>\n {helperText ? (\n <MuiFormHelperText id={helperId}>\n {helperText}\n </MuiFormHelperText>\n ) : null}\n\n {!!errorText && (\n <MuiFormHelperText error id={errorId}>\n <CancelCircleIcon size={\"md\"} />\n {errorText}\n </MuiFormHelperText>\n )}\n\n </StyledLabelContainer>\n\n <MuiRadioGroup\n aria-describedby={describedBy}\n aria-invalid={errorText ? true : undefined}\n aria-labelledby={labelId}\n name={fdKey}\n onChange={handleChange}\n value={value}\n {...groupProps}\n >\n {React.Children.map(children, (child) => {\n if (!isValidElement(child)) {\n return child;\n }\n\n // Check if child has FormControlLabel-specific props (value and control)\n // This ensures we only process FormControlLabel components, not arbitrary elements like <Box>\n const childProps = child.props as Partial<FormControlLabelProps>;\n if (!('value' in childProps) || !('control' in childProps)) {\n return child;\n }\n\n const formControlLabelProps = childProps as FormControlLabelProps;\n const radioValue = typeof formControlLabelProps.value === 'string' ? formControlLabelProps.value : String(formControlLabelProps.value ?? '');\n const isSelected = value === radioValue;\n\n // Extract Radio component from FormControlLabel's control prop\n const radioControl = formControlLabelProps.control;\n\n const optionConditionalChildren = formControlLabelProps.children;\n\n // Clone the Radio component with updated children and checked state\n const updatedRadio = isValidElement(radioControl)\n ? React.cloneElement(radioControl, {\n ...(radioControl.props as Record<string, unknown>),\n checked: isSelected,\n error: !!errorText,\n name: fdKey,\n } as Partial<unknown>)\n : radioControl;\n\n return (\n <StyledBox key={radioValue}>\n {React.cloneElement(child as ReactElement<FormControlLabelProps>, {\n ...formControlLabelProps,\n children: undefined,\n control: updatedRadio,\n })}\n\n {/* conditional block under the selected option */}\n {isSelected && optionConditionalChildren && (\n <ConditionalContainer>\n {optionConditionalChildren}\n </ConditionalContainer>\n )}\n </StyledBox>\n );\n })}\n </MuiRadioGroup>\n </Box>\n );\n}\n\nexport default RadioGroup;"],"names":["StyledBox","styled","Box","theme","marginTop","spacing","marginBottom","StyledLabelContainer","display","flexDirection","ConditionalContainer","gap","borderLeft","palette","semantic","stroke","marginLeft","paddingLeft","WeakText","color","text","RadioGroup","fdKey","value","onChange","label","required","helperText","errorText","children","groupProps","labelId","helperId","undefined","errorId","describedBy","filter","Boolean","join","_jsxs","FormLabel","id","_jsx","MuiFormHelperText","error","CancelCircleIcon","size","MuiRadioGroup","name","e","newVal","React","Children","map","child","isValidElement","childProps","props","formControlLabelProps","radioValue","String","isSelected","radioControl","control","optionConditionalChildren","updatedRadio","cloneElement","checked"],"mappings":"iYAWA,MAAMA,EAAYC,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CAChCC,UAAWD,EAAME,QAAQ,GAAIC,aAAc,MAG7CC,EAAuBN,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfH,aAAcH,EAAME,QAAQ,QAGxBK,EAAuBT,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfE,IAAKR,EAAME,QAAQ,IACnBO,WAAY,aAAaT,EAAMU,QAAQC,SAASC,OAAO,iBACvDX,UAAWD,EAAME,QAAQ,GACzBW,WAAYb,EAAME,QAAQ,MAC1BY,YAAad,EAAME,QAAQ,UAGvBa,EAAWjB,EAAO,OAAPA,EAAe,EAAGE,YAAO,CACxCgB,MAAOhB,EAAMU,QAAQC,SAASM,KAAK,aACnCJ,WAAYb,EAAME,QAAQ,QAuCfgB,EAAa,EACxBC,QAAQ,gBACRC,QAAQ,GACRC,WACAC,QACAC,YAAW,EACXC,aACAC,YACAC,cACGC,MAGH,MAAMC,EAAU,GAAGT,UACbU,EAAWL,EAAa,GAAGL,gBAAiBW,EAC5CC,EAAUN,EAAY,GAAGN,eAAgBW,EACzCE,EAAc,CAACH,EAAUE,GAASE,OAAOC,SAASC,KAAK,WAAQL,EAIrE,OACEM,EAACrC,EAAG,CAAA2B,SAAA,CACFU,EAAChC,EAAoB,CAAAsB,SAAA,CACnBU,EAACC,EAAS,CAACC,GAAIV,EAAOF,SAAA,CACnBJ,EAAK,IAAGC,GAAYgB,EAACxB,EAAQ,CAAAW,SAAE,UAEjCF,EACCe,EAACC,EAAiB,CAACF,GAAIT,EAAQH,SAC5BF,IAED,OAEDC,GACDW,EAACI,EAAiB,CAACC,OAAK,EAACH,GAAIP,EAAOL,SAAA,CAClCa,EAACG,EAAgB,CAACC,KAAM,OACvBlB,QAMPc,EAACK,EAAa,CAAA,mBACMZ,EAAW,iBACfP,QAAmBK,oBAChBF,EACjBiB,KAAM1B,EACNE,SA5B+C,CAACyB,EAAGC,IAAW1B,EAASyB,EAAGC,GA6B1E3B,MAAOA,KACHO,EAAUD,SAEbsB,EAAMC,SAASC,IAAIxB,GAAWyB,IAC7B,IAAKC,EAAeD,GAClB,OAAOA,EAKT,MAAME,EAAaF,EAAMG,MACzB,KAAM,UAAWD,MAAiB,YAAaA,GAC7C,OAAOF,EAGT,MAAMI,EAAwBF,EACxBG,EAAoD,iBAAhCD,EAAsBnC,MAAqBmC,EAAsBnC,MAAQqC,OAAOF,EAAsBnC,OAAS,IACnIsC,EAAatC,IAAUoC,EAGvBG,EAAeJ,EAAsBK,QAErCC,EAA4BN,EAAsB7B,SAGlDoC,EAAeV,EAAeO,GAChCX,EAAMe,aAAaJ,EAAc,IAC7BA,EAAaL,MACjBU,QAASN,EACTjB,QAAShB,EACToB,KAAM1B,IAENwC,EAEJ,OACEvB,EAACvC,EAAS,CAAA6B,SAAA,CACPsB,EAAMe,aAAaZ,EAA8C,IAC7DI,EACH7B,cAAUI,EACV8B,QAASE,IAIVJ,GAAcG,GACbtB,EAAChC,EAAoB,CAAAmB,SAClBmC,MAVSL"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/molecules/RadioGroup/index.tsx"],"sourcesContent":["import React, { isValidElement, type ReactElement } from 'react';\n\nimport MuiFormHelperText from '@mui/material/FormHelperText';\nimport FormLabel from '@mui/material/FormLabel';\nimport MuiRadioGroup, { type RadioGroupProps as MuiRadioGroupProps } from '@mui/material/RadioGroup';\nimport { styled } from '@mui/material/styles';\n\nimport Box from '@fd/components/atoms/Box';\nimport { type FormControlLabelProps } from '@fd/components/molecules/FormControlLabel';\nimport CancelCircleIcon from '@fd/icons/CancelCircle';\n\nconst StyledBox = styled(Box)(({ theme }) => ({\n ...{ marginTop: theme.spacing(2), marginBottom: 0 },\n}));\n\nconst StyledLabelContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n marginBottom: theme.spacing(0.5),\n}));\n\nconst ConditionalContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(0.5),\n borderLeft: `4px solid ${theme.palette.semantic.stroke['stroke-weak']}`,\n marginTop: theme.spacing(2),\n marginLeft: theme.spacing(1.75),\n paddingLeft: theme.spacing(3.25),\n}));\n\nconst WeakText = styled('span')(({ theme }) => ({\n color: theme.palette.semantic.text['text-weak'],\n marginLeft: theme.spacing(0.5),\n}));\n\n/** Props for a radio group */\nexport interface RadioGroupProps extends Omit<MuiRadioGroupProps, 'name' | 'onChange' | 'value'> {\n /** Identifier applied to the radio group; also used for the radio name attribute. */\n fdKey?: string;\n /** Currently selected option value; use null or undefined when no option should be selected. */\n value?: string | null;\n /** Callback fired whenever the selection changes with the event and newly selected value. */\n onChange: (event: React.ChangeEvent<HTMLInputElement>, value: string) => void;\n /** Label rendered above the group; accepts plain text or a custom node. */\n label: React.ReactNode;\n /** Marks the radio group as required and surfaces the required indicator. */\n required?: boolean;\n /** Helper text displayed below the label to provide additional guidance. */\n helperText?: string;\n /** Error message shown below the helper text and marks the inputs as invalid. */\n errorText?: string;\n /** The children of the radio group. */\n children?: React.ReactNode;\n}\n\n/**\n * RadioGroup component is a wrapper component for a radio group.\n *\n * @param fdKey - Identifier applied to the radio group; also used for the radio name attribute.\n * @param value - Currently selected option value; use null or undefined when no option should be selected.\n * @param onChange - Callback fired whenever the selection changes with the event and newly selected value.\n * @param label - Label rendered above the group; accepts plain text or a custom node.\n * @param required - Marks the radio group as required and surfaces the required indicator.\n * @param helperText - Helper text displayed below the label to provide additional guidance.\n * @param errorText - Error message shown below the helper text and marks the inputs as invalid.\n * @param children - The children of the radio group.\n * @param groupProps - Additional props to pass to the underlying MUI RadioGroup component.\n * @returns The RadioGroup component.\n */\n\nexport const RadioGroup = ({\n fdKey = 'radio-default',\n value = '',\n onChange,\n label,\n required = false,\n helperText,\n errorText,\n children,\n ...groupProps\n}: RadioGroupProps): React.ReactElement => {\n const labelId = `${fdKey}-label`;\n const helperId = helperText ? `${fdKey}-helper` : undefined;\n const errorId = errorText ? `${fdKey}-error` : undefined;\n const describedBy = [helperId, errorId].filter(Boolean).join(' ') || undefined;\n\n const handleChange: MuiRadioGroupProps['onChange'] = (e, newVal) => onChange(e, newVal);\n\n return (\n <Box>\n <StyledLabelContainer>\n <FormLabel id={labelId}>\n {label} {required && <WeakText>{' *'}</WeakText>}\n </FormLabel>\n {helperText ? <MuiFormHelperText id={helperId}>{helperText}</MuiFormHelperText> : null}\n\n {!!errorText && (\n <MuiFormHelperText error id={errorId}>\n <CancelCircleIcon size={'md'} />\n {errorText}\n </MuiFormHelperText>\n )}\n </StyledLabelContainer>\n\n <MuiRadioGroup\n aria-describedby={describedBy}\n aria-invalid={errorText ? true : undefined}\n aria-labelledby={labelId}\n name={fdKey}\n onChange={handleChange}\n value={value}\n {...groupProps}\n >\n {React.Children.map(children, (child) => {\n if (!isValidElement(child)) {\n return child;\n }\n\n // Check if child has FormControlLabel-specific props (value and control)\n // This ensures we only process FormControlLabel components, not arbitrary elements like <Box>\n const childProps = child.props as Partial<FormControlLabelProps>;\n if (!('value' in childProps) || !('control' in childProps)) {\n return child;\n }\n\n const formControlLabelProps = childProps as FormControlLabelProps;\n const radioValue =\n typeof formControlLabelProps.value === 'string'\n ? formControlLabelProps.value\n : String(formControlLabelProps.value ?? '');\n const isSelected = value === radioValue;\n\n // Extract Radio component from FormControlLabel's control prop\n const radioControl = formControlLabelProps.control;\n\n const optionConditionalChildren = formControlLabelProps.children;\n\n // Clone the Radio component with updated children and checked state\n const updatedRadio = isValidElement(radioControl)\n ? React.cloneElement(radioControl, {\n ...(radioControl.props as Record<string, unknown>),\n checked: isSelected,\n error: !!errorText,\n name: fdKey,\n } as Partial<unknown>)\n : radioControl;\n\n return (\n <StyledBox key={radioValue}>\n {React.cloneElement(child as ReactElement<FormControlLabelProps>, {\n ...formControlLabelProps,\n children: undefined,\n control: updatedRadio,\n })}\n\n {/* conditional block under the selected option */}\n {isSelected && optionConditionalChildren && (\n <ConditionalContainer>{optionConditionalChildren}</ConditionalContainer>\n )}\n </StyledBox>\n );\n })}\n </MuiRadioGroup>\n </Box>\n );\n};\n\nexport default RadioGroup;\n"],"names":["StyledBox","styled","Box","theme","marginTop","spacing","marginBottom","StyledLabelContainer","display","flexDirection","ConditionalContainer","gap","borderLeft","palette","semantic","stroke","marginLeft","paddingLeft","WeakText","color","text","RadioGroup","fdKey","value","onChange","label","required","helperText","errorText","children","groupProps","labelId","helperId","undefined","errorId","describedBy","filter","Boolean","join","_jsxs","FormLabel","id","_jsx","MuiFormHelperText","error","CancelCircleIcon","size","MuiRadioGroup","name","e","newVal","React","Children","map","child","isValidElement","childProps","props","formControlLabelProps","radioValue","String","isSelected","radioControl","control","optionConditionalChildren","updatedRadio","cloneElement","checked"],"mappings":"iYAWA,MAAMA,EAAYC,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjCC,UAAWD,EAAME,QAAQ,GAAIC,aAAc,MAG5CC,EAAuBN,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfH,aAAcH,EAAME,QAAQ,QAGxBK,EAAuBT,EAAOC,EAAPD,EAAY,EAAGE,YAAO,CACjDK,QAAS,OACTC,cAAe,SACfE,IAAKR,EAAME,QAAQ,IACnBO,WAAY,aAAaT,EAAMU,QAAQC,SAASC,OAAO,iBACvDX,UAAWD,EAAME,QAAQ,GACzBW,WAAYb,EAAME,QAAQ,MAC1BY,YAAad,EAAME,QAAQ,UAGvBa,EAAWjB,EAAO,OAAPA,EAAe,EAAGE,YAAO,CACxCgB,MAAOhB,EAAMU,QAAQC,SAASM,KAAK,aACnCJ,WAAYb,EAAME,QAAQ,QAsCfgB,EAAa,EACxBC,QAAQ,gBACRC,QAAQ,GACRC,WACAC,QACAC,YAAW,EACXC,aACAC,YACAC,cACGC,MAEH,MAAMC,EAAU,GAAGT,UACbU,EAAWL,EAAa,GAAGL,gBAAiBW,EAC5CC,EAAUN,EAAY,GAAGN,eAAgBW,EACzCE,EAAc,CAACH,EAAUE,GAASE,OAAOC,SAASC,KAAK,WAAQL,EAIrE,OACEM,EAACrC,EAAG,CAAA2B,SAAA,CACFU,EAAChC,EAAoB,CAAAsB,SAAA,CACnBU,EAACC,EAAS,CAACC,GAAIV,EAAOF,SAAA,CACnBJ,EAAK,IAAGC,GAAYgB,EAACxB,EAAQ,CAAAW,SAAE,UAEjCF,EAAae,EAACC,EAAiB,CAACF,GAAIT,EAAQH,SAAGF,IAAkC,OAE/EC,GACDW,EAACI,EAAiB,CAACC,OAAK,EAACH,GAAIP,YAC3BQ,EAACG,EAAgB,CAACC,KAAM,OACvBlB,QAKPc,EAACK,EAAa,CAAA,mBACMZ,EAAW,iBACfP,QAAmBK,EAAS,kBACzBF,EACjBiB,KAAM1B,EACNE,SAvB+C,CAACyB,EAAGC,IAAW1B,EAASyB,EAAGC,GAwB1E3B,MAAOA,KACHO,EAAUD,SAEbsB,EAAMC,SAASC,IAAIxB,GAAWyB,IAC7B,IAAKC,EAAeD,GAClB,OAAOA,EAKT,MAAME,EAAaF,EAAMG,MACzB,KAAM,UAAWD,MAAiB,YAAaA,GAC7C,OAAOF,EAGT,MAAMI,EAAwBF,EACxBG,EACmC,iBAAhCD,EAAsBnC,MACzBmC,EAAsBnC,MACtBqC,OAAOF,EAAsBnC,OAAS,IACtCsC,EAAatC,IAAUoC,EAGvBG,EAAeJ,EAAsBK,QAErCC,EAA4BN,EAAsB7B,SAGlDoC,EAAeV,EAAeO,GAChCX,EAAMe,aAAaJ,EAAc,IAC3BA,EAAaL,MACjBU,QAASN,EACTjB,QAAShB,EACToB,KAAM1B,IAERwC,EAEJ,OACEvB,EAACvC,EAAS,CAAA6B,SAAA,CACPsB,EAAMe,aAAaZ,EAA8C,IAC7DI,EACH7B,cAAUI,EACV8B,QAASE,IAIVJ,GAAcG,GACbtB,EAAChC,EAAoB,CAAAmB,SAAEmC,MATXL"}
@@ -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