@rio-cloud/rio-uikit 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/actionBarItem/ActionBarItem.d.ts +2 -2
- package/components/actionBarItem/ActionBarItem.js.map +1 -1
- package/components/applicationHeader/ApplicationHeader.d.ts +6 -7
- package/components/applicationHeader/ApplicationHeader.js.map +1 -1
- package/components/applicationLayout/SubNavigation.d.ts +7 -0
- package/components/applicationLayout/SubNavigation.js.map +1 -1
- package/components/assetTree/AssetTree.d.ts +7 -0
- package/components/assetTree/AssetTree.js.map +1 -1
- package/components/assetTree/Tree.d.ts +15 -0
- package/components/assetTree/Tree.js.map +1 -1
- package/components/assetTree/TreeIcon.d.ts +30 -0
- package/components/assetTree/TreeIcon.js +16 -0
- package/components/assetTree/TreeIcon.js.map +1 -0
- package/components/assetTree/TreeLeaf.js +22 -22
- package/components/assetTree/TreeLeaf.js.map +1 -1
- package/components/assetTree/TreeNode.js +25 -25
- package/components/assetTree/TreeNode.js.map +1 -1
- package/components/assetTree/TreeSearch.d.ts +2 -0
- package/components/assetTree/TreeSearch.js.map +1 -1
- package/components/barList/BarList.d.ts +26 -0
- package/components/barList/BarList.js.map +1 -1
- package/components/bottomSheet/BottomSheet.d.ts +17 -3
- package/components/bottomSheet/BottomSheet.js.map +1 -1
- package/components/bottomSheet/TimedBottomSheet.d.ts +10 -0
- package/components/bottomSheet/TimedBottomSheet.js.map +1 -1
- package/components/calendarStripe/CalendarStripe.d.ts +1 -1
- package/components/calendarStripe/CalendarStripe.js +34 -36
- package/components/calendarStripe/CalendarStripe.js.map +1 -1
- package/components/charts/Area.d.ts +2 -2
- package/components/charts/Area.js.map +1 -1
- package/components/charts/Line.d.ts +2 -2
- package/components/charts/Line.js.map +1 -1
- package/components/checkbox/Checkbox.d.ts +0 -3
- package/components/checkbox/Checkbox.js.map +1 -1
- package/components/clearableInput/ClearableInput.d.ts +21 -20
- package/components/clearableInput/ClearableInput.js.map +1 -1
- package/components/collapse/Collapse.d.ts +3 -0
- package/components/collapse/Collapse.js +12 -12
- package/components/collapse/Collapse.js.map +1 -1
- package/components/contentLoader/ContentLoader.d.ts +10 -2
- package/components/contentLoader/ContentLoader.js.map +1 -1
- package/components/dataTabs/DataTabs.d.ts +6 -0
- package/components/dataTabs/DataTabs.js.map +1 -1
- package/components/datepicker/DatePicker.d.ts +2 -2
- package/components/datepicker/DatePicker.js +31 -31
- package/components/datepicker/DatePicker.js.map +1 -1
- package/components/dialog/ConfirmationDialog.d.ts +22 -0
- package/components/dialog/ConfirmationDialog.js.map +1 -1
- package/components/dialog/Dialog.d.ts +13 -1
- package/components/dialog/Dialog.js.map +1 -1
- package/components/dialog/ReleaseNotesDialog.d.ts +3 -3
- package/components/dialog/ReleaseNotesDialog.js.map +1 -1
- package/components/dropdown/ButtonDropdown.d.ts +4 -0
- package/components/dropdown/ButtonDropdown.js +51 -51
- package/components/dropdown/ButtonDropdown.js.map +1 -1
- package/components/dropdown/DropdownSubmenu.d.ts +4 -0
- package/components/dropdown/DropdownSubmenu.js.map +1 -1
- package/components/editableContent/EditableContent.d.ts +6 -0
- package/components/editableContent/EditableContent.js.map +1 -1
- package/components/expander/ExpanderList.d.ts +3 -0
- package/components/expander/ExpanderList.js.map +1 -1
- package/components/expander/ExpanderPanel.d.ts +14 -4
- package/components/expander/ExpanderPanel.js.map +1 -1
- package/components/fade/Fade.d.ts +1 -1
- package/components/fade/Fade.js.map +1 -1
- package/components/filepicker/FilePicker.d.ts +0 -2
- package/components/filepicker/FilePicker.js.map +1 -1
- package/components/groupedItemList/GroupedItemList.d.ts +10 -7
- package/components/groupedItemList/GroupedItemList.js.map +1 -1
- package/components/listMenu/ListMenu.js.map +1 -1
- package/components/listMenu/ListMenuGroup.d.ts +2 -1
- package/components/listMenu/ListMenuGroup.js.map +1 -1
- package/components/map/components/Map.js.map +1 -1
- package/components/map/components/constants.js.map +1 -1
- package/components/map/components/features/ContextMenuItem.d.ts +1 -1
- package/components/map/components/features/ContextMenuItem.js +2 -17
- package/components/map/components/features/ContextMenuItem.js.map +1 -1
- package/components/map/components/features/basics/Polyline.d.ts +4 -1
- package/components/map/components/features/basics/Polyline.js +1 -1
- package/components/map/components/features/basics/Polyline.js.map +1 -1
- package/components/map/components/features/layers/MarkerLayer.d.ts +3 -1
- package/components/map/components/features/layers/MarkerLayer.js.map +1 -1
- package/components/map/components/features/layers/clustering/ClusterLayer.js +1 -1
- package/components/map/components/features/layers/clustering/ClusterLayer.js.map +1 -1
- package/components/map/components/features/layers/clustering/SimpleClusterLayer.d.ts +3 -2
- package/components/map/components/features/layers/clustering/SimpleClusterLayer.js.map +1 -1
- package/components/map/components/features/layers/overlayLayers/RoadRestrictionLayer.js +7 -7
- package/components/map/components/features/layers/overlayLayers/RoadRestrictionLayer.js.map +1 -1
- package/components/map/components/features/layers/overlayLayers/TrafficLayer.js +4 -4
- package/components/map/components/features/layers/overlayLayers/TrafficLayer.js.map +1 -1
- package/components/map/utils/clustering.d.ts +6 -1
- package/components/map/utils/clustering.js +25 -19
- package/components/map/utils/clustering.js.map +1 -1
- package/components/map/utils/rendering.d.ts +1 -1
- package/components/map/utils/rendering.js +23 -23
- package/components/map/utils/rendering.js.map +1 -1
- package/components/menuItems/MenuItem.d.ts +23 -0
- package/components/menuItems/MenuItem.js.map +1 -1
- package/components/notification/Notification.js +4 -4
- package/components/notification/Notification.js.map +1 -1
- package/components/onboarding/OnboardingTip.d.ts +18 -12
- package/components/onboarding/OnboardingTip.js.map +1 -1
- package/components/overlay/OverlayTrigger.d.ts +43 -1
- package/components/overlay/OverlayTrigger.js.map +1 -1
- package/components/pager/Pager.d.ts +3 -0
- package/components/pager/Pager.js.map +1 -1
- package/components/popover/Popover.d.ts +1 -0
- package/components/popover/Popover.js.map +1 -1
- package/components/preloader/ImagePreloader.d.ts +1 -1
- package/components/preloader/ImagePreloader.js.map +1 -1
- package/components/radiobutton/RadioButton.d.ts +9 -5
- package/components/radiobutton/RadioButton.js.map +1 -1
- package/components/releaseNotes/ReleaseNotes.d.ts +0 -3
- package/components/releaseNotes/ReleaseNotes.js.map +1 -1
- package/components/resizer/Resizer.d.ts +17 -3
- package/components/resizer/Resizer.js.map +1 -1
- package/components/rioglyph/Rioglyph.d.ts +20 -8
- package/components/rioglyph/Rioglyph.js.map +1 -1
- package/components/rules/RulesWrapper.js +1 -1
- package/components/rules/RulesWrapper.js.map +1 -1
- package/components/saveableInput/SaveableDateInput.d.ts +20 -2
- package/components/saveableInput/SaveableDateInput.js.map +1 -1
- package/components/saveableInput/SaveableInput.d.ts +10 -2
- package/components/saveableInput/SaveableInput.js.map +1 -1
- package/components/selects/BaseSelectDropdown.js +90 -79
- package/components/selects/BaseSelectDropdown.js.map +1 -1
- package/components/selects/Select.d.ts +5 -0
- package/components/selects/Select.js +94 -94
- package/components/selects/Select.js.map +1 -1
- package/components/sidebars/Sidebar.d.ts +19 -3
- package/components/sidebars/Sidebar.js.map +1 -1
- package/components/slider/RangeSlider.d.ts +15 -0
- package/components/slider/RangeSlider.js.map +1 -1
- package/components/slider/Slider.d.ts +9 -0
- package/components/slider/Slider.js.map +1 -1
- package/components/smoothScrollbars/SmoothScrollbars.d.ts +44 -0
- package/components/smoothScrollbars/SmoothScrollbars.js.map +1 -1
- package/components/spinner/Spinner.d.ts +3 -3
- package/components/spinner/Spinner.js.map +1 -1
- package/components/states/BaseStateProps.d.ts +6 -2
- package/components/states/StateIcon.d.ts +14 -1
- package/components/states/StateIcon.js.map +1 -1
- package/components/statsWidget/StatsWidget.d.ts +2 -0
- package/components/statsWidget/StatsWidget.js.map +1 -1
- package/components/statsWidget/StatsWidgetBody.d.ts +1 -0
- package/components/statsWidget/StatsWidgetBody.js.map +1 -1
- package/components/statsWidget/StatsWidgetNumber.d.ts +2 -0
- package/components/statsWidget/StatsWidgetNumber.js.map +1 -1
- package/components/statusBar/StatusBar.d.ts +2 -31
- package/components/statusBar/StatusBar.js.map +1 -1
- package/components/statusBar/StatusBarIcon.d.ts +2 -2
- package/components/statusBar/StatusBarIcon.js.map +1 -1
- package/components/statusBar/StatusBarLabel.d.ts +2 -2
- package/components/statusBar/StatusBarLabel.js.map +1 -1
- package/components/statusBar/StatusBarProgressBar.d.ts +1 -1
- package/components/statusBar/StatusBarProgressBar.js.map +1 -1
- package/components/statusBar/{StatusBar.types.d.ts → StatusBarProps.d.ts} +44 -2
- package/components/steppedProgressBar/SteppedProgressBar.d.ts +1 -1
- package/components/steppedProgressBar/SteppedProgressBar.js.map +1 -1
- package/components/switch/Switch.d.ts +13 -1
- package/components/switch/Switch.js.map +1 -1
- package/components/table/SortArrowDown.d.ts +1 -1
- package/components/table/SortArrowDown.js.map +1 -1
- package/components/table/SortArrowUp.d.ts +1 -1
- package/components/table/SortArrowUp.js.map +1 -1
- package/components/table/TableSettingsDialog.d.ts +5 -0
- package/components/table/TableSettingsDialog.js +119 -109
- package/components/table/TableSettingsDialog.js.map +1 -1
- package/components/table/TableSettingsDialogFooter.js +9 -9
- package/components/table/TableSettingsDialogFooter.js.map +1 -1
- package/components/table/TableViewToggles.d.ts +21 -1
- package/components/table/TableViewToggles.js.map +1 -1
- package/components/tag/Tag.d.ts +7 -2
- package/components/tag/Tag.js.map +1 -1
- package/components/tagManager/TagManager.d.ts +15 -0
- package/components/tagManager/TagManager.js.map +1 -1
- package/components/tagManager/TagManagerTag.d.ts +9 -0
- package/components/teaser/Teaser.d.ts +57 -55
- package/components/teaser/Teaser.js.map +1 -1
- package/components/teaser/TeaserContainer.d.ts +1 -1
- package/components/teaser/TeaserContainer.js.map +1 -1
- package/components/tooltip/SimpleTooltip.d.ts +22 -4
- package/components/tooltip/SimpleTooltip.js.map +1 -1
- package/components/tooltip/Tooltip.d.ts +22 -2
- package/components/tooltip/Tooltip.js.map +1 -1
- package/components/video/ResponsiveVideo.d.ts +8 -3
- package/components/video/ResponsiveVideo.js.map +1 -1
- package/hooks/useKey.d.ts +1 -1
- package/hooks/useKey.js.map +1 -1
- package/hooks/useOnboarding.d.ts +86 -80
- package/hooks/useOnboarding.js.map +1 -1
- package/hooks/useTableExport.js.map +1 -1
- package/hooks/useUncontrollable.d.ts +1 -1
- package/hooks/useUncontrollable.js.map +1 -1
- package/package.json +12 -14
- package/utils/colorScheme.js +14 -13
- package/utils/colorScheme.js.map +1 -1
- package/utils/cssuseragent.js.map +1 -1
- package/utils/scrollItemIntoView.js +12 -11
- package/utils/scrollItemIntoView.js.map +1 -1
- package/utils/urlFeatureToggles.js +19 -20
- package/utils/urlFeatureToggles.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditableContent.js","sources":["../../../src/components/editableContent/EditableContent.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useMemo, type PropsWithChildren, type CSSProperties } from 'react';\nimport { noop } from 'es-toolkit/function';\nimport classNames from 'classnames';\nimport { usePopper } from 'react-popper';\nimport { createPortal } from 'react-dom';\nimport type { Options, Rect, StrictModifiers } from '@popperjs/core';\n\nimport { getOrCreatePortalRoot } from '../../utils/portalRoot';\nimport useEsc from '../../hooks/useEsc';\nimport useFocusTrap from '../../hooks/useFocusTrap';\n\n// Features\n// [x] Hide children with opacity 0\n// [x] Allow custom editor elements\n// [x] Use textarea for longer text\n// [x] Use focus trap\n// [x] Keyboard support mit esc and tab+enter to accept\n// [-] If space, get the width of the text to make the input larger - no nice solution\n// [x] Control edit mode from outside\n\n// This offset takes into account the padding of the overlay and the padding/border of the edit component (input).\n// This works best if the text is wrapped within a single <span> tag and has a dotted bottom border only.\n// External offset defined by the using component will be starting from 0 for easier understanding.\nconst POPOVER_OFFSET_TOP = -13;\nconst POPOVER_OFFSET_LEFT = -18;\n\nconst DEFAULT_TEXTAREA_ROWS = 1;\n\nexport type EditableContentProps = {\n /**\n * Visibility flag used to control edit mode from outside.\n * @default undefined\n */\n show?: boolean;\n\n /**\n * Callback function triggered when the save button is clicked.\n * @param value\n * @returns\n */\n onSave?: (value: string) => void;\n\n /**\n * Callback function triggered when the cancel button is clicked.\n */\n onCancel?: VoidFunction;\n\n /**\n * Callback function triggered when the input gets focused.\n */\n onFocus?: VoidFunction;\n\n /**\n * Callback function triggered when input looses it's focus.\n */\n onBlur?: VoidFunction;\n\n /**\n * Callback function triggered when entering the edit mode.\n */\n onEditMode?: VoidFunction;\n\n /**\n * Validation function to intercept saving and prevent save on error.\n */\n onSaveValidation?: (value: string) => boolean;\n\n /**\n * Controls the error message visibility from outside. This is useful when using a custom input\n * where the validation happens outside of this component.\n * @default true\n */\n isValid?: boolean;\n\n /**\n * The error message that shall be shown to the user in case the validation fails.\n */\n errorMessage?: React.ReactNode;\n\n /**\n * Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker.\n * Handling input state changes of a custom editor need to be handled outside of this component.\n * By default the EditableContent uses a built-in textarea to allow resizing for larger text.\n */\n customEditor?: React.ReactNode;\n\n /**\n * Offset value to control the vertical position for the editor in case the text element has\n * borders, spacings different text sizes.\n * @default 0\n */\n editorOffsetTop?: number;\n\n /**\n * Offset value to control the horizontal position for the editor in case the text element has\n * borders, spacings different text sizes.\n * @default 0\n */\n editorOffsetLeft?: number;\n\n /**\n * Defines the input and button size. Use 'lg' for headlines.\n * @default 'md'\n */\n size?: 'md' | 'lg';\n\n /**\n * Defines if the internal textarea is allowed to resize vertically.\n * This comes in handy for larger text and when using multiple input rows.\n * @default false\n */\n allowResize?: boolean;\n\n /**\n * Defines the number of rows to use by the internal textarea component.\n * When a single line is used, the input is saved on \"enter\" key.\n *\n * @default 1\n */\n inputRows?: number;\n\n /**\n * Additional classes to be set on the editor input itself.\n */\n inputClassName?: string;\n\n /**\n * Additional classes to be set on the editor wrapper element.\n */\n editorClassName?: string;\n\n /**\n * Additional classes to be set on the text wrapper element.\n */\n className?: string;\n};\n\nconst EditableContent = (props: PropsWithChildren<EditableContentProps>) => {\n const {\n show = undefined,\n onSave = noop,\n onCancel = noop,\n onFocus = noop,\n onBlur = noop,\n onEditMode = noop,\n onSaveValidation = () => true,\n isValid = true,\n errorMessage,\n allowResize = false,\n inputRows = DEFAULT_TEXTAREA_ROWS,\n editorOffsetTop = 0,\n editorOffsetLeft = 0,\n customEditor,\n size = 'md',\n className = '',\n inputClassName = '',\n editorClassName = '',\n children,\n ...remainingProps\n } = props;\n\n const [isEditMode, setIsEditMode] = useState(show);\n const [inputValue, setInputValue] = useState('');\n const [hasError, setHasError] = useState(!isValid);\n\n const [referenceElement, setReferenceElement] = useState<HTMLSpanElement | null>(null);\n const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);\n\n const hasCustomControl = !!customEditor;\n\n // Close edit mode on \"esc\"\n useEsc(() => handleCloseEditMode());\n\n // Trap focus on the popper element\n useFocusTrap(popperElement);\n\n // Input ref used to focus inside when entering edit mode\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n if (inputRef.current && isEditMode) {\n inputRef.current?.focus();\n inputRef.current?.select();\n }\n }, [isEditMode, inputRef.current]);\n\n const customModifier = useMemo(\n () => ({\n name: 'offset',\n options: {\n offset: ({ reference }: { reference: Rect }) => {\n const leftOffset = POPOVER_OFFSET_LEFT + editorOffsetLeft;\n const topOffset = -(reference.height - (POPOVER_OFFSET_TOP + editorOffsetTop));\n return [leftOffset, topOffset];\n },\n },\n }),\n []\n );\n\n const popperConfig: Options = {\n placement: 'bottom-start',\n modifiers: [customModifier],\n strategy: 'absolute',\n };\n\n const { styles, attributes } = usePopper<StrictModifiers>(referenceElement, popperElement, popperConfig);\n\n const handleEditMode = () => {\n // In a controlled usage, the \"show\" property is set from the outside\n // and the edit mode is handled there\n if (show === undefined) {\n enterEditMode();\n }\n onEditMode();\n };\n\n const enterEditMode = () => {\n setIsEditMode(true);\n setInputValue(referenceElement?.textContent?.trimEnd() ?? '');\n setHasError(false);\n };\n\n const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setInputValue(event.target.value);\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n // Prevent line breaks when textarea is a single line\n if (event.key === 'Enter' && inputRows === DEFAULT_TEXTAREA_ROWS) {\n event.preventDefault();\n\n // Save on \"enter\" when textarea has a single line\n handleSaveChanges();\n }\n };\n\n const handleCloseEditMode = () => {\n setIsEditMode(false);\n onCancel();\n };\n\n const handleSaveChanges = () => {\n const isInputValid = onSaveValidation(inputValue);\n setHasError(!isInputValid);\n\n if (isInputValid) {\n setIsEditMode(false);\n onSave(inputValue.trimEnd());\n }\n };\n\n // Control edit mode from outside for instance if text element got focus\n // and user used \"Enter\" key to enter edit mode.\n const [previousShow, setPreviousShow] = useState(show);\n if (show !== previousShow) {\n if (show) {\n enterEditMode();\n }\n if (!show) {\n setIsEditMode(false);\n }\n setPreviousShow(show);\n }\n\n // Control error message visibility from outside. This is useful when using a custom input\n // where the validation happens outside of this component.\n const [previousIsValid, setPreviousIsValid] = useState(isValid);\n if (isValid !== previousIsValid) {\n setHasError(!isValid);\n setPreviousIsValid(isValid);\n }\n\n const textWrapperClasses = classNames(className, isEditMode && 'opacity-0');\n\n const overlayWrapperClasses = classNames(\n 'EditableContentEditor',\n 'display-flex gap-5',\n 'padding-5',\n 'rounded',\n 'shadow-accent',\n 'z-index-max',\n 'bg-white',\n editorClassName\n );\n\n const inputWrapperClasses = classNames(\n 'display-flex flex-column gap-5',\n 'margin-0',\n 'form-group',\n hasError && 'has-feedback has-error'\n );\n\n const inputClasses = classNames('form-control', size === 'lg' && 'input-lg', inputClassName);\n\n const inputStyle: CSSProperties = {\n minWidth: '100px',\n resize: allowResize ? 'vertical' : 'none',\n };\n\n return (\n <>\n <span ref={setReferenceElement} onClick={handleEditMode} className={textWrapperClasses}>\n {children}\n </span>\n {isEditMode &&\n createPortal(\n <div\n className={overlayWrapperClasses}\n onClick={event => event.stopPropagation()}\n ref={setPopperElement}\n style={styles.popper}\n {...attributes.popper}\n {...remainingProps}\n >\n <div className={inputWrapperClasses}>\n {hasCustomControl && customEditor}\n {!hasCustomControl && (\n <textarea\n ref={inputRef}\n className={inputClasses}\n value={inputValue}\n onChange={handleInputChange}\n onFocus={onFocus}\n onBlur={onBlur}\n onKeyDown={handleKeyDown}\n style={inputStyle}\n rows={inputRows}\n />\n )}\n {hasError && <div className='help-block position-relative'>{errorMessage}</div>}\n </div>\n <div className='display-flex gap-5'>\n <button\n type='button'\n onClick={handleCloseEditMode}\n className={`EditableContentCancel btn btn-default btn-icon-only btn-${size}`}\n aria-label='EditableContent cancel button'\n data-testid='EditableContentCancel'\n >\n <span className='rioglyph rioglyph-remove' />\n </button>\n <button\n type='button'\n onClick={handleSaveChanges}\n className={`EditableContentSave btn btn-primary btn-icon-only btn-${size}`}\n aria-label='EditableContent save button'\n data-testid='EditableContentSave'\n >\n <span className='rioglyph rioglyph-ok' />\n </button>\n </div>\n </div>,\n getOrCreatePortalRoot()\n )}\n </>\n );\n};\n\nexport default EditableContent;\n"],"names":["POPOVER_OFFSET_TOP","POPOVER_OFFSET_LEFT","DEFAULT_TEXTAREA_ROWS","EditableContent","props","show","onSave","noop","onCancel","onFocus","onBlur","onEditMode","onSaveValidation","isValid","errorMessage","allowResize","inputRows","editorOffsetTop","editorOffsetLeft","customEditor","size","className","inputClassName","editorClassName","children","remainingProps","isEditMode","setIsEditMode","useState","inputValue","setInputValue","hasError","setHasError","referenceElement","setReferenceElement","popperElement","setPopperElement","hasCustomControl","useEsc","handleCloseEditMode","useFocusTrap","inputRef","useRef","useEffect","popperConfig","useMemo","reference","leftOffset","topOffset","styles","attributes","usePopper","handleEditMode","enterEditMode","handleInputChange","event","handleKeyDown","handleSaveChanges","isInputValid","previousShow","setPreviousShow","previousIsValid","setPreviousIsValid","textWrapperClasses","classNames","overlayWrapperClasses","inputWrapperClasses","inputClasses","inputStyle","jsxs","Fragment","jsx","createPortal","getOrCreatePortalRoot"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditableContent.js","sources":["../../../src/components/editableContent/EditableContent.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useMemo, type PropsWithChildren, type CSSProperties } from 'react';\nimport { noop } from 'es-toolkit/function';\nimport classNames from 'classnames';\nimport { usePopper } from 'react-popper';\nimport { createPortal } from 'react-dom';\nimport type { Options, Rect, StrictModifiers } from '@popperjs/core';\n\nimport { getOrCreatePortalRoot } from '../../utils/portalRoot';\nimport useEsc from '../../hooks/useEsc';\nimport useFocusTrap from '../../hooks/useFocusTrap';\n\n// Features\n// [x] Hide children with opacity 0\n// [x] Allow custom editor elements\n// [x] Use textarea for longer text\n// [x] Use focus trap\n// [x] Keyboard support mit esc and tab+enter to accept\n// [-] If space, get the width of the text to make the input larger - no nice solution\n// [x] Control edit mode from outside\n\n// This offset takes into account the padding of the overlay and the padding/border of the edit component (input).\n// This works best if the text is wrapped within a single <span> tag and has a dotted bottom border only.\n// External offset defined by the using component will be starting from 0 for easier understanding.\nconst POPOVER_OFFSET_TOP = -13;\nconst POPOVER_OFFSET_LEFT = -18;\n\nconst DEFAULT_TEXTAREA_ROWS = 1;\n\nexport type EditableContentProps = {\n /**\n * Visibility flag used to control edit mode from outside.\n *\n * @default undefined\n */\n show?: boolean;\n\n /**\n * Callback function triggered when the save button is clicked.\n *\n * @param value\n * @returns\n */\n onSave?: (value: string) => void;\n\n /**\n * Callback function triggered when the cancel button is clicked.\n */\n onCancel?: VoidFunction;\n\n /**\n * Callback function triggered when the input gets focused.\n */\n onFocus?: VoidFunction;\n\n /**\n * Callback function triggered when input looses it's focus.\n */\n onBlur?: VoidFunction;\n\n /**\n * Callback function triggered when entering the edit mode.\n */\n onEditMode?: VoidFunction;\n\n /**\n * Validation function to intercept saving and prevent save on error.\n */\n onSaveValidation?: (value: string) => boolean;\n\n /**\n * Controls the error message visibility from outside. This is useful when using a custom input\n * where the validation happens outside of this component.\n * @default true\n */\n isValid?: boolean;\n\n /**\n * The error message that shall be shown to the user in case the validation fails.\n */\n errorMessage?: React.ReactNode;\n\n /**\n * Use this prop to define a custom editor input component like Select, NumberInput, TimePicker or DatePicker.\n * Handling input state changes of a custom editor need to be handled outside of this component.\n * By default the EditableContent uses a built-in textarea to allow resizing for larger text.\n */\n customEditor?: React.ReactNode;\n\n /**\n * Offset value to control the vertical position for the editor in case the text element has\n * borders, spacings different text sizes.\n *\n * @default 0\n */\n editorOffsetTop?: number;\n\n /**\n * Offset value to control the horizontal position for the editor in case the text element has\n * borders, spacings different text sizes.\n *\n * @default 0\n */\n editorOffsetLeft?: number;\n\n /**\n * Defines the input and button size. Use 'lg' for headlines.\n *\n * @default 'md'\n */\n size?: 'md' | 'lg';\n\n /**\n * Defines if the internal textarea is allowed to resize vertically.\n * This comes in handy for larger text and when using multiple input rows.\n *\n * @default false\n */\n allowResize?: boolean;\n\n /**\n * Defines the number of rows to use by the internal textarea component.\n * When a single line is used, the input is saved on \"enter\" key.\n *\n * @default 1\n */\n inputRows?: number;\n\n /**\n * Additional classes to be set on the editor input itself.\n */\n inputClassName?: string;\n\n /**\n * Additional classes to be set on the editor wrapper element.\n */\n editorClassName?: string;\n\n /**\n * Additional classes to be set on the text wrapper element.\n */\n className?: string;\n};\n\nconst EditableContent = (props: PropsWithChildren<EditableContentProps>) => {\n const {\n show = undefined,\n onSave = noop,\n onCancel = noop,\n onFocus = noop,\n onBlur = noop,\n onEditMode = noop,\n onSaveValidation = () => true,\n isValid = true,\n errorMessage,\n allowResize = false,\n inputRows = DEFAULT_TEXTAREA_ROWS,\n editorOffsetTop = 0,\n editorOffsetLeft = 0,\n customEditor,\n size = 'md',\n className = '',\n inputClassName = '',\n editorClassName = '',\n children,\n ...remainingProps\n } = props;\n\n const [isEditMode, setIsEditMode] = useState(show);\n const [inputValue, setInputValue] = useState('');\n const [hasError, setHasError] = useState(!isValid);\n\n const [referenceElement, setReferenceElement] = useState<HTMLSpanElement | null>(null);\n const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);\n\n const hasCustomControl = !!customEditor;\n\n // Close edit mode on \"esc\"\n useEsc(() => handleCloseEditMode());\n\n // Trap focus on the popper element\n useFocusTrap(popperElement);\n\n // Input ref used to focus inside when entering edit mode\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n if (inputRef.current && isEditMode) {\n inputRef.current?.focus();\n inputRef.current?.select();\n }\n }, [isEditMode, inputRef.current]);\n\n const customModifier = useMemo(\n () => ({\n name: 'offset',\n options: {\n offset: ({ reference }: { reference: Rect }) => {\n const leftOffset = POPOVER_OFFSET_LEFT + editorOffsetLeft;\n const topOffset = -(reference.height - (POPOVER_OFFSET_TOP + editorOffsetTop));\n return [leftOffset, topOffset];\n },\n },\n }),\n []\n );\n\n const popperConfig: Options = {\n placement: 'bottom-start',\n modifiers: [customModifier],\n strategy: 'absolute',\n };\n\n const { styles, attributes } = usePopper<StrictModifiers>(referenceElement, popperElement, popperConfig);\n\n const handleEditMode = () => {\n // In a controlled usage, the \"show\" property is set from the outside\n // and the edit mode is handled there\n if (show === undefined) {\n enterEditMode();\n }\n onEditMode();\n };\n\n const enterEditMode = () => {\n setIsEditMode(true);\n setInputValue(referenceElement?.textContent?.trimEnd() ?? '');\n setHasError(false);\n };\n\n const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setInputValue(event.target.value);\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n // Prevent line breaks when textarea is a single line\n if (event.key === 'Enter' && inputRows === DEFAULT_TEXTAREA_ROWS) {\n event.preventDefault();\n\n // Save on \"enter\" when textarea has a single line\n handleSaveChanges();\n }\n };\n\n const handleCloseEditMode = () => {\n setIsEditMode(false);\n onCancel();\n };\n\n const handleSaveChanges = () => {\n const isInputValid = onSaveValidation(inputValue);\n setHasError(!isInputValid);\n\n if (isInputValid) {\n setIsEditMode(false);\n onSave(inputValue.trimEnd());\n }\n };\n\n // Control edit mode from outside for instance if text element got focus\n // and user used \"Enter\" key to enter edit mode.\n const [previousShow, setPreviousShow] = useState(show);\n if (show !== previousShow) {\n if (show) {\n enterEditMode();\n }\n if (!show) {\n setIsEditMode(false);\n }\n setPreviousShow(show);\n }\n\n // Control error message visibility from outside. This is useful when using a custom input\n // where the validation happens outside of this component.\n const [previousIsValid, setPreviousIsValid] = useState(isValid);\n if (isValid !== previousIsValid) {\n setHasError(!isValid);\n setPreviousIsValid(isValid);\n }\n\n const textWrapperClasses = classNames(className, isEditMode && 'opacity-0');\n\n const overlayWrapperClasses = classNames(\n 'EditableContentEditor',\n 'display-flex gap-5',\n 'padding-5',\n 'rounded',\n 'shadow-accent',\n 'z-index-max',\n 'bg-white',\n editorClassName\n );\n\n const inputWrapperClasses = classNames(\n 'display-flex flex-column gap-5',\n 'margin-0',\n 'form-group',\n hasError && 'has-feedback has-error'\n );\n\n const inputClasses = classNames('form-control', size === 'lg' && 'input-lg', inputClassName);\n\n const inputStyle: CSSProperties = {\n minWidth: '100px',\n resize: allowResize ? 'vertical' : 'none',\n };\n\n return (\n <>\n <span ref={setReferenceElement} onClick={handleEditMode} className={textWrapperClasses}>\n {children}\n </span>\n {isEditMode &&\n createPortal(\n <div\n className={overlayWrapperClasses}\n onClick={event => event.stopPropagation()}\n ref={setPopperElement}\n style={styles.popper}\n {...attributes.popper}\n {...remainingProps}\n >\n <div className={inputWrapperClasses}>\n {hasCustomControl && customEditor}\n {!hasCustomControl && (\n <textarea\n ref={inputRef}\n className={inputClasses}\n value={inputValue}\n onChange={handleInputChange}\n onFocus={onFocus}\n onBlur={onBlur}\n onKeyDown={handleKeyDown}\n style={inputStyle}\n rows={inputRows}\n />\n )}\n {hasError && <div className='help-block position-relative'>{errorMessage}</div>}\n </div>\n <div className='display-flex gap-5'>\n <button\n type='button'\n onClick={handleCloseEditMode}\n className={`EditableContentCancel btn btn-default btn-icon-only btn-${size}`}\n aria-label='EditableContent cancel button'\n data-testid='EditableContentCancel'\n >\n <span className='rioglyph rioglyph-remove' />\n </button>\n <button\n type='button'\n onClick={handleSaveChanges}\n className={`EditableContentSave btn btn-primary btn-icon-only btn-${size}`}\n aria-label='EditableContent save button'\n data-testid='EditableContentSave'\n >\n <span className='rioglyph rioglyph-ok' />\n </button>\n </div>\n </div>,\n getOrCreatePortalRoot()\n )}\n </>\n );\n};\n\nexport default EditableContent;\n"],"names":["POPOVER_OFFSET_TOP","POPOVER_OFFSET_LEFT","DEFAULT_TEXTAREA_ROWS","EditableContent","props","show","onSave","noop","onCancel","onFocus","onBlur","onEditMode","onSaveValidation","isValid","errorMessage","allowResize","inputRows","editorOffsetTop","editorOffsetLeft","customEditor","size","className","inputClassName","editorClassName","children","remainingProps","isEditMode","setIsEditMode","useState","inputValue","setInputValue","hasError","setHasError","referenceElement","setReferenceElement","popperElement","setPopperElement","hasCustomControl","useEsc","handleCloseEditMode","useFocusTrap","inputRef","useRef","useEffect","popperConfig","useMemo","reference","leftOffset","topOffset","styles","attributes","usePopper","handleEditMode","enterEditMode","handleInputChange","event","handleKeyDown","handleSaveChanges","isInputValid","previousShow","setPreviousShow","previousIsValid","setPreviousIsValid","textWrapperClasses","classNames","overlayWrapperClasses","inputWrapperClasses","inputClasses","inputStyle","jsxs","Fragment","jsx","createPortal","getOrCreatePortalRoot"],"mappings":"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"}
|
|
@@ -46,17 +46,20 @@ export type ExpanderListProps = {
|
|
|
46
46
|
items: ExpanderListItem[];
|
|
47
47
|
/**
|
|
48
48
|
* Defines whether the "expander-list-body" is rounded or not.
|
|
49
|
+
*
|
|
49
50
|
* @default true
|
|
50
51
|
*/
|
|
51
52
|
rounded?: boolean;
|
|
52
53
|
/**
|
|
53
54
|
* Defines whether the "expander-list-body" has a border or not.
|
|
55
|
+
*
|
|
54
56
|
* @default true
|
|
55
57
|
*/
|
|
56
58
|
bordered?: boolean;
|
|
57
59
|
/**
|
|
58
60
|
* It unmounts the body component (remove it from the DOM) when it is collapsed.
|
|
59
61
|
* Set it to false to avoid the unmount.
|
|
62
|
+
*
|
|
60
63
|
* @default true
|
|
61
64
|
*/
|
|
62
65
|
unmountOnExit?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpanderList.js","sources":["../../../src/components/expander/ExpanderList.tsx"],"sourcesContent":["import type React from 'react';\nimport { type PropsWithChildren, useState } from 'react';\nimport { isEqual } from 'es-toolkit/predicate';\n\nimport classNames from 'classnames';\n\nimport Collapse from '../collapse/Collapse';\nimport { hasBorderClass, hasRoundedClass } from '../../utils/hasUtilityClass';\n\nexport type ExpanderListItem = {\n /**\n * The \"id\" property is used to know which item is expanded. It will also be used to control\n * the component from the outside. It can be a number or a string. If there is no \"id\" provided,\n * a unique id is internally generated which will be used internally for the uncontrolled case.'\n */\n id?: string | number;\n\n /**\n * The header content.\n */\n header: string | React.ReactNode;\n\n /**\n * The body content. If there is no \"body\" provided, the list item is not expandable.\n */\n body?: string | React.ReactNode;\n\n /**\n * Defines if the item will be expanded or closed by default.\n */\n open?: boolean;\n\n /**\n * Callback fired when item toggles to open after a click.\n */\n onOpen?: VoidFunction;\n\n /**\n * Callback fired when item toggles to close after a click.\n */\n onClose?: VoidFunction;\n\n /**\n * Additional classes to be set on \"expander-list-header\" node.\n */\n headerClassName?: string;\n\n /**\n * Additional classes to be set on \"expander-list-body\" node.\n */\n bodyClassName?: string;\n\n /**\n * Additional classes to be set on list item node.\n */\n className?: string;\n};\n\nexport type ExpanderListProps = {\n /**\n * List of items to be rendered. The expanded state can be defined via the items `open` prop.\n */\n items: ExpanderListItem[];\n\n /**\n * Defines whether the \"expander-list-body\" is rounded or not.\n * @default true\n */\n rounded?: boolean;\n\n /**\n * Defines whether the \"expander-list-body\" has a border or not.\n * @default true\n */\n bordered?: boolean;\n\n /**\n * It unmounts the body component (remove it from the DOM) when it is collapsed.\n * Set it to false to avoid the unmount.\n * @default true\n */\n unmountOnExit?: boolean;\n\n /**\n * Additional classes to be set on the unordered list itself.\n */\n className?: string;\n};\n\nconst getRandomString = () => (Math.random() + 1).toString(36).toUpperCase().substring(2);\n\n// Generate a unique id (if not present) instead of using the index for the key as it will create\n// side effects when removing items from the list and re-render the ExpanderList.\nconst parseItems = (items: ExpanderListItem[]) => {\n return items.map(item => {\n if (!item.id) {\n item.id = getRandomString();\n }\n return item;\n });\n};\n\nconst ExpanderList = (props: ExpanderListProps) => {\n const { items = [], unmountOnExit = true, rounded = true, bordered = true, className = '' } = props;\n\n const [listItems, setListItems] = useState(parseItems(items));\n\n // Update internal state from external prop change\n const [previousItems, setPreviousItems] = useState(items);\n if (!isEqual(previousItems, items)) {\n setListItems(parseItems(items));\n setPreviousItems(items);\n }\n\n const handleToggleItem = (itemToExpand: ExpanderListItem) => {\n if (!itemToExpand.body) {\n return;\n }\n\n // Toggle the open state for the selected item based on the provided or generated id\n const updatedListItems = [...listItems].map(item => {\n if (item.id === itemToExpand.id) {\n item.onOpen && !item.open && item.onOpen();\n item.onClose && item.open && item.onClose();\n item.open = !item.open;\n }\n return item;\n });\n\n setListItems(updatedListItems);\n };\n\n const listClassNames = classNames(\n 'expander-list list-group',\n rounded && !hasRoundedClass(className) && 'rounded',\n bordered && !hasBorderClass(className) && 'border',\n className\n );\n\n return (\n <ul className={listClassNames}>\n {listItems.map(item => {\n const isOpen = item.open;\n\n const itemClassNames = classNames(\n 'list-group-item',\n item.className && item.className,\n item.body && 'expandable',\n isOpen && 'open'\n );\n\n return (\n <li className={itemClassNames} key={item.id}>\n <ExpanderListItemHeader item={item} onToggle={handleToggleItem} />\n {item.body && (\n <Collapse open={isOpen} unmountOnExit={unmountOnExit}>\n <div className='expander-list-body-wrapper'>\n <ExpanderListItemBody className={item.bodyClassName}>\n {item.body}\n </ExpanderListItemBody>\n </div>\n </Collapse>\n )}\n </li>\n );\n })}\n </ul>\n );\n};\n\ntype ExpanderListItemHeaderProps = {\n item: ExpanderListItem;\n onToggle: (item: ExpanderListItem) => void;\n};\n\nconst ExpanderListItemHeader = ({ item, onToggle }: ExpanderListItemHeaderProps) => {\n const headerClassNames = classNames('expander-list-header', item.headerClassName);\n const iconClassNames = classNames('expander-icon', 'rioglyph', 'rioglyph-chevron-down');\n\n return (\n <div className={headerClassNames} onClick={() => onToggle(item)} aria-label='expander item header'>\n <span className='expander-list-header-content'>{item.header}</span>\n {item.body && <span className={iconClassNames} />}\n </div>\n );\n};\n\ntype ExpanderListItemBodyProps = {\n className?: string;\n};\n\nconst ExpanderListItemBody = ({ className, children }: PropsWithChildren<ExpanderListItemBodyProps>) => {\n const bodyClassNames = classNames('expander-list-body', className);\n return (\n <div className={bodyClassNames} aria-label='expander item body'>\n {children}\n </div>\n );\n};\n\nexport default ExpanderList;\n"],"names":["getRandomString","parseItems","items","item","ExpanderList","props","unmountOnExit","rounded","bordered","className","listItems","setListItems","useState","previousItems","setPreviousItems","isEqual","handleToggleItem","itemToExpand","updatedListItems","listClassNames","classNames","hasRoundedClass","hasBorderClass","isOpen","itemClassNames","jsxs","jsx","ExpanderListItemHeader","Collapse","ExpanderListItemBody","onToggle","headerClassNames","iconClassNames","children","bodyClassNames"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"ExpanderList.js","sources":["../../../src/components/expander/ExpanderList.tsx"],"sourcesContent":["import type React from 'react';\nimport { type PropsWithChildren, useState } from 'react';\nimport { isEqual } from 'es-toolkit/predicate';\n\nimport classNames from 'classnames';\n\nimport Collapse from '../collapse/Collapse';\nimport { hasBorderClass, hasRoundedClass } from '../../utils/hasUtilityClass';\n\nexport type ExpanderListItem = {\n /**\n * The \"id\" property is used to know which item is expanded. It will also be used to control\n * the component from the outside. It can be a number or a string. If there is no \"id\" provided,\n * a unique id is internally generated which will be used internally for the uncontrolled case.'\n */\n id?: string | number;\n\n /**\n * The header content.\n */\n header: string | React.ReactNode;\n\n /**\n * The body content. If there is no \"body\" provided, the list item is not expandable.\n */\n body?: string | React.ReactNode;\n\n /**\n * Defines if the item will be expanded or closed by default.\n */\n open?: boolean;\n\n /**\n * Callback fired when item toggles to open after a click.\n */\n onOpen?: VoidFunction;\n\n /**\n * Callback fired when item toggles to close after a click.\n */\n onClose?: VoidFunction;\n\n /**\n * Additional classes to be set on \"expander-list-header\" node.\n */\n headerClassName?: string;\n\n /**\n * Additional classes to be set on \"expander-list-body\" node.\n */\n bodyClassName?: string;\n\n /**\n * Additional classes to be set on list item node.\n */\n className?: string;\n};\n\nexport type ExpanderListProps = {\n /**\n * List of items to be rendered. The expanded state can be defined via the items `open` prop.\n */\n items: ExpanderListItem[];\n\n /**\n * Defines whether the \"expander-list-body\" is rounded or not.\n *\n * @default true\n */\n rounded?: boolean;\n\n /**\n * Defines whether the \"expander-list-body\" has a border or not.\n *\n * @default true\n */\n bordered?: boolean;\n\n /**\n * It unmounts the body component (remove it from the DOM) when it is collapsed.\n * Set it to false to avoid the unmount.\n *\n * @default true\n */\n unmountOnExit?: boolean;\n\n /**\n * Additional classes to be set on the unordered list itself.\n */\n className?: string;\n};\n\nconst getRandomString = () => (Math.random() + 1).toString(36).toUpperCase().substring(2);\n\n// Generate a unique id (if not present) instead of using the index for the key as it will create\n// side effects when removing items from the list and re-render the ExpanderList.\nconst parseItems = (items: ExpanderListItem[]) => {\n return items.map(item => {\n if (!item.id) {\n item.id = getRandomString();\n }\n return item;\n });\n};\n\nconst ExpanderList = (props: ExpanderListProps) => {\n const { items = [], unmountOnExit = true, rounded = true, bordered = true, className = '' } = props;\n\n const [listItems, setListItems] = useState(parseItems(items));\n\n // Update internal state from external prop change\n const [previousItems, setPreviousItems] = useState(items);\n if (!isEqual(previousItems, items)) {\n setListItems(parseItems(items));\n setPreviousItems(items);\n }\n\n const handleToggleItem = (itemToExpand: ExpanderListItem) => {\n if (!itemToExpand.body) {\n return;\n }\n\n // Toggle the open state for the selected item based on the provided or generated id\n const updatedListItems = [...listItems].map(item => {\n if (item.id === itemToExpand.id) {\n item.onOpen && !item.open && item.onOpen();\n item.onClose && item.open && item.onClose();\n item.open = !item.open;\n }\n return item;\n });\n\n setListItems(updatedListItems);\n };\n\n const listClassNames = classNames(\n 'expander-list list-group',\n rounded && !hasRoundedClass(className) && 'rounded',\n bordered && !hasBorderClass(className) && 'border',\n className\n );\n\n return (\n <ul className={listClassNames}>\n {listItems.map(item => {\n const isOpen = item.open;\n\n const itemClassNames = classNames(\n 'list-group-item',\n item.className && item.className,\n item.body && 'expandable',\n isOpen && 'open'\n );\n\n return (\n <li className={itemClassNames} key={item.id}>\n <ExpanderListItemHeader item={item} onToggle={handleToggleItem} />\n {item.body && (\n <Collapse open={isOpen} unmountOnExit={unmountOnExit}>\n <div className='expander-list-body-wrapper'>\n <ExpanderListItemBody className={item.bodyClassName}>\n {item.body}\n </ExpanderListItemBody>\n </div>\n </Collapse>\n )}\n </li>\n );\n })}\n </ul>\n );\n};\n\ntype ExpanderListItemHeaderProps = {\n item: ExpanderListItem;\n onToggle: (item: ExpanderListItem) => void;\n};\n\nconst ExpanderListItemHeader = ({ item, onToggle }: ExpanderListItemHeaderProps) => {\n const headerClassNames = classNames('expander-list-header', item.headerClassName);\n const iconClassNames = classNames('expander-icon', 'rioglyph', 'rioglyph-chevron-down');\n\n return (\n <div className={headerClassNames} onClick={() => onToggle(item)} aria-label='expander item header'>\n <span className='expander-list-header-content'>{item.header}</span>\n {item.body && <span className={iconClassNames} />}\n </div>\n );\n};\n\ntype ExpanderListItemBodyProps = {\n className?: string;\n};\n\nconst ExpanderListItemBody = ({ className, children }: PropsWithChildren<ExpanderListItemBodyProps>) => {\n const bodyClassNames = classNames('expander-list-body', className);\n return (\n <div className={bodyClassNames} aria-label='expander item body'>\n {children}\n </div>\n );\n};\n\nexport default ExpanderList;\n"],"names":["getRandomString","parseItems","items","item","ExpanderList","props","unmountOnExit","rounded","bordered","className","listItems","setListItems","useState","previousItems","setPreviousItems","isEqual","handleToggleItem","itemToExpand","updatedListItems","listClassNames","classNames","hasRoundedClass","hasBorderClass","isOpen","itemClassNames","jsxs","jsx","ExpanderListItemHeader","Collapse","ExpanderListItemBody","onToggle","headerClassNames","iconClassNames","children","bodyClassNames"],"mappings":";;;;;;AA4FA,MAAMA,IAAkB,OAAO,KAAK,OAAA,IAAW,GAAG,SAAS,EAAE,EAAE,cAAc,UAAU,CAAC,GAIlFC,IAAa,CAACC,MACTA,EAAM,IAAI,CAAAC,OACRA,EAAK,OACNA,EAAK,KAAKH,EAAA,IAEPG,EACV,GAGCC,IAAe,CAACC,MAA6B;AAC/C,QAAM,EAAE,OAAAH,IAAQ,IAAI,eAAAI,IAAgB,IAAM,SAAAC,IAAU,IAAM,UAAAC,IAAW,IAAM,WAAAC,IAAY,GAAA,IAAOJ,GAExF,CAACK,GAAWC,CAAY,IAAIC,EAASX,EAAWC,CAAK,CAAC,GAGtD,CAACW,GAAeC,CAAgB,IAAIF,EAASV,CAAK;AACxD,EAAKa,EAAQF,GAAeX,CAAK,MAC7BS,EAAaV,EAAWC,CAAK,CAAC,GAC9BY,EAAiBZ,CAAK;AAG1B,QAAMc,IAAmB,CAACC,MAAmC;AACzD,QAAI,CAACA,EAAa;AACd;AAIJ,UAAMC,IAAmB,CAAC,GAAGR,CAAS,EAAE,IAAI,CAAAP,OACpCA,EAAK,OAAOc,EAAa,OACzBd,EAAK,UAAU,CAACA,EAAK,QAAQA,EAAK,OAAA,GAClCA,EAAK,WAAWA,EAAK,QAAQA,EAAK,QAAA,GAClCA,EAAK,OAAO,CAACA,EAAK,OAEfA,EACV;AAED,IAAAQ,EAAaO,CAAgB;AAAA,EACjC,GAEMC,IAAiBC;AAAA,IACnB;AAAA,IACAb,KAAW,CAACc,EAAgBZ,CAAS,KAAK;AAAA,IAC1CD,KAAY,CAACc,EAAeb,CAAS,KAAK;AAAA,IAC1CA;AAAA,EAAA;AAGJ,2BACK,MAAA,EAAG,WAAWU,GACV,UAAAT,EAAU,IAAI,CAAAP,MAAQ;AACnB,UAAMoB,IAASpB,EAAK,MAEdqB,IAAiBJ;AAAA,MACnB;AAAA,MACAjB,EAAK,aAAaA,EAAK;AAAA,MACvBA,EAAK,QAAQ;AAAA,MACboB,KAAU;AAAA,IAAA;AAGd,WACI,gBAAAE,EAAC,MAAA,EAAG,WAAWD,GACX,UAAA;AAAA,MAAA,gBAAAE,EAACC,GAAA,EAAuB,MAAAxB,GAAY,UAAUa,EAAA,CAAkB;AAAA,MAC/Db,EAAK,QACF,gBAAAuB,EAACE,KAAS,MAAML,GAAQ,eAAAjB,GACpB,UAAA,gBAAAoB,EAAC,OAAA,EAAI,WAAU,8BACX,UAAA,gBAAAA,EAACG,KAAqB,WAAW1B,EAAK,eACjC,UAAAA,EAAK,KAAA,CACV,GACJ,EAAA,CACJ;AAAA,IAAA,EAAA,GAT4BA,EAAK,EAWzC;AAAA,EAER,CAAC,EAAA,CACL;AAER,GAOMwB,IAAyB,CAAC,EAAE,MAAAxB,GAAM,UAAA2B,QAA4C;AAChF,QAAMC,IAAmBX,EAAW,wBAAwBjB,EAAK,eAAe,GAC1E6B,IAAiBZ,EAAW,iBAAiB,YAAY,uBAAuB;AAEtF,SACI,gBAAAK,EAAC,OAAA,EAAI,WAAWM,GAAkB,SAAS,MAAMD,EAAS3B,CAAI,GAAG,cAAW,wBACxE,UAAA;AAAA,IAAA,gBAAAuB,EAAC,QAAA,EAAK,WAAU,gCAAgC,UAAAvB,EAAK,QAAO;AAAA,IAC3DA,EAAK,QAAQ,gBAAAuB,EAAC,QAAA,EAAK,WAAWM,EAAA,CAAgB;AAAA,EAAA,GACnD;AAER,GAMMH,IAAuB,CAAC,EAAE,WAAApB,GAAW,UAAAwB,QAA6D;AACpG,QAAMC,IAAiBd,EAAW,sBAAsBX,CAAS;AACjE,2BACK,OAAA,EAAI,WAAWyB,GAAgB,cAAW,sBACtC,UAAAD,GACL;AAER;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { default as React
|
|
2
|
-
type ChildrenType = React.ReactNode | ((isOpen: boolean) =>
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
type ChildrenType = React.ReactNode | ((isOpen: boolean) => React.ReactNode | JSX.Element);
|
|
3
3
|
export type ExpanderPanelProps = {
|
|
4
4
|
/**
|
|
5
5
|
* The title to be shown in the expander header.
|
|
@@ -7,27 +7,33 @@ export type ExpanderPanelProps = {
|
|
|
7
7
|
title: string | React.ReactNode;
|
|
8
8
|
/**
|
|
9
9
|
* Component visual or contextual style variants.
|
|
10
|
+
*
|
|
11
|
+
* @default 'blank'
|
|
10
12
|
*/
|
|
11
13
|
bsStyle?: 'blank' | 'default' | 'separator' | 'primary' | 'secondary' | 'info' | 'warning' | 'danger' | 'success';
|
|
12
14
|
/**
|
|
13
15
|
* Defines if the icon will be align left, otherwise it is aligned right.
|
|
16
|
+
*
|
|
14
17
|
* @default false
|
|
15
18
|
*/
|
|
16
19
|
iconLeft?: boolean;
|
|
17
20
|
/**
|
|
18
21
|
* Defines if the panel will be opened or closed by default.
|
|
19
22
|
* The open/closed state will be handled internally.
|
|
23
|
+
*
|
|
20
24
|
* @default false
|
|
21
25
|
*/
|
|
22
26
|
open?: boolean;
|
|
23
27
|
/**
|
|
24
28
|
* It unmounts the body component (remove it from the DOM) when it is collapsed.
|
|
25
29
|
* Set it to false to avoid the unmount.
|
|
30
|
+
*
|
|
26
31
|
* @default true
|
|
27
32
|
*/
|
|
28
33
|
unmountOnExit?: boolean;
|
|
29
34
|
/**
|
|
30
35
|
* Callback function for when the header is clicked and the expander toggles.
|
|
36
|
+
*
|
|
31
37
|
* @param isOpen
|
|
32
38
|
* @returns
|
|
33
39
|
*/
|
|
@@ -66,10 +72,14 @@ export type ExpanderPanelProps = {
|
|
|
66
72
|
className?: string;
|
|
67
73
|
/**
|
|
68
74
|
* Any element to be rendered inside the panel body.
|
|
69
|
-
* Providing a function enables the render prop approach.
|
|
75
|
+
* Providing a function enables the render prop approach.
|
|
76
|
+
*
|
|
77
|
+
* See {@link https://reactjs.org/docs/render-props.html}
|
|
78
|
+
*
|
|
79
|
+
* The function gets the
|
|
70
80
|
* `isOpen` state passed and is responsible for rendering the custom content.
|
|
71
81
|
*/
|
|
72
82
|
children?: ChildrenType;
|
|
73
83
|
};
|
|
74
|
-
declare const ExpanderPanel: (props:
|
|
84
|
+
declare const ExpanderPanel: (props: ExpanderPanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
75
85
|
export default ExpanderPanel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpanderPanel.js","sources":["../../../src/components/expander/ExpanderPanel.tsx"],"sourcesContent":["import React
|
|
1
|
+
{"version":3,"file":"ExpanderPanel.js","sources":["../../../src/components/expander/ExpanderPanel.tsx"],"sourcesContent":["import type React from 'react';\nimport { useState } from 'react';\nimport classNames from 'classnames';\nimport { noop } from 'es-toolkit/function';\n\nimport Collapse from '../collapse/Collapse';\n\ntype ChildrenType = React.ReactNode | ((isOpen: boolean) => React.ReactNode | JSX.Element);\n\nexport type ExpanderPanelProps = {\n /**\n * The title to be shown in the expander header.\n */\n title: string | React.ReactNode;\n\n /**\n * Component visual or contextual style variants.\n *\n * @default 'blank'\n */\n bsStyle?: 'blank' | 'default' | 'separator' | 'primary' | 'secondary' | 'info' | 'warning' | 'danger' | 'success';\n\n /**\n * Defines if the icon will be align left, otherwise it is aligned right.\n *\n * @default false\n */\n iconLeft?: boolean;\n\n /**\n * Defines if the panel will be opened or closed by default.\n * The open/closed state will be handled internally.\n *\n * @default false\n */\n open?: boolean;\n\n /**\n * It unmounts the body component (remove it from the DOM) when it is collapsed.\n * Set it to false to avoid the unmount.\n *\n * @default true\n */\n unmountOnExit?: boolean;\n\n /**\n * Callback function for when the header is clicked and the expander toggles.\n *\n * @param isOpen\n * @returns\n */\n onToggle?: (isOpen: boolean) => void;\n\n /**\n * Callback fired after the component has expanded.\n */\n onEntered?: VoidFunction;\n\n /**\n * Callback fired after the component has collapsed.\n */\n onExited?: VoidFunction;\n\n /**\n * Callback fired when the animation starts for either expand or collapse.\n */\n onAnimationStart?: VoidFunction;\n\n /**\n * Additional classes to be set on the panel header.\n */\n headerClassName?: string;\n\n /**\n * Additional classes to be set on the header title.\n */\n titleClassName?: string;\n\n /**\n * Additional classes to be set on the panel body.\n */\n bodyClassName?: string;\n\n /**\n * Additional classes added to the chevron icon\n */\n iconClassName?: string;\n\n /**\n * Additional classes to be set on the wrapper element.\n */\n className?: string;\n\n /**\n * Any element to be rendered inside the panel body.\n * Providing a function enables the render prop approach.\n *\n * See {@link https://reactjs.org/docs/render-props.html}\n *\n * The function gets the\n * `isOpen` state passed and is responsible for rendering the custom content.\n */\n children?: ChildrenType;\n};\n\nconst ExpanderPanel = (props: ExpanderPanelProps) => {\n const {\n open = false,\n iconLeft = false,\n bsStyle = 'blank',\n title,\n headerClassName,\n titleClassName,\n bodyClassName,\n iconClassName,\n unmountOnExit = true,\n onEntered = noop,\n onExited = noop,\n onAnimationStart = noop,\n onToggle = noop,\n className,\n children,\n ...remainingProps\n } = props;\n\n const [isOpen, setIsOpen] = useState(open);\n\n // Update internal state from external prop change\n const [previousOpen, setPreviousOpen] = useState(open);\n if (previousOpen !== open) {\n setIsOpen(open);\n setPreviousOpen(open);\n }\n\n const handleToggle = () => {\n const newState = !isOpen;\n setIsOpen(newState);\n onToggle(newState);\n };\n\n const wrapperClassNames = classNames('expander-panel panel', `panel-${bsStyle}`, className);\n\n const iconClassNames = classNames('expander-icon', iconClassName, 'rioglyph', 'rioglyph-chevron-down');\n\n const headerClassNames = classNames(\n 'panel-heading',\n isOpen && 'open',\n iconLeft && 'icon-left',\n headerClassName && headerClassName\n );\n\n const titleClassNames = classNames('title', titleClassName && titleClassName);\n\n const bodyClassNames = classNames('panel-body', bodyClassName && bodyClassName);\n\n const isRenderCallback = children && typeof children === 'function';\n\n return (\n <div {...remainingProps} className={wrapperClassNames} aria-label='expander panel'>\n <div className={headerClassNames} onClick={handleToggle} aria-label='panel heading'>\n <span className={titleClassNames}>{title}</span>\n <span className={iconClassNames} />\n {bsStyle === 'separator' && (\n <div className='separator'>\n <hr />\n </div>\n )}\n </div>\n <Collapse\n open={isOpen}\n unmountOnExit={unmountOnExit}\n onEntered={onEntered}\n onExited={onExited}\n onAnimationStart={() => onAnimationStart()}\n >\n <div>\n <div className={bodyClassNames}>{isRenderCallback ? children(isOpen) : children}</div>\n </div>\n </Collapse>\n </div>\n );\n};\n\nexport default ExpanderPanel;\n"],"names":["ExpanderPanel","props","open","iconLeft","bsStyle","title","headerClassName","titleClassName","bodyClassName","iconClassName","unmountOnExit","onEntered","noop","onExited","onAnimationStart","onToggle","className","children","remainingProps","isOpen","setIsOpen","useState","previousOpen","setPreviousOpen","handleToggle","newState","wrapperClassNames","classNames","iconClassNames","headerClassNames","titleClassNames","bodyClassNames","jsxs","jsx","Collapse"],"mappings":";;;;;AAyGA,MAAMA,IAAgB,CAACC,MAA8B;AACjD,QAAM;AAAA,IACF,MAAAC,IAAO;AAAA,IACP,UAAAC,IAAW;AAAA,IACX,SAAAC,IAAU;AAAA,IACV,OAAAC;AAAA,IACA,iBAAAC;AAAA,IACA,gBAAAC;AAAA,IACA,eAAAC;AAAA,IACA,eAAAC;AAAA,IACA,eAAAC,IAAgB;AAAA,IAChB,WAAAC,IAAYC;AAAA,IACZ,UAAAC,IAAWD;AAAA,IACX,kBAAAE,IAAmBF;AAAA,IACnB,UAAAG,IAAWH;AAAA,IACX,WAAAI;AAAA,IACA,UAAAC;AAAA,IACA,GAAGC;AAAA,EAAA,IACHjB,GAEE,CAACkB,GAAQC,CAAS,IAAIC,EAASnB,CAAI,GAGnC,CAACoB,GAAcC,CAAe,IAAIF,EAASnB,CAAI;AACrD,EAAIoB,MAAiBpB,MACjBkB,EAAUlB,CAAI,GACdqB,EAAgBrB,CAAI;AAGxB,QAAMsB,IAAe,MAAM;AACvB,UAAMC,IAAW,CAACN;AAClB,IAAAC,EAAUK,CAAQ,GAClBV,EAASU,CAAQ;AAAA,EACrB,GAEMC,IAAoBC,EAAW,wBAAwB,SAASvB,CAAO,IAAIY,CAAS,GAEpFY,IAAiBD,EAAW,iBAAiBlB,GAAe,YAAY,uBAAuB,GAE/FoB,IAAmBF;AAAA,IACrB;AAAA,IACAR,KAAU;AAAA,IACVhB,KAAY;AAAA,IACZG,KAAmBA;AAAA,EAAA,GAGjBwB,IAAkBH,EAAW,SAASpB,KAAkBA,CAAc,GAEtEwB,IAAiBJ,EAAW,cAAcnB,KAAiBA,CAAa;AAI9E,2BACK,OAAA,EAAK,GAAGU,GAAgB,WAAWQ,GAAmB,cAAW,kBAC9D,UAAA;AAAA,IAAA,gBAAAM,EAAC,SAAI,WAAWH,GAAkB,SAASL,GAAc,cAAW,iBAChE,UAAA;AAAA,MAAA,gBAAAS,EAAC,QAAA,EAAK,WAAWH,GAAkB,UAAAzB,GAAM;AAAA,MACzC,gBAAA4B,EAAC,QAAA,EAAK,WAAWL,EAAA,CAAgB;AAAA,MAChCxB,MAAY,eACT,gBAAA6B,EAAC,OAAA,EAAI,WAAU,aACX,UAAA,gBAAAA,EAAC,QAAG,EAAA,CACR;AAAA,IAAA,GAER;AAAA,IACA,gBAAAA;AAAA,MAACC;AAAA,MAAA;AAAA,QACG,MAAMf;AAAA,QACN,eAAAT;AAAA,QACA,WAAAC;AAAA,QACA,UAAAE;AAAA,QACA,kBAAkB,MAAMC,EAAA;AAAA,QAExB,UAAA,gBAAAmB,EAAC,OAAA,EACG,UAAA,gBAAAA,EAAC,OAAA,EAAI,WAAWF,GAAiB,UArBxBd,KAAY,OAAOA,KAAa,aAqBWA,EAASE,CAAM,IAAIF,EAAA,CAAS,EAAA,CACpF;AAAA,MAAA;AAAA,IAAA;AAAA,EACJ,GACJ;AAER;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Fade.js","sources":["../../../src/components/fade/Fade.tsx"],"sourcesContent":["import { type PropsWithChildren, useState } from 'react';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { noop } from 'es-toolkit/compat';\n\nimport useAfterMount from '../../useAfterMount';\n\ntype AnimationValue = { x: number; y: number; opacity: number };\n\ntype AnimationProp = {\n initial: 'pageEnter' | AnimationValue | false;\n animate: 'pageCenter' | AnimationValue;\n exit: 'pageExit' | AnimationValue;\n};\n\nexport type FadeAnimationStyle =\n | 'fade'\n | 'tabs'\n | 'page'\n | 'pageBack'\n | 'fromLeft'\n | 'fromRight'\n | 'fromTop'\n | 'fromBottom';\n\nconst animationPropsForStyle: Record<FadeAnimationStyle, AnimationProp> = {\n fromLeft: {\n initial: { x: -10, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: -10, y: 0, opacity: 0 },\n },\n fromRight: {\n initial: { x: 10, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: 10, y: 0, opacity: 0 },\n },\n fromTop: {\n initial: { y: -10, x: 0, opacity: 0 },\n animate: { y: 0, x: 0, opacity: 1 },\n exit: { y: -10, x: 0, opacity: 0 },\n },\n fromBottom: {\n initial: { y: 10, x: 0, opacity: 0 },\n animate: { y: 0, x: 0, opacity: 1 },\n exit: { y: 10, x: 0, opacity: 0 },\n },\n fade: {\n initial: { x: 0, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: 0, y: 0, opacity: 0 },\n },\n tabs: {\n initial: { x: 2, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: -2, y: 0, opacity: 0 },\n },\n page: {\n initial: 'pageEnter',\n animate: 'pageCenter',\n exit: 'pageExit',\n },\n pageBack: {\n initial: 'pageEnter',\n animate: 'pageCenter',\n exit: 'pageExit',\n },\n} as const;\n\ntype PageDirection = -1 | 1;\n\nconst getPageDirection = (animationStyle: FadeAnimationStyle): PageDirection =>\n animationStyle === 'pageBack' ? -1 : 1;\n\nconst pageTransitionVariants = {\n pageEnter: (pageDirection: PageDirection) => ({ x: `${pageDirection * 60}%`, opacity: 0 }),\n pageCenter: { x: 0, opacity: 1 },\n pageExit: (pageDirection: PageDirection) => ({ zIndex: 0, x: `${-pageDirection * 60}%`, opacity: 0 }),\n};\n\nexport type FadeProps = {\n /**\n * Defines whether to show the content.\n *\n * @default true\n */\n show?: boolean;\n\n /**\n * Duration of the fade animation in seconds.\n *\n * @default 0.2\n */\n duration?: number;\n\n /**\n * Defines whether the animation is triggered initially when showing the content.\n *\n * @default false\n */\n initial?: boolean;\n\n /**\n * Defines the desired animation style.\n *\n * Possible values are:\n * - `fade`\n * - `tabs`\n * - `page`\n * - `pageBack`\n * - `fromLeft`\n * - `fromRight`\n * - `fromTop`\n * - `fromBottom`\n *\n * @default 'fade'\n */\n animationStyle?: FadeAnimationStyle;\n\n /**\n * If set to true, only one component will be rendered at a time.\n *\n * The exiting component will finish its exit animation before the entering component is rendered.\n *\n * @default false\n */\n exitBeforeEnter?: boolean;\n\n /**\n * Fires when all exiting nodes have completed animating out.\n *\n * @default noop\n */\n onExitComplete?: VoidFunction;\n\n /**\n * Additional custom props for the underlying Framer motion AnimatePresence component.\n *\n * Use this for additional customizations.\n */\n animatePresenceProps?: object;\n\n /**\n * Additional custom props for the underlying Framer motion <motion.div> component.\n *\n * Use this for additional customizations.\n */\n motionProps?: object;\n};\n\nconst Fade = (props: PropsWithChildren<FadeProps>) => {\n const {\n show = true,\n initial = false,\n duration = 0.2,\n exitBeforeEnter = false,\n animationStyle = 'fade',\n animatePresenceProps,\n motionProps,\n onExitComplete = noop,\n children,\n } = props;\n\n const [allowInitialAnimation, setAllowInitialAnimation] = useState(initial);\n\n useAfterMount(() => {\n if (!allowInitialAnimation) {\n setAllowInitialAnimation(true);\n }\n });\n\n let motionDivKey = 'fade';\n\n // If there are multiple children that are conditionally rendered like\n // in case of a tabbed content - get the current visible child key\n // to update the motion.div key to animate different children\n if (Array.isArray(children)) {\n const [currentChild] = children.filter(Boolean);\n motionDivKey = `fade-${currentChild.key}`;\n }\n\n // In case the animation is a page transition, use custom variants and add a custom pageDirection variable\n // to animate in the right direction\n const isPageTransition = animationStyle === 'page' || animationStyle === 'pageBack';\n const pageDirection = getPageDirection(animationStyle);\n const custom = isPageTransition ? pageDirection : null;\n const pageTransitionProps = isPageTransition ? { variants: pageTransitionVariants } : null;\n\n const selectedAnimationProps = animationPropsForStyle[animationStyle];\n\n // Disable initial animation on mount. After mount the initial animation step is needed to enable\n // the \"fade-in\" animation, otherwise it just appears\n const selectedInitial = allowInitialAnimation ? selectedAnimationProps.initial : false;\n const animationProps = { ...selectedAnimationProps, initial: selectedInitial };\n\n return (\n <AnimatePresence\n initial={allowInitialAnimation}\n mode={exitBeforeEnter ? 'wait' : 'sync'}\n onExitComplete={onExitComplete}\n custom={custom}\n {...animatePresenceProps}\n >\n {show && (\n <motion.div\n key={motionDivKey}\n transition={{ duration }}\n custom={custom}\n {...animationProps}\n {...pageTransitionProps}\n {...motionProps}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n );\n};\n\nFade.FADE = 'fade' as const;\nFade.FROM_LEFT = 'fromLeft' as const;\nFade.FROM_RIGHT = 'fromRight' as const;\nFade.FROM_TOP = 'fromTop' as const;\nFade.FROM_BOTTOM = 'fromBottom' as const;\nFade.TABS = 'tabs' as const;\nFade.PAGE = 'page' as const;\nFade.PAGE_BACK = 'pageBack' as const;\n\nexport default Fade;\n"],"names":["animationPropsForStyle","getPageDirection","animationStyle","pageTransitionVariants","pageDirection","Fade","props","show","initial","duration","exitBeforeEnter","animatePresenceProps","motionProps","onExitComplete","noop","children","allowInitialAnimation","setAllowInitialAnimation","useState","useAfterMount","motionDivKey","currentChild","isPageTransition","custom","pageTransitionProps","selectedAnimationProps","selectedInitial","animationProps","jsx","AnimatePresence","motion"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Fade.js","sources":["../../../src/components/fade/Fade.tsx"],"sourcesContent":["import { type PropsWithChildren, useState } from 'react';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { noop } from 'es-toolkit/compat';\n\nimport useAfterMount from '../../useAfterMount';\n\ntype AnimationValue = { x: number; y: number; opacity: number };\n\ntype AnimationProp = {\n initial: 'pageEnter' | AnimationValue | false;\n animate: 'pageCenter' | AnimationValue;\n exit: 'pageExit' | AnimationValue;\n};\n\nexport type FadeAnimationStyle =\n | 'fade'\n | 'tabs'\n | 'page'\n | 'pageBack'\n | 'fromLeft'\n | 'fromRight'\n | 'fromTop'\n | 'fromBottom';\n\nconst animationPropsForStyle: Record<FadeAnimationStyle, AnimationProp> = {\n fromLeft: {\n initial: { x: -10, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: -10, y: 0, opacity: 0 },\n },\n fromRight: {\n initial: { x: 10, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: 10, y: 0, opacity: 0 },\n },\n fromTop: {\n initial: { y: -10, x: 0, opacity: 0 },\n animate: { y: 0, x: 0, opacity: 1 },\n exit: { y: -10, x: 0, opacity: 0 },\n },\n fromBottom: {\n initial: { y: 10, x: 0, opacity: 0 },\n animate: { y: 0, x: 0, opacity: 1 },\n exit: { y: 10, x: 0, opacity: 0 },\n },\n fade: {\n initial: { x: 0, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: 0, y: 0, opacity: 0 },\n },\n tabs: {\n initial: { x: 2, y: 0, opacity: 0 },\n animate: { x: 0, y: 0, opacity: 1 },\n exit: { x: -2, y: 0, opacity: 0 },\n },\n page: {\n initial: 'pageEnter',\n animate: 'pageCenter',\n exit: 'pageExit',\n },\n pageBack: {\n initial: 'pageEnter',\n animate: 'pageCenter',\n exit: 'pageExit',\n },\n} as const;\n\ntype PageDirection = -1 | 1;\n\nconst getPageDirection = (animationStyle: FadeAnimationStyle): PageDirection =>\n animationStyle === 'pageBack' ? -1 : 1;\n\nconst pageTransitionVariants = {\n pageEnter: (pageDirection: PageDirection) => ({ x: `${pageDirection * 60}%`, opacity: 0 }),\n pageCenter: { x: 0, opacity: 1 },\n pageExit: (pageDirection: PageDirection) => ({ zIndex: 0, x: `${-pageDirection * 60}%`, opacity: 0 }),\n};\n\nexport type FadeProps = {\n /**\n * Defines whether to show the content.\n *\n * @default true\n */\n show?: boolean;\n\n /**\n * Duration of the fade animation in seconds.\n *\n * @default 0.2\n */\n duration?: number;\n\n /**\n * Defines whether the animation is triggered initially when showing the content.\n *\n * @default false\n */\n initial?: boolean;\n\n /**\n * Defines the desired animation style.\n *\n * Possible values are:\n * - `fade`\n * - `tabs`\n * - `page`\n * - `pageBack`\n * - `fromLeft`\n * - `fromRight`\n * - `fromTop`\n * - `fromBottom`\n *\n * @default 'fade'\n */\n animationStyle?: FadeAnimationStyle;\n\n /**\n * If set to true, only one component will be rendered at a time.\n *\n * The exiting component will finish its exit animation before the entering component is rendered.\n *\n * @default false\n */\n exitBeforeEnter?: boolean;\n\n /**\n * Fires when all exiting nodes have completed animating out.\n *\n * @default () => {}\n */\n onExitComplete?: VoidFunction;\n\n /**\n * Additional custom props for the underlying Framer motion AnimatePresence component.\n *\n * Use this for additional customizations.\n */\n animatePresenceProps?: object;\n\n /**\n * Additional custom props for the underlying Framer motion <motion.div> component.\n *\n * Use this for additional customizations.\n */\n motionProps?: object;\n};\n\nconst Fade = (props: PropsWithChildren<FadeProps>) => {\n const {\n show = true,\n initial = false,\n duration = 0.2,\n exitBeforeEnter = false,\n animationStyle = 'fade',\n animatePresenceProps,\n motionProps,\n onExitComplete = noop,\n children,\n } = props;\n\n const [allowInitialAnimation, setAllowInitialAnimation] = useState(initial);\n\n useAfterMount(() => {\n if (!allowInitialAnimation) {\n setAllowInitialAnimation(true);\n }\n });\n\n let motionDivKey = 'fade';\n\n // If there are multiple children that are conditionally rendered like\n // in case of a tabbed content - get the current visible child key\n // to update the motion.div key to animate different children\n if (Array.isArray(children)) {\n const [currentChild] = children.filter(Boolean);\n motionDivKey = `fade-${currentChild.key}`;\n }\n\n // In case the animation is a page transition, use custom variants and add a custom pageDirection variable\n // to animate in the right direction\n const isPageTransition = animationStyle === 'page' || animationStyle === 'pageBack';\n const pageDirection = getPageDirection(animationStyle);\n const custom = isPageTransition ? pageDirection : null;\n const pageTransitionProps = isPageTransition ? { variants: pageTransitionVariants } : null;\n\n const selectedAnimationProps = animationPropsForStyle[animationStyle];\n\n // Disable initial animation on mount. After mount the initial animation step is needed to enable\n // the \"fade-in\" animation, otherwise it just appears\n const selectedInitial = allowInitialAnimation ? selectedAnimationProps.initial : false;\n const animationProps = { ...selectedAnimationProps, initial: selectedInitial };\n\n return (\n <AnimatePresence\n initial={allowInitialAnimation}\n mode={exitBeforeEnter ? 'wait' : 'sync'}\n onExitComplete={onExitComplete}\n custom={custom}\n {...animatePresenceProps}\n >\n {show && (\n <motion.div\n key={motionDivKey}\n transition={{ duration }}\n custom={custom}\n {...animationProps}\n {...pageTransitionProps}\n {...motionProps}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n );\n};\n\nFade.FADE = 'fade' as const;\nFade.FROM_LEFT = 'fromLeft' as const;\nFade.FROM_RIGHT = 'fromRight' as const;\nFade.FROM_TOP = 'fromTop' as const;\nFade.FROM_BOTTOM = 'fromBottom' as const;\nFade.TABS = 'tabs' as const;\nFade.PAGE = 'page' as const;\nFade.PAGE_BACK = 'pageBack' as const;\n\nexport default Fade;\n"],"names":["animationPropsForStyle","getPageDirection","animationStyle","pageTransitionVariants","pageDirection","Fade","props","show","initial","duration","exitBeforeEnter","animatePresenceProps","motionProps","onExitComplete","noop","children","allowInitialAnimation","setAllowInitialAnimation","useState","useAfterMount","motionDivKey","currentChild","isPageTransition","custom","pageTransitionProps","selectedAnimationProps","selectedInitial","animationProps","jsx","AnimatePresence","motion"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilePicker.js","sources":["../../../src/components/filepicker/FilePicker.tsx"],"sourcesContent":["import { type ReactNode, useCallback, useEffect, useRef } from 'react';\nimport Dropzone, { type FileRejection, type DropzoneProps, type DropzoneRef } from 'react-dropzone';\nimport { noop } from 'es-toolkit/compat';\n\nimport Button from '../button/Button';\n\ntype FilePickerRenderProps = {\n isDragActive: boolean;\n};\n\nexport type FilePickerProps = {\n /**\n * Defines the file picker display mode.\n *\n * Possible values are `'button'` for a single button, `'dropzone'` for a custom dropzone passed as child function,\n * or `'full'` for showing both.\n *\n * @default 'button'\n */\n displayMode?: 'button' | 'dropzone' | 'full';\n\n /**\n * Indicating if multiple files may be selected.\n *\n * @default true\n */\n multiple?: boolean; // multi select\n\n /**\n * Text to display on Button if displayMode is set to \"button\".\n *\n * @default 'Select files'\n */\n label?: string | ReactNode;\n\n /**\n * Maximum file size in byte. 5 MB = 5,242,880 byte\n */\n maxSize?: number;\n\n /**\n * Function called after one or multiple files have been picked.\n */\n onPick?: (files: File[], rejectedFiles: FileRejection[]) => void;\n\n /**\n * Additional classes for the select button.\n
|
|
1
|
+
{"version":3,"file":"FilePicker.js","sources":["../../../src/components/filepicker/FilePicker.tsx"],"sourcesContent":["import { type ReactNode, useCallback, useEffect, useRef } from 'react';\nimport Dropzone, { type FileRejection, type DropzoneProps, type DropzoneRef } from 'react-dropzone';\nimport { noop } from 'es-toolkit/compat';\n\nimport Button from '../button/Button';\n\ntype FilePickerRenderProps = {\n isDragActive: boolean;\n};\n\nexport type FilePickerProps = {\n /**\n * Defines the file picker display mode.\n *\n * Possible values are `'button'` for a single button, `'dropzone'` for a custom dropzone passed as child function,\n * or `'full'` for showing both.\n *\n * @default 'button'\n */\n displayMode?: 'button' | 'dropzone' | 'full';\n\n /**\n * Indicating if multiple files may be selected.\n *\n * @default true\n */\n multiple?: boolean; // multi select\n\n /**\n * Text to display on Button if displayMode is set to \"button\".\n *\n * @default 'Select files'\n */\n label?: string | ReactNode;\n\n /**\n * Maximum file size in byte. 5 MB = 5,242,880 byte\n */\n maxSize?: number;\n\n /**\n * Function called after one or multiple files have been picked.\n */\n onPick?: (files: File[], rejectedFiles: FileRejection[]) => void;\n\n /**\n * Additional classes for the select button.\n */\n className?: string;\n\n /**\n * Flag to set the dropzone wrapper element to 100% height.\n *\n * This comes in handy, when the dropzone should have 100 % height so it can grow.\n *\n * @default false\n */\n fullHeight?: boolean;\n\n /**\n * Object list of accepted Mime Types as keys and file extensions array as value.\n */\n accept?: { [mimeType: string]: string[] };\n\n /**\n * Pass a custom dropzone element as function receiving some render props.\n */\n children?: ({ isDragActive }: FilePickerRenderProps) => React.ReactElement;\n};\n\nconst FilePicker = (props: FilePickerProps) => {\n const {\n displayMode = 'button',\n multiple = true,\n label = 'Select files',\n maxSize,\n onPick = noop,\n className = '',\n accept,\n fullHeight = false,\n children,\n ...remainingProps\n } = props;\n\n const dropzoneRef = useRef<DropzoneRef>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n\n const isFull = displayMode === 'full';\n const showButton = isFull || displayMode === 'button';\n const showDropzone = isFull || displayMode === 'dropzone';\n\n const handleDrop: NonNullable<DropzoneProps['onDrop']> = useCallback(\n (acceptedFiles, rejectedFiles) => {\n const hasImagesType = accept && Object.keys(accept).some(mimeType => mimeType.startsWith('image'));\n const files = hasImagesType\n ? acceptedFiles.map(file => Object.assign(file, { preview: URL.createObjectURL(file) }))\n : acceptedFiles;\n\n onPick(files, rejectedFiles);\n },\n [onPick, accept]\n );\n\n const handleClick = () => dropzoneRef.current?.open();\n\n useEffect(() => {\n // Set height to dropzones inner presentation div where there is no other access to this DOM element.\n // This comes in handy, when the dropzone should have 100 % height so it can grow.\n if (wrapperRef.current && fullHeight) {\n wrapperRef.current.querySelector('.FilePicker > div')?.setAttribute('class', 'height-100pct');\n }\n }, [fullHeight, wrapperRef.current]);\n\n return (\n <div ref={wrapperRef} className='FilePicker'>\n {showButton && (\n <Button onClick={handleClick} className={className}>\n {label}\n </Button>\n )}\n <Dropzone\n {...remainingProps}\n onDrop={handleDrop}\n accept={accept}\n multiple={multiple}\n maxSize={maxSize}\n ref={dropzoneRef}\n >\n {({ getRootProps, getInputProps, isDragActive }) => {\n return (\n <div {...getRootProps()}>\n <input {...getInputProps()} />\n {showDropzone && children && children({ isDragActive })}\n </div>\n );\n }}\n </Dropzone>\n </div>\n );\n};\n\nFilePicker.DISPLAY_MODE_BUTTON = 'button' as const;\nFilePicker.DISPLAY_MODE_DROPZONE = 'dropzone' as const;\nFilePicker.DISPLAY_MODE_FULL = 'full' as const;\n\nexport default FilePicker;\n"],"names":["FilePicker","props","displayMode","multiple","label","maxSize","onPick","noop","className","accept","fullHeight","children","remainingProps","dropzoneRef","useRef","wrapperRef","isFull","showButton","showDropzone","handleDrop","useCallback","acceptedFiles","rejectedFiles","files","mimeType","file","handleClick","useEffect","jsxs","jsx","Button","Dropzone","getRootProps","getInputProps","isDragActive"],"mappings":";;;;;AAsEA,MAAMA,IAAa,CAACC,MAA2B;AAC3C,QAAM;AAAA,IACF,aAAAC,IAAc;AAAA,IACd,UAAAC,IAAW;AAAA,IACX,OAAAC,IAAQ;AAAA,IACR,SAAAC;AAAA,IACA,QAAAC,IAASC;AAAA,IACT,WAAAC,IAAY;AAAA,IACZ,QAAAC;AAAA,IACA,YAAAC,IAAa;AAAA,IACb,UAAAC;AAAA,IACA,GAAGC;AAAA,EAAA,IACHX,GAEEY,IAAcC,EAAoB,IAAI,GACtCC,IAAaD,EAAuB,IAAI,GAExCE,IAASd,MAAgB,QACzBe,IAAaD,KAAUd,MAAgB,UACvCgB,IAAeF,KAAUd,MAAgB,YAEzCiB,IAAmDC;AAAA,IACrD,CAACC,GAAeC,MAAkB;AAE9B,YAAMC,IADgBd,KAAU,OAAO,KAAKA,CAAM,EAAE,KAAK,CAAAe,MAAYA,EAAS,WAAW,OAAO,CAAC,IAE3FH,EAAc,IAAI,CAAAI,MAAQ,OAAO,OAAOA,GAAM,EAAE,SAAS,IAAI,gBAAgBA,CAAI,EAAA,CAAG,CAAC,IACrFJ;AAEN,MAAAf,EAAOiB,GAAOD,CAAa;AAAA,IAC/B;AAAA,IACA,CAAChB,GAAQG,CAAM;AAAA,EAAA,GAGbiB,IAAc,MAAMb,EAAY,SAAS,KAAA;AAE/C,SAAAc,EAAU,MAAM;AAGZ,IAAIZ,EAAW,WAAWL,KACtBK,EAAW,QAAQ,cAAc,mBAAmB,GAAG,aAAa,SAAS,eAAe;AAAA,EAEpG,GAAG,CAACL,GAAYK,EAAW,OAAO,CAAC,GAG/B,gBAAAa,EAAC,OAAA,EAAI,KAAKb,GAAY,WAAU,cAC3B,UAAA;AAAA,IAAAE,KACG,gBAAAY,EAACC,GAAA,EAAO,SAASJ,GAAa,WAAAlB,GACzB,UAAAJ,GACL;AAAA,IAEJ,gBAAAyB;AAAA,MAACE;AAAA,MAAA;AAAA,QACI,GAAGnB;AAAA,QACJ,QAAQO;AAAA,QACR,QAAAV;AAAA,QACA,UAAAN;AAAA,QACA,SAAAE;AAAA,QACA,KAAKQ;AAAA,QAEJ,UAAA,CAAC,EAAE,cAAAmB,GAAc,eAAAC,GAAe,cAAAC,QAEzB,gBAAAN,EAAC,OAAA,EAAK,GAAGI,EAAA,GACL,UAAA;AAAA,UAAA,gBAAAH,EAAC,SAAA,EAAO,GAAGI,EAAA,EAAc,CAAG;AAAA,UAC3Bf,KAAgBP,KAAYA,EAAS,EAAE,cAAAuB,GAAc;AAAA,QAAA,GAC1D;AAAA,MAER;AAAA,IAAA;AAAA,EACJ,GACJ;AAER;AAEAlC,EAAW,sBAAsB;AACjCA,EAAW,wBAAwB;AACnCA,EAAW,oBAAoB;"}
|
|
@@ -8,25 +8,26 @@ export type GroupedItemListProps<T> = {
|
|
|
8
8
|
/**
|
|
9
9
|
* The list of items to be grouped and rendered.
|
|
10
10
|
*
|
|
11
|
-
* By default, the component assumes an item object containing an
|
|
11
|
+
* By default, the component assumes an item object containing an `id` and `name` property
|
|
12
12
|
* plus some other attributes.
|
|
13
13
|
*/
|
|
14
14
|
items: T[];
|
|
15
15
|
/**
|
|
16
16
|
* The property of the items to group by as a string, or a custom group function.
|
|
17
|
-
* Defaults to
|
|
17
|
+
* Defaults to `name` if not provided.
|
|
18
18
|
*
|
|
19
19
|
* @default 'name'
|
|
20
20
|
*/
|
|
21
21
|
groupBy?: keyof T | ((item: T) => string);
|
|
22
22
|
/**
|
|
23
|
-
* The order in which to sort the groups. Accepts
|
|
24
|
-
* @default 'asc'
|
|
23
|
+
* The order in which to sort the groups. Accepts `asc` for ascending or `desc` for descending.
|
|
24
|
+
* @default 'asc'
|
|
25
25
|
*/
|
|
26
26
|
groupSortOrder?: SortDirectionType;
|
|
27
27
|
/**
|
|
28
|
-
* The order in which to sort the items inside a group. Accepts
|
|
29
|
-
*
|
|
28
|
+
* The order in which to sort the items inside a group. Accepts `asc` for ascending or `desc` for descending.
|
|
29
|
+
*
|
|
30
|
+
* @default 'asc'
|
|
30
31
|
*/
|
|
31
32
|
itemSortOrder?: SortDirectionType;
|
|
32
33
|
/**
|
|
@@ -49,7 +50,9 @@ export type GroupedItemListProps<T> = {
|
|
|
49
50
|
renderItem?: (item: T) => React.ReactNode;
|
|
50
51
|
/**
|
|
51
52
|
* Specifies the HTML element to use for the list inside each group.
|
|
52
|
-
* Default is
|
|
53
|
+
* Default is `div`. Use `ul` for rendering the list items as `<li>` elements.
|
|
54
|
+
*
|
|
55
|
+
* @default 'div'
|
|
53
56
|
*/
|
|
54
57
|
listElement?: 'div' | 'ul';
|
|
55
58
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GroupedItemList.js","sources":["../../../src/components/groupedItemList/GroupedItemList.tsx"],"sourcesContent":["import React, { useMemo } from 'react';\n\nimport { naturalSortByProperty, sortByProperty, type SortDirectionType } from '../../SortUtils';\n\nexport type Group = {\n groupKey: string;\n items: any[];\n};\n\nexport type GroupedItemListProps<T> = {\n /**\n * The list of items to be grouped and rendered.\n *\n * By default, the component assumes an item object containing an
|
|
1
|
+
{"version":3,"file":"GroupedItemList.js","sources":["../../../src/components/groupedItemList/GroupedItemList.tsx"],"sourcesContent":["import React, { useMemo } from 'react';\n\nimport { naturalSortByProperty, sortByProperty, type SortDirectionType } from '../../SortUtils';\n\nexport type Group = {\n groupKey: string;\n items: any[];\n};\n\nexport type GroupedItemListProps<T> = {\n /**\n * The list of items to be grouped and rendered.\n *\n * By default, the component assumes an item object containing an `id` and `name` property\n * plus some other attributes.\n */\n items: T[];\n\n /**\n * The property of the items to group by as a string, or a custom group function.\n * Defaults to `name` if not provided.\n *\n * @default 'name'\n */\n groupBy?: keyof T | ((item: T) => string);\n\n /**\n * The order in which to sort the groups. Accepts `asc` for ascending or `desc` for descending.\n * @default 'asc'\n */\n groupSortOrder?: SortDirectionType;\n\n /**\n * The order in which to sort the items inside a group. Accepts `asc` for ascending or `desc` for descending.\n *\n * @default 'asc'\n */\n itemSortOrder?: SortDirectionType;\n\n /**\n * Sorting function for the groups.\n */\n groupSortFunction?: (groups: Group[]) => Group[];\n\n /**\n * Sorting function for the items within each group.\n */\n itemSortFunction?: (items: T[]) => T[];\n\n /**\n * A custom render function for the group divider.\n * Receives the group key (e.g., letter or date) as a parameter.\n */\n renderDivider?: (groupKey: string) => React.ReactNode;\n\n /**\n * A custom render function for individual list items.\n * Receives an item as a parameter.\n */\n renderItem?: (item: T) => React.ReactNode;\n\n /**\n * Specifies the HTML element to use for the list inside each group.\n * Default is `div`. Use `ul` for rendering the list items as `<li>` elements.\n *\n * @default 'div'\n */\n listElement?: 'div' | 'ul';\n\n /**\n * Optional class name for the list element inside each group.\n * Applied to the `div` or `ul` wrapping the items.\n */\n listElementClassName?: string;\n\n /**\n * Optional class name for the divider element in each group.\n * Applied to the `div` wrapping each group divider.\n */\n dividerElementClassName?: string;\n\n /**\n * Optional class name for the outer wrapper element.\n * Applied to the outer `div` wrapping the entire component.\n */\n className?: string;\n};\n\n/**\n * A React component that groups and renders a list of items based on a specified property.\n * The component can be customized to render dividers and list items in different styles.\n *\n * @param {GroupedItemListProps} props - The props for the component.\n * @returns {JSX.Element} The grouped and rendered list component.\n */\nconst GroupedItemList = <T,>(props: GroupedItemListProps<T>) => {\n const {\n items,\n groupBy = 'name',\n groupSortOrder = 'asc', // Default to ascending order if not specified\n groupSortFunction,\n itemSortFunction,\n itemSortOrder = 'asc', // Default to ascending order if not specified\n renderDivider,\n renderItem,\n listElement = 'div', // Default to 'div' if not specified\n listElementClassName,\n dividerElementClassName,\n className,\n } = props;\n\n // Default group sorting function or specific function provided\n const groupSortFn = groupSortFunction\n ? groupSortFunction\n : (groups: Group[]) => naturalSortByProperty(groups, 'groupKey', groupSortOrder);\n\n // Default item sorting within groups or specific function provided\n const itemSortFn = itemSortFunction\n ? itemSortFunction\n : (itemsToSort: T[]) => sortByProperty(itemsToSort, groupBy as keyof T, itemSortOrder);\n\n // Determine the grouping function based on the type of groupBy\n // const getGroupKey = (item: T): string => {\n // if (typeof groupBy === 'string') {\n // if (groupBy === 'name') {\n // return (item as any).name?.[0]?.toUpperCase() ?? '';\n // }\n // return (item as any)[groupBy]?.toString() ?? '';\n // } else if (typeof groupBy === 'function') {\n // return groupBy(item);\n // }\n // throw new Error('Please define a \"groupBy\" property as a string or function.');\n // };\n\n const getGroupKey =\n typeof groupBy === 'function'\n ? groupBy\n : (item: T) => ((item as any)[groupBy] as string)?.[0]?.toUpperCase() ?? '';\n\n // Group items by the specified property\n // const allGroups = useMemo(\n // () =>\n // items.reduce<Record<string, T[]>>((acc, item) => {\n // const groupKey = getGroupKey(item);\n // if (!acc[groupKey]) {\n // acc[groupKey] = [];\n // }\n // acc[groupKey].push(item);\n // return acc;\n // }, {}),\n // [items, getGroupKey]\n // );\n\n // Group items by the specified property\n const groupedItems = useMemo(() => {\n // Explicitly typing the accumulator as Record<string, T[]>\n const groups: Record<string, T[]> = items.reduce(\n (acc: Record<string, T[]>, item: T) => {\n const groupKey = getGroupKey(item);\n if (!acc[groupKey]) {\n acc[groupKey] = [];\n }\n acc[groupKey].push(item);\n return acc;\n },\n {} as Record<string, T[]>\n );\n\n return Object.keys(groups).map(groupKey => ({\n groupKey,\n items: groups[groupKey],\n }));\n }, [items, getGroupKey]);\n\n // Assign the specified list element ('div' or 'ul')\n const ListElement = listElement;\n\n // Sort groups using the provided sort function\n const sortedGroups = useMemo(() => groupSortFn(groupedItems), [groupedItems, groupSortFn]);\n\n return (\n <div className={`grouped-item-list ${className ?? ''}`}>\n {sortedGroups.map(group => (\n <React.Fragment key={group.groupKey}>\n <div className={`group-divider ${dividerElementClassName ?? ''}`}>\n {renderDivider ? renderDivider(group.groupKey) : group.groupKey}\n </div>\n <ListElement className={listElementClassName}>\n {itemSortFn(group.items).map(item =>\n renderItem ? (\n renderItem(item)\n ) : (\n <React.Fragment key={(item as any).id ?? crypto.randomUUID()}>\n <div className='item-name'>{(item as any).name}</div>\n </React.Fragment>\n )\n )}\n </ListElement>\n </React.Fragment>\n ))}\n </div>\n );\n};\n\nexport default GroupedItemList;\n"],"names":["GroupedItemList","props","items","groupBy","groupSortOrder","groupSortFunction","itemSortFunction","itemSortOrder","renderDivider","renderItem","listElement","listElementClassName","dividerElementClassName","className","groupSortFn","groups","naturalSortByProperty","itemSortFn","itemsToSort","sortByProperty","getGroupKey","item","groupedItems","useMemo","acc","groupKey","ListElement","sortedGroups","jsx","group","jsxs","React"],"mappings":";;;AA+FA,MAAMA,IAAkB,CAAKC,MAAmC;AAC5D,QAAM;AAAA,IACF,OAAAC;AAAA,IACA,SAAAC,IAAU;AAAA,IACV,gBAAAC,IAAiB;AAAA;AAAA,IACjB,mBAAAC;AAAA,IACA,kBAAAC;AAAA,IACA,eAAAC,IAAgB;AAAA;AAAA,IAChB,eAAAC;AAAA,IACA,YAAAC;AAAA,IACA,aAAAC,IAAc;AAAA;AAAA,IACd,sBAAAC;AAAA,IACA,yBAAAC;AAAA,IACA,WAAAC;AAAA,EAAA,IACAZ,GAGEa,IAAcT,MAEd,CAACU,MAAoBC,EAAsBD,GAAQ,YAAYX,CAAc,IAG7Ea,IAAaX,MAEb,CAACY,MAAqBC,EAAeD,GAAaf,GAAoBI,CAAa,IAenFa,IACF,OAAOjB,KAAY,aACbA,IACA,CAACkB,MAAcA,EAAalB,CAAO,IAAe,CAAC,GAAG,iBAAiB,IAiB3EmB,IAAeC,EAAQ,MAAM;AAE/B,UAAMR,IAA8Bb,EAAM;AAAA,MACtC,CAACsB,GAA0BH,MAAY;AACnC,cAAMI,IAAWL,EAAYC,CAAI;AACjC,eAAKG,EAAIC,CAAQ,MACbD,EAAIC,CAAQ,IAAI,CAAA,IAEpBD,EAAIC,CAAQ,EAAE,KAAKJ,CAAI,GAChBG;AAAA,MACX;AAAA,MACA,CAAA;AAAA,IAAC;AAGL,WAAO,OAAO,KAAKT,CAAM,EAAE,IAAI,CAAAU,OAAa;AAAA,MACxC,UAAAA;AAAA,MACA,OAAOV,EAAOU,CAAQ;AAAA,IAAA,EACxB;AAAA,EACN,GAAG,CAACvB,GAAOkB,CAAW,CAAC,GAGjBM,IAAchB,GAGdiB,IAAeJ,EAAQ,MAAMT,EAAYQ,CAAY,GAAG,CAACA,GAAcR,CAAW,CAAC;AAEzF,SACI,gBAAAc,EAAC,OAAA,EAAI,WAAW,qBAAqBf,KAAa,EAAE,IAC/C,UAAAc,EAAa,IAAI,CAAAE,MACd,gBAAAC,EAACC,EAAM,UAAN,EACG,UAAA;AAAA,IAAA,gBAAAH,EAAC,OAAA,EAAI,WAAW,iBAAiBhB,KAA2B,EAAE,IACzD,UAAAJ,IAAgBA,EAAcqB,EAAM,QAAQ,IAAIA,EAAM,UAC3D;AAAA,sBACCH,GAAA,EAAY,WAAWf,GACnB,UAAAM,EAAWY,EAAM,KAAK,EAAE;AAAA,MAAI,CAAAR,MACzBZ,IACIA,EAAWY,CAAI,IAEf,gBAAAO,EAACG,EAAM,UAAN,EACG,UAAA,gBAAAH,EAAC,SAAI,WAAU,aAAc,YAAa,KAAA,CAAK,EAAA,GAD7BP,EAAa,MAAM,OAAO,YAEhD;AAAA,IAAA,EAER,CACJ;AAAA,EAAA,EAAA,GAdiBQ,EAAM,QAe3B,CACH,GACL;AAER;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListMenu.js","sources":["../../../src/components/listMenu/ListMenu.tsx"],"sourcesContent":["// biome-ignore lint/style/useImportType: <explanation>\nimport React, { useEffect, useRef, useState, type MouseEvent, type ReactNode } from 'react';\nimport { isEmpty } from 'es-toolkit/compat';\nimport classNames from 'classnames';\n\nimport useEffectOnce from '../../hooks/useEffectOnce';\nimport ClearableInput from '../clearableInput/ClearableInput';\nimport ExpanderPanel from '../expander/ExpanderPanel';\nimport ListMenuGroup, { type ListMenuNavItem, type ListMenuItem } from './ListMenuGroup';\nimport useEsc from '../../hooks/useEsc';\nimport useWindowResize from '../../hooks/useWindowResize';\nimport useKey from '../../hooks/useKey';\nimport { debounce } from 'es-toolkit/function';\n\nexport type { ListMenuNavItem, ListMenuItem } from './ListMenuGroup';\n\nconst RESIZE_THROTTLING = 10;\nconst MOBILE_MAX_WIDTH = 580;\n\nconst filterMenuItems = <T extends ListMenuNavItem>(\n items: ListMenuItem<T>[],\n value: string,\n filterKey: keyof T\n): ListMenuItem<T>[] =>\n items.map(item => ({\n ...item,\n navItems: item.navItems.filter(navItem =>\n (navItem[filterKey] as string).toLowerCase().includes(value.toLowerCase())\n ),\n }));\n\nconst hasMenuItems = <T extends ListMenuNavItem>(items: ListMenuItem<T>[]) =>\n items.find(({ navItems }) => !isEmpty(navItems));\n\nexport type ListMenuProps<T extends ListMenuNavItem> = {\n /**\n * List of menu item groups to be shown.\n */\n menuItems: ListMenuItem<T>[];\n\n /**\n * Enables the filter.\n *\n * @default false\n */\n enableFilter?: boolean;\n\n /**\n * Focus the filter input.\n *\n * @default false\n */\n focusFilter?: boolean;\n\n /**\n * Define the attribute key for filtering.\n *\n * @default 'key'\n */\n filterKey?: keyof T;\n\n /**\n * The placeholder text for the filter input.\n */\n filterPlaceholder?: string;\n\n /**\n * Gets called when the filter input changes.\n *\n * @param value\n * @returns\n */\n onFilterChange?: (value: string) => void;\n\n /**\n * A localized message to be shown when the filter result is empty.\n */\n notFoundMessage?: string | ReactNode;\n\n /**\n * The menu uses collapses on smaller screens using an expander panel.\n *\n * @default true\n */\n responsive?: boolean;\n\n /**\n * Enables automatic closing of the expander panel.\n *\n * Only relevant when using the `responsive` flag. Note: Make sure to not stop the events from bubbling up when\n * clicking on a list item!\n *\n * Using `event.stopPropagation()` will prevent the panel from closing.\n *\n * @default true\n */\n autoClose?: boolean;\n\n /**\n * Additional classes to be set on the menu group element.\n */\n groupClassName?: string;\n\n /**\n * Additional addon for the input group.\n */\n trailingInputAddon?: React.ReactNode;\n\n /**\n * Additional classes to be set on the wrapper element.\n */\n className?: string;\n};\n\nconst ListMenu = <T extends ListMenuNavItem>(props: ListMenuProps<T>) => {\n const {\n menuItems,\n focusFilter = false,\n enableFilter = false,\n filterPlaceholder,\n onFilterChange = () => {},\n notFoundMessage,\n className = '',\n groupClassName = '',\n responsive = true,\n autoClose = true,\n filterKey = 'key',\n trailingInputAddon,\n ...remainingProps\n } = props;\n\n const [filterValue, setFilterValue] = useState('');\n const [isMobileMode, setIsMobileMode] = useState(false);\n const [isExpanderOpen, setIsExpanderOpen] = useState(false);\n const [mobileHeader, setMobileHeader] = useState<ReactNode>('');\n\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n\n const buildMobileHeader = () => {\n if (responsive && listRef.current) {\n const [activeElement] = listRef.current.getElementsByClassName('active') as HTMLCollectionOf<HTMLElement>;\n setMobileHeader(\n <div className='display-flex align-items-center'>\n <span className='rioglyph rioglyph-menu-hamburger margin-right-10' />\n <span>{activeElement?.innerText}</span>\n </div>\n );\n }\n };\n\n // clear filter input on esc\n useEsc(() => {\n if (enableFilter && inputRef.current === document.activeElement) {\n setFilterValue('');\n }\n });\n\n // Convert the menu to an expandable panel for smaller screens\n const handleMobileSize = debounce(() => {\n buildMobileHeader();\n setIsMobileMode(window.innerWidth < MOBILE_MAX_WIDTH);\n }, RESIZE_THROTTLING);\n\n // Render a dropdown menu on mobile on mount\n useEffect(handleMobileSize, []);\n\n useWindowResize(handleMobileSize);\n\n const focusInput = () => {\n inputRef.current?.focus();\n };\n\n useKey((event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'k') {\n event.preventDefault();\n focusInput();\n }\n });\n\n // Focus filter input if requested\n useEffectOnce(() => {\n focusFilter && focusInput();\n });\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>\n useEffect(() => buildMobileHeader, [menuItems]);\n\n const handleClear = () => focusFilter && focusInput();\n\n const handleFilterChange = (value: string) => {\n setFilterValue(value);\n onFilterChange(value);\n };\n\n const filteredMenuItems = filterMenuItems(menuItems, filterValue, filterKey);\n\n const handleExpanderBodyClick = (event: MouseEvent) => {\n const isListItem = (event.target as HTMLDivElement).parentElement?.tagName.toLowerCase() === 'li';\n if (autoClose && isListItem) {\n setIsExpanderOpen(false);\n }\n };\n\n const formClassNames = classNames(\n 'form-group',\n 'margin-bottom-5',\n 'padding-left-15',\n 'padding-right-15',\n 'padding-bottom-15',\n 'position-sticky',\n 'top-0',\n 'z-index-1'\n );\n\n const listMenu = (\n <div {...remainingProps} className={`ListMenu ${className} ${filterValue ? 'filtered' : ''}`} ref={listRef}>\n {enableFilter && (\n <div className={formClassNames}>\n <div className='input-group width-100pct'>\n <span className='input-group-addon'>\n <span className='rioglyph rioglyph-search' aria-hidden='true' />\n </span>\n <ClearableInput\n value={filterValue}\n inputRef={inputRef}\n placeholder={filterPlaceholder}\n onChange={handleFilterChange}\n onClear={handleClear}\n />\n {trailingInputAddon && trailingInputAddon}\n </div>\n </div>\n )}\n\n {!hasMenuItems(filteredMenuItems) && (\n <div className='padding-top-25 text-center text-color-gray'>{notFoundMessage}</div>\n )}\n\n {filteredMenuItems.map((menuGroup, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: using a random key would re-render the list every time the parent updates\n <ListMenuGroup key={index} className={groupClassName} menuGroup={menuGroup} />\n ))}\n </div>\n );\n\n if (responsive && isMobileMode) {\n return (\n <ExpanderPanel\n title={mobileHeader}\n bsStyle='default'\n open={isExpanderOpen}\n onToggle={() => setIsExpanderOpen(!isExpanderOpen)}\n unmountOnExit={false}\n className='shadow-default'\n >\n <div onClick={handleExpanderBodyClick}>{listMenu}</div>\n </ExpanderPanel>\n );\n }\n\n return listMenu;\n};\n\nexport default ListMenu;\n"],"names":["RESIZE_THROTTLING","MOBILE_MAX_WIDTH","filterMenuItems","items","value","filterKey","item","navItem","hasMenuItems","navItems","isEmpty","ListMenu","props","menuItems","focusFilter","enableFilter","filterPlaceholder","onFilterChange","notFoundMessage","className","groupClassName","responsive","autoClose","trailingInputAddon","remainingProps","filterValue","setFilterValue","useState","isMobileMode","setIsMobileMode","isExpanderOpen","setIsExpanderOpen","mobileHeader","setMobileHeader","inputRef","useRef","listRef","buildMobileHeader","activeElement","jsxs","jsx","useEsc","handleMobileSize","debounce","useEffect","useWindowResize","focusInput","useKey","event","useEffectOnce","handleClear","handleFilterChange","filteredMenuItems","handleExpanderBodyClick","isListItem","formClassNames","classNames","listMenu","ClearableInput","menuGroup","index","ListMenuGroup","ExpanderPanel"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ListMenu.js","sources":["../../../src/components/listMenu/ListMenu.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState, type MouseEvent, type ReactNode } from 'react';\nimport { isEmpty } from 'es-toolkit/compat';\nimport classNames from 'classnames';\n\nimport useEffectOnce from '../../hooks/useEffectOnce';\nimport ClearableInput from '../clearableInput/ClearableInput';\nimport ExpanderPanel from '../expander/ExpanderPanel';\nimport ListMenuGroup, { type ListMenuNavItem, type ListMenuItem } from './ListMenuGroup';\nimport useEsc from '../../hooks/useEsc';\nimport useWindowResize from '../../hooks/useWindowResize';\nimport useKey from '../../hooks/useKey';\nimport { debounce } from 'es-toolkit/function';\n\nexport type { ListMenuNavItem, ListMenuItem } from './ListMenuGroup';\n\nconst RESIZE_THROTTLING = 10;\nconst MOBILE_MAX_WIDTH = 580;\n\nconst filterMenuItems = <T extends ListMenuNavItem>(\n items: ListMenuItem<T>[],\n value: string,\n filterKey: keyof T\n): ListMenuItem<T>[] =>\n items.map(item => ({\n ...item,\n navItems: item.navItems.filter(navItem =>\n (navItem[filterKey] as string).toLowerCase().includes(value.toLowerCase())\n ),\n }));\n\nconst hasMenuItems = <T extends ListMenuNavItem>(items: ListMenuItem<T>[]) =>\n items.find(({ navItems }) => !isEmpty(navItems));\n\nexport type ListMenuProps<T extends ListMenuNavItem> = {\n /**\n * List of menu item groups to be shown.\n */\n menuItems: ListMenuItem<T>[];\n\n /**\n * Enables the filter.\n *\n * @default false\n */\n enableFilter?: boolean;\n\n /**\n * Focus the filter input.\n *\n * @default false\n */\n focusFilter?: boolean;\n\n /**\n * Define the attribute key for filtering.\n *\n * @default 'key'\n */\n filterKey?: keyof T;\n\n /**\n * The placeholder text for the filter input.\n */\n filterPlaceholder?: string;\n\n /**\n * Gets called when the filter input changes.\n *\n * @param value\n * @returns\n */\n onFilterChange?: (value: string) => void;\n\n /**\n * A localized message to be shown when the filter result is empty.\n */\n notFoundMessage?: string | ReactNode;\n\n /**\n * The menu uses collapses on smaller screens using an expander panel.\n *\n * @default true\n */\n responsive?: boolean;\n\n /**\n * Enables automatic closing of the expander panel.\n *\n * Only relevant when using the `responsive` flag. Note: Make sure to not stop the events from bubbling up when\n * clicking on a list item!\n *\n * Using `event.stopPropagation()` will prevent the panel from closing.\n *\n * @default true\n */\n autoClose?: boolean;\n\n /**\n * Additional classes to be set on the menu group element.\n */\n groupClassName?: string;\n\n /**\n * Additional addon for the input group.\n */\n trailingInputAddon?: React.ReactNode;\n\n /**\n * Additional classes to be set on the wrapper element.\n */\n className?: string;\n};\n\nconst ListMenu = <T extends ListMenuNavItem>(props: ListMenuProps<T>) => {\n const {\n menuItems,\n focusFilter = false,\n enableFilter = false,\n filterPlaceholder,\n onFilterChange = () => {},\n notFoundMessage,\n className = '',\n groupClassName = '',\n responsive = true,\n autoClose = true,\n filterKey = 'key',\n trailingInputAddon,\n ...remainingProps\n } = props;\n\n const [filterValue, setFilterValue] = useState('');\n const [isMobileMode, setIsMobileMode] = useState(false);\n const [isExpanderOpen, setIsExpanderOpen] = useState(false);\n const [mobileHeader, setMobileHeader] = useState<ReactNode>('');\n\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n\n const buildMobileHeader = () => {\n if (responsive && listRef.current) {\n const [activeElement] = listRef.current.getElementsByClassName('active') as HTMLCollectionOf<HTMLElement>;\n setMobileHeader(\n <div className='display-flex align-items-center'>\n <span className='rioglyph rioglyph-menu-hamburger margin-right-10' />\n <span>{activeElement?.innerText}</span>\n </div>\n );\n }\n };\n\n // clear filter input on esc\n useEsc(() => {\n if (enableFilter && inputRef.current === document.activeElement) {\n setFilterValue('');\n }\n });\n\n // Convert the menu to an expandable panel for smaller screens\n const handleMobileSize = debounce(() => {\n buildMobileHeader();\n setIsMobileMode(window.innerWidth < MOBILE_MAX_WIDTH);\n }, RESIZE_THROTTLING);\n\n // Render a dropdown menu on mobile on mount\n useEffect(handleMobileSize, []);\n\n useWindowResize(handleMobileSize);\n\n const focusInput = () => {\n inputRef.current?.focus();\n };\n\n useKey((event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'k') {\n event.preventDefault();\n focusInput();\n }\n });\n\n // Focus filter input if requested\n useEffectOnce(() => {\n focusFilter && focusInput();\n });\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>\n useEffect(() => buildMobileHeader, [menuItems]);\n\n const handleClear = () => focusFilter && focusInput();\n\n const handleFilterChange = (value: string) => {\n setFilterValue(value);\n onFilterChange(value);\n };\n\n const filteredMenuItems = filterMenuItems(menuItems, filterValue, filterKey);\n\n const handleExpanderBodyClick = (event: MouseEvent) => {\n const isListItem = (event.target as HTMLDivElement).parentElement?.tagName.toLowerCase() === 'li';\n if (autoClose && isListItem) {\n setIsExpanderOpen(false);\n }\n };\n\n const formClassNames = classNames(\n 'form-group',\n 'margin-bottom-5',\n 'padding-left-15',\n 'padding-right-15',\n 'padding-bottom-15',\n 'position-sticky',\n 'top-0',\n 'z-index-1'\n );\n\n const listMenu = (\n <div {...remainingProps} className={`ListMenu ${className} ${filterValue ? 'filtered' : ''}`} ref={listRef}>\n {enableFilter && (\n <div className={formClassNames}>\n <div className='input-group width-100pct'>\n <span className='input-group-addon'>\n <span className='rioglyph rioglyph-search' aria-hidden='true' />\n </span>\n <ClearableInput\n value={filterValue}\n inputRef={inputRef}\n placeholder={filterPlaceholder}\n onChange={handleFilterChange}\n onClear={handleClear}\n />\n {trailingInputAddon && trailingInputAddon}\n </div>\n </div>\n )}\n\n {!hasMenuItems(filteredMenuItems) && (\n <div className='padding-top-25 text-center text-color-gray'>{notFoundMessage}</div>\n )}\n\n {filteredMenuItems.map((menuGroup, index) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: using a random key would re-render the list every time the parent updates\n <ListMenuGroup key={index} className={groupClassName} menuGroup={menuGroup} />\n ))}\n </div>\n );\n\n if (responsive && isMobileMode) {\n return (\n <ExpanderPanel\n title={mobileHeader}\n bsStyle='default'\n open={isExpanderOpen}\n onToggle={() => setIsExpanderOpen(!isExpanderOpen)}\n unmountOnExit={false}\n className='shadow-default'\n >\n <div onClick={handleExpanderBodyClick}>{listMenu}</div>\n </ExpanderPanel>\n );\n }\n\n return listMenu;\n};\n\nexport default ListMenu;\n"],"names":["RESIZE_THROTTLING","MOBILE_MAX_WIDTH","filterMenuItems","items","value","filterKey","item","navItem","hasMenuItems","navItems","isEmpty","ListMenu","props","menuItems","focusFilter","enableFilter","filterPlaceholder","onFilterChange","notFoundMessage","className","groupClassName","responsive","autoClose","trailingInputAddon","remainingProps","filterValue","setFilterValue","useState","isMobileMode","setIsMobileMode","isExpanderOpen","setIsExpanderOpen","mobileHeader","setMobileHeader","inputRef","useRef","listRef","buildMobileHeader","activeElement","jsxs","jsx","useEsc","handleMobileSize","debounce","useEffect","useWindowResize","focusInput","useKey","event","useEffectOnce","handleClear","handleFilterChange","filteredMenuItems","handleExpanderBodyClick","isListItem","formClassNames","classNames","listMenu","ClearableInput","menuGroup","index","ListMenuGroup","ExpanderPanel"],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ export type ListMenuNavItem = {
|
|
|
9
9
|
/**
|
|
10
10
|
* The menu nav item itself.
|
|
11
11
|
*
|
|
12
|
-
* This can be a simple
|
|
12
|
+
* This can be a simple `<a>`, `<NavLink>`, `<Link>`, `<span>` or even a `<FormattedMessage>`.
|
|
13
13
|
*/
|
|
14
14
|
item: string | ReactNode;
|
|
15
15
|
/**
|
|
@@ -49,6 +49,7 @@ export type ListMenuItem<T extends ListMenuNavItem = ListMenuNavItem> = {
|
|
|
49
49
|
* The type of badge to display.
|
|
50
50
|
*
|
|
51
51
|
* Determines the visual style of the badge. Available types include:
|
|
52
|
+
*
|
|
52
53
|
* - `muted`: A subdued badge style.
|
|
53
54
|
* - `success`: Indicates a successful state.
|
|
54
55
|
* - `info`: Represents informational content.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListMenuGroup.js","sources":["../../../src/components/listMenu/ListMenuGroup.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport classNames from 'classnames';\n\nimport ListMenuHeader from './ListMenuHeader';\n\nexport type ListMenuNavItem = {\n /**\n * The name or key of the menu nav item.\n *\n * This key will be used for filtering, hence it should match the translated menu item name.\n */\n key: string;\n\n /**\n * The menu nav item itself.\n *\n * This can be a simple
|
|
1
|
+
{"version":3,"file":"ListMenuGroup.js","sources":["../../../src/components/listMenu/ListMenuGroup.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport classNames from 'classnames';\n\nimport ListMenuHeader from './ListMenuHeader';\n\nexport type ListMenuNavItem = {\n /**\n * The name or key of the menu nav item.\n *\n * This key will be used for filtering, hence it should match the translated menu item name.\n */\n key: string;\n\n /**\n * The menu nav item itself.\n *\n * This can be a simple `<a>`, `<NavLink>`, `<Link>`, `<span>` or even a `<FormattedMessage>`.\n */\n item: string | ReactNode;\n\n /**\n * Disables the list item.\n *\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Whether the current menu item is a child of another menu item.\n */\n isSubItem?: boolean;\n} & {\n [otherFilterKey: Exclude<string, 'key' | 'item' | 'disabled' | 'isSubItem'>]: unknown;\n};\n\nexport type ListMenuItem<T extends ListMenuNavItem = ListMenuNavItem> = {\n /**\n * The optional group header to be shown.\n *\n * This can also be a component such as a `FormattedMessage`.\n */\n group?: string | ReactNode;\n\n /**\n * The optional group header link to be shown.\n *\n * This can be a link component such as `NavLink` or `Link`. This way, the header can be a link itself. In this\n * case, the `group` prop can be omitted.\n */\n groupNavItem?: string | ReactNode;\n\n /**\n * The optional badge displayed alongside the group or nav items.\n *\n * This can be text or a React node for additional flexibility.\n */\n badge?: string | ReactNode;\n\n /**\n * The type of badge to display.\n *\n * Determines the visual style of the badge. Available types include:\n *\n * - `muted`: A subdued badge style.\n * - `success`: Indicates a successful state.\n * - `info`: Represents informational content.\n * - `warning`: Warns of potential issues.\n * - `danger`: Highlights a critical state.\n */\n badgeType?: 'muted' | 'success' | 'info' | 'warning' | 'danger';\n\n /**\n * The list of all menu items of a group.\n */\n navItems: T[];\n};\n\ntype ListMenuGroupProps<T extends ListMenuNavItem> = {\n menuGroup: ListMenuItem<T>;\n className: string;\n};\n\nconst ListMenuGroup = <T extends ListMenuNavItem>({ menuGroup, className }: ListMenuGroupProps<T>) => {\n if (!menuGroup.navItems.length) {\n return null;\n }\n\n return (\n <ul className={`ListMenuGroup ${className}`}>\n <ListMenuHeader {...menuGroup} />\n\n {menuGroup.navItems.map(item => (\n <li key={item.key} className={classNames({ disabled: item.disabled, subitem: item.isSubItem })}>\n {item.item}\n </li>\n ))}\n </ul>\n );\n};\n\nexport default ListMenuGroup;\n"],"names":["ListMenuGroup","menuGroup","className","jsxs","jsx","ListMenuHeader","item","classNames"],"mappings":";;;AAkFA,MAAMA,IAAgB,CAA4B,EAAE,WAAAC,GAAW,WAAAC,QACtDD,EAAU,SAAS,SAKpB,gBAAAE,EAAC,MAAA,EAAG,WAAW,iBAAiBD,CAAS,IACrC,UAAA;AAAA,EAAA,gBAAAE,EAACC,GAAA,EAAgB,GAAGJ,GAAW;AAAA,EAE9BA,EAAU,SAAS,IAAI,CAAAK,wBACnB,MAAA,EAAkB,WAAWC,EAAW,EAAE,UAAUD,EAAK,UAAU,SAASA,EAAK,WAAW,GACxF,UAAAA,EAAK,KAAA,GADDA,EAAK,GAEd,CACH;AAAA,GACL,IAZO;"}
|