From dc237eecdd75f6d4bcc78b902fc14c7a7720a960 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Thu, 3 Sep 2026 03:24:25 +0100 Subject: [PATCH] Implement Custom Spin Component and Update Loading States - Introduced a new Spin component to standardize loading indicators across the application, enhancing visual consistency. - Updated ObjectKanban, ObjectKanbanColumn, and ObjectTable components to utilize the new Spin component, simplifying loading state management. - Refactored CSS styles in App.css to support the new Spin component, including responsive design adjustments for various loading sizes. - Removed redundant loading indicators and streamlined loading behavior for improved user experience during data fetching. --- assets/stylesheets/App.css | 98 ++++++++++++------- .../Dashboard/common/ObjectKanban.jsx | 10 +- .../Dashboard/common/ObjectKanbanColumn.jsx | 6 +- .../Dashboard/common/ObjectTable.jsx | 61 +++++------- src/components/Dashboard/common/Spin.jsx | 77 +++++++++++++++ 5 files changed, 168 insertions(+), 84 deletions(-) create mode 100644 src/components/Dashboard/common/Spin.jsx diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index f95d3f28..416e29e5 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -475,6 +475,67 @@ body { .ant-spin-blur { filter: blur(3px); } + +.spin { + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--color-primary); + font-size: 20px; + line-height: 1; +} + +.spin-nested { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-width: 0; + min-height: 0; + flex: 1; +} + +.spin-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-width: 0; + min-height: 0; + flex: 1; + transition: opacity 0.3s; +} + +.spin-content.is-spinning { + filter: blur(3px); + opacity: 0.5; + user-select: none; + pointer-events: none; +} + +.spin-overlay { + position: absolute; + inset: 0; + z-index: 4; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-primary); + font-size: 20px; + pointer-events: none; +} + +.spin.spin-sm, +.spin-nested.spin-sm .spin-overlay { + font-size: 14px; +} + +.spin.spin-lg, +.spin-nested.spin-lg .spin-overlay { + font-size: 32px; +} /* --- End of src/index.css --- */ /* --- Start of src/components/Dashboard/Layout.css --- */ @@ -1377,26 +1438,6 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { flex-direction: column; } -.objectTableCardsContainer > .ant-spin-nested-loading, -.objectTableCardsContainer > .ant-spin-nested-loading > .ant-spin-container, -.objectTableCardsContainer .ant-spin-nested-loading { - width: 100%; - height: 100%; - min-height: 0; - flex: 1; - display: flex; - flex-direction: column; -} - -.objectTableCardsContainer .ant-spin-container { - width: 100%; - height: 100%; - min-height: 0; - flex: 1; - display: flex; - flex-direction: column; -} - .objectKanbanContainerWrapper { position: relative; width: 100%; @@ -1407,15 +1448,6 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { flex-direction: column; } -.objectKanbanContainerWrapper > .ant-spin-nested-loading, -.objectKanbanContainerWrapper > .ant-spin-nested-loading > .ant-spin-container { - height: 100%; - min-height: 0; - flex: 1; - display: flex; - flex-direction: column; -} - .objectKanbanContainerSkeletons { display: flex; flex-direction: column; @@ -1449,12 +1481,8 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { height: 100%; min-width: 0; min-height: 0; -} - -.objectKanbanWrap > .ant-spin-nested-loading, -.objectKanbanWrap > .ant-spin-nested-loading > .ant-spin-container { - height: 100%; - min-height: 0; + display: flex; + flex-direction: column; } .objectKanbanScroll { diff --git a/src/components/Dashboard/common/ObjectKanban.jsx b/src/components/Dashboard/common/ObjectKanban.jsx index a93bc726..e4dc9e11 100644 --- a/src/components/Dashboard/common/ObjectKanban.jsx +++ b/src/components/Dashboard/common/ObjectKanban.jsx @@ -8,14 +8,14 @@ import { useRef, useState } from 'react' -import { Empty, Flex, Spin } from 'antd' -import { LoadingOutlined } from '@ant-design/icons' +import { Empty, Flex } from 'antd' import PropTypes from 'prop-types' import { ApiServerContext } from '../context/ApiServerContext' import ObjectCard from './ObjectCard' import ObjectKanbanColumn from './ObjectKanbanColumn' import ObjectKanbanHeader from './ObjectKanbanHeader' import ScrollBox from './ScrollBox' +import Spin from './Spin' import { getCategoryValueKey } from './viewModeUtils' const KANBAN_COLUMN_WIDTH = 360 @@ -311,11 +311,7 @@ const ObjectKanban = forwardRef( return (
- } - spinning={showSkeleton} - style={{ height: '100%', flex: 1, minHeight: 0 }} - > +
- } spinning={loading}> + {isKanban ? (
- } - spinning={loading} - style={{ height: '100%' }} - > + - } - spinning={loading} - style={{ - height: '300px', - flex: 1, - minHeight: 0, - width: '100%' - }} - > - {renderCards()} - + {renderCards()}
) : ( - - }} - onScroll={handleScroll} - onChange={handleTableChange} - showSorterTooltip={false} - style={{ height: '100%' }} - size={size} - components={components} - onRow={onRow} - /> + +
+ )} {(showSortSidebar || showFilterSidebar) && ( diff --git a/src/components/Dashboard/common/Spin.jsx b/src/components/Dashboard/common/Spin.jsx new file mode 100644 index 00000000..d0fafd4d --- /dev/null +++ b/src/components/Dashboard/common/Spin.jsx @@ -0,0 +1,77 @@ +import PropTypes from 'prop-types' +import { LoadingOutlined } from '@ant-design/icons' + +const SIZE_CLASS = { + small: 'spin-sm', + default: '', + large: 'spin-lg' +} + +const DEFAULT_INDICATOR = ( + +) + +const Spin = ({ + spinning = true, + children, + indicator = DEFAULT_INDICATOR, + size = 'default', + style, + className +}) => { + const sizeClass = SIZE_CLASS[size] ?? '' + + if (children == null) { + if (!spinning) return null + + return ( +
+ {indicator} +
+ ) + } + + return ( +
+ {spinning && ( +
+ {indicator} +
+ )} +
+ {children} +
+
+ ) +} + +Spin.propTypes = { + spinning: PropTypes.bool, + children: PropTypes.node, + indicator: PropTypes.node, + size: PropTypes.oneOf(['small', 'default', 'large']), + style: PropTypes.object, + className: PropTypes.string +} + +export default Spin