chore: refines shimmer, adds to list view and CodeEditor element
This commit is contained in:
45
src/admin/components/elements/CodeEditor/CodeEditor.tsx
Normal file
45
src/admin/components/elements/CodeEditor/CodeEditor.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import type { Props } from './types';
|
||||
import { useTheme } from '../../utilities/Theme';
|
||||
|
||||
import './index.scss';
|
||||
import { ShimmerEffect } from '../ShimmerEffect';
|
||||
|
||||
const baseClass = 'code-editor';
|
||||
|
||||
const CodeEditor: React.FC<Props> = (props) => {
|
||||
const { readOnly, className, options, ...rest } = props;
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
className,
|
||||
rest?.defaultLanguage ? `language--${rest.defaultLanguage}` : '',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<Editor
|
||||
className={classes}
|
||||
theme={theme === 'dark' ? 'vs-dark' : 'vs'}
|
||||
loading={<ShimmerEffect height="35vh" />}
|
||||
options={
|
||||
{
|
||||
detectIndentation: true,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
readOnly: Boolean(readOnly),
|
||||
scrollBeyondLastLine: false,
|
||||
tabSize: 2,
|
||||
wordWrap: 'on',
|
||||
...options,
|
||||
}
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeEditor;
|
||||
@@ -1,44 +1,18 @@
|
||||
import React from 'react';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import type { Props } from './types';
|
||||
import { useTheme } from '../../utilities/Theme';
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import { ShimmerEffect } from '../ShimmerEffect';
|
||||
import { Props } from './types';
|
||||
|
||||
import './index.scss';
|
||||
const LazyEditor = lazy(() => import('./CodeEditor'));
|
||||
|
||||
const baseClass = 'code-editor';
|
||||
|
||||
const CodeEditor: React.FC<Props> = (props) => {
|
||||
const { readOnly, className, options, ...rest } = props;
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
className,
|
||||
rest?.defaultLanguage ? `language--${rest.defaultLanguage}` : '',
|
||||
].filter(Boolean).join(' ');
|
||||
export const CodeEditor: React.FC<Props> = (props) => {
|
||||
const { height = '35vh' } = props;
|
||||
|
||||
return (
|
||||
<Editor
|
||||
height="35vh"
|
||||
className={classes}
|
||||
theme={theme === 'dark' ? 'vs-dark' : 'vs'}
|
||||
options={
|
||||
{
|
||||
detectIndentation: true,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
readOnly: Boolean(readOnly),
|
||||
scrollBeyondLastLine: false,
|
||||
tabSize: 2,
|
||||
wordWrap: 'on',
|
||||
...options,
|
||||
}
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
<Suspense fallback={<ShimmerEffect height={height} />}>
|
||||
<LazyEditor
|
||||
{...props}
|
||||
height={height}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeEditor;
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--theme-bg);
|
||||
background-color: var(--theme-elevation-50);
|
||||
opacity: .85;
|
||||
z-index: -1;
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
&__bar {
|
||||
width: 2px;
|
||||
background-color: white;
|
||||
background-color: var(--theme-elevation-1000);
|
||||
height: 15px;
|
||||
|
||||
&:nth-child(1) {
|
||||
|
||||
@@ -19,7 +19,6 @@ export const Loading: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const baseClass = 'fullscreen-loader';
|
||||
|
||||
type Props = {
|
||||
show?: boolean;
|
||||
@@ -27,6 +26,7 @@ type Props = {
|
||||
overlayType?: string
|
||||
}
|
||||
export const FullscreenLoader: React.FC<Props> = ({ loadingText, show = true, overlayType }) => {
|
||||
const baseClass = 'fullscreen-loader';
|
||||
const { t } = useTranslation('general');
|
||||
|
||||
return (
|
||||
@@ -50,6 +50,7 @@ export const FullscreenLoader: React.FC<Props> = ({ loadingText, show = true, ov
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
type UseLoadingOverlayToggleT = {
|
||||
show: boolean;
|
||||
name: string;
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
.shimmer-effect {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: var(--theme-elevation-50);
|
||||
|
||||
&__shimmer {
|
||||
&__shine {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
scale: 2;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
transform: translateX(-100%);
|
||||
background: linear-gradient(130deg,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(#1A1A1A, .5) 35%,
|
||||
rgba(#252525, 1) 50%,
|
||||
rgba(#1A1A1A, .5) 65%,
|
||||
rgba(0, 0, 0, 0) 100%);
|
||||
animation: shimmer 3s infinite;
|
||||
animation: shimmer 2.5s infinite;
|
||||
opacity: .75;
|
||||
background: linear-gradient(100deg,
|
||||
var(--theme-elevation-50) 0%,
|
||||
var(--theme-elevation-50) 15%,
|
||||
var(--theme-elevation-100) 45%,
|
||||
var(--theme-elevation-100) 55%,
|
||||
var(--theme-elevation-50) 85%,
|
||||
var(--theme-elevation-50) 100%)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,63 @@
|
||||
import * as React from 'react';
|
||||
import { useDelay } from '../../../hooks/useDelay';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
export const ShimmerEffect: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
type ShimmerEffectT = {
|
||||
shimmerDelay?: number | string;
|
||||
height?: number | string;
|
||||
width?: number | string;
|
||||
}
|
||||
export const ShimmerEffect: React.FC<ShimmerEffectT> = ({ shimmerDelay = 0, height = '60px', width = '100%' }) => {
|
||||
return (
|
||||
<div className="shimmer-effect">
|
||||
{children}
|
||||
<div className="shimmer-effect__shimmer" />
|
||||
<div
|
||||
className="shimmer-effect"
|
||||
style={{
|
||||
height: typeof height === 'number' ? `${height}px` : height,
|
||||
width: typeof width === 'number' ? `${width}px` : width,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="shimmer-effect__shine"
|
||||
style={{
|
||||
animationDelay: `calc(${typeof shimmerDelay === 'number' ? `${shimmerDelay}ms` : shimmerDelay} + 500ms)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type StaggeredShimmersT = {
|
||||
count: number;
|
||||
shimmerDelay?: number | string;
|
||||
height?: number | string;
|
||||
width?: number | string;
|
||||
className?: string;
|
||||
shimmerItemClassName?: string;
|
||||
renderDelay?: number;
|
||||
}
|
||||
export const StaggeredShimmers: React.FC<StaggeredShimmersT> = ({ count, className, shimmerItemClassName, width, height, shimmerDelay = 25, renderDelay = 500 }) => {
|
||||
const shimmerDelayToPass = typeof shimmerDelay === 'number' ? `${shimmerDelay}ms` : shimmerDelay;
|
||||
const { hasDelayed } = useDelay(renderDelay, true);
|
||||
|
||||
if (!hasDelayed) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
>
|
||||
{[...Array(count)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={shimmerItemClassName}
|
||||
>
|
||||
<ShimmerEffect
|
||||
shimmerDelay={`calc(${i} * ${shimmerDelayToPass})`}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { code } from '../../../../../fields/validations';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
import Label from '../../Label';
|
||||
import { Props } from './types';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import CodeEditor from '../../../elements/CodeEditor';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const prismToMonacoLanguageMap = {
|
||||
js: 'javascript',
|
||||
ts: 'typescript',
|
||||
};
|
||||
|
||||
const baseClass = 'code-field';
|
||||
|
||||
const Code: React.FC<Props> = (props) => {
|
||||
const {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
validate = code,
|
||||
admin: {
|
||||
readOnly,
|
||||
style,
|
||||
className,
|
||||
width,
|
||||
language,
|
||||
description,
|
||||
condition,
|
||||
editorOptions,
|
||||
} = {},
|
||||
label,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value, options) => {
|
||||
return validate(value, { ...options, required });
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
'field-type',
|
||||
className,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<CodeEditor
|
||||
options={editorOptions}
|
||||
defaultLanguage={prismToMonacoLanguageMap[language] || language}
|
||||
value={value as string || ''}
|
||||
onChange={readOnly ? () => null : (val) => setValue(val)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<FieldDescription
|
||||
value={value}
|
||||
description={description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withCondition(Code);
|
||||
@@ -1,13 +1,98 @@
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import { Loading } from '../../../elements/Loading';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { code } from '../../../../../fields/validations';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
import Label from '../../Label';
|
||||
import { Props } from './types';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import { CodeEditor } from '../../../elements/CodeEditor';
|
||||
import { ShimmerEffect } from '../../../elements/ShimmerEffect';
|
||||
|
||||
const Code = lazy(() => import('./Code'));
|
||||
import './index.scss';
|
||||
|
||||
const CodeField: React.FC<Props> = (props) => (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Code {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
const prismToMonacoLanguageMap = {
|
||||
js: 'javascript',
|
||||
ts: 'typescript',
|
||||
};
|
||||
|
||||
export default CodeField;
|
||||
const baseClass = 'code-field';
|
||||
|
||||
const Code: React.FC<Props> = (props) => {
|
||||
const {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
validate = code,
|
||||
admin: {
|
||||
readOnly,
|
||||
style,
|
||||
className,
|
||||
width,
|
||||
language,
|
||||
description,
|
||||
condition,
|
||||
editorOptions,
|
||||
} = {},
|
||||
label,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const memoizedValidate = useCallback((value, options) => {
|
||||
return validate(value, { ...options, required });
|
||||
}, [validate, required]);
|
||||
|
||||
const {
|
||||
value,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
'field-type',
|
||||
className,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<CodeEditor
|
||||
options={editorOptions}
|
||||
defaultLanguage={prismToMonacoLanguageMap[language] || language}
|
||||
value={value as string || ''}
|
||||
onChange={readOnly ? () => null : (val) => setValue(val)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<FieldDescription
|
||||
value={value}
|
||||
description={description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withCondition(Code);
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
import { json } from '../../../../../fields/validations';
|
||||
import Label from '../../Label';
|
||||
import { Props } from './types';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import CodeEditor from '../../../elements/CodeEditor';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'json-field';
|
||||
|
||||
const JSONField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
validate = json,
|
||||
admin: {
|
||||
readOnly,
|
||||
style,
|
||||
className,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
editorOptions,
|
||||
} = {},
|
||||
label,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
const [stringValue, setStringValue] = useState<string>();
|
||||
const [jsonError, setJsonError] = useState<string>();
|
||||
|
||||
const memoizedValidate = useCallback((value, options) => {
|
||||
return validate(value, { ...options, required, jsonError });
|
||||
}, [validate, required, jsonError]);
|
||||
|
||||
const {
|
||||
value,
|
||||
initialValue,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useField<string>({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const handleChange = useCallback((val) => {
|
||||
if (readOnly) return;
|
||||
setStringValue(val);
|
||||
|
||||
try {
|
||||
setValue(JSON.parse(val.trim() || '{}'));
|
||||
setJsonError(undefined);
|
||||
} catch (e) {
|
||||
setJsonError(e);
|
||||
}
|
||||
}, [readOnly, setValue, setStringValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setStringValue(JSON.stringify(initialValue, null, 2));
|
||||
}, [initialValue]);
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
'field-type',
|
||||
className,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<CodeEditor
|
||||
options={editorOptions}
|
||||
defaultLanguage="json"
|
||||
value={stringValue}
|
||||
onChange={handleChange}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<FieldDescription
|
||||
value={value}
|
||||
description={description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withCondition(JSONField);
|
||||
@@ -1,13 +1,110 @@
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import { Loading } from '../../../elements/Loading';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
import { json } from '../../../../../fields/validations';
|
||||
import Label from '../../Label';
|
||||
import { Props } from './types';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import { CodeEditor } from '../../../elements/CodeEditor';
|
||||
|
||||
const JSON = lazy(() => import('./JSON'));
|
||||
import './index.scss';
|
||||
|
||||
const JSONField: React.FC<Props> = (props) => (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<JSON {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
const baseClass = 'json-field';
|
||||
|
||||
export default JSONField;
|
||||
const JSONField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
required,
|
||||
validate = json,
|
||||
admin: {
|
||||
readOnly,
|
||||
style,
|
||||
className,
|
||||
width,
|
||||
description,
|
||||
condition,
|
||||
editorOptions,
|
||||
} = {},
|
||||
label,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
const [stringValue, setStringValue] = useState<string>();
|
||||
const [jsonError, setJsonError] = useState<string>();
|
||||
|
||||
const memoizedValidate = useCallback((value, options) => {
|
||||
return validate(value, { ...options, required, jsonError });
|
||||
}, [validate, required, jsonError]);
|
||||
|
||||
const {
|
||||
value,
|
||||
initialValue,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useField<string>({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const handleChange = useCallback((val) => {
|
||||
if (readOnly) return;
|
||||
setStringValue(val);
|
||||
|
||||
try {
|
||||
setValue(JSON.parse(val.trim() || '{}'));
|
||||
setJsonError(undefined);
|
||||
} catch (e) {
|
||||
setJsonError(e);
|
||||
}
|
||||
}, [readOnly, setValue, setStringValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setStringValue(JSON.stringify(initialValue, null, 2));
|
||||
}, [initialValue]);
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
'field-type',
|
||||
className,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<CodeEditor
|
||||
options={editorOptions}
|
||||
defaultLanguage="json"
|
||||
value={stringValue}
|
||||
onChange={handleChange}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<FieldDescription
|
||||
value={value}
|
||||
description={description}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withCondition(JSONField);
|
||||
|
||||
@@ -20,11 +20,18 @@ export const LoadingOverlayProvider: React.FC<{ children?: React.ReactNode }> =
|
||||
|
||||
const [loadingOverlayText, setLoadingOverlayText] = useState<string>(t('loading'));
|
||||
const [overlays, dispatchOverlay] = React.useReducer(reducer, defaultLoadingOverlayState);
|
||||
const { isMounted, isUnmounting, triggerRenderTimeout: suspendDisplay } = useDelayedRender({ show: overlays.isLoading });
|
||||
|
||||
const {
|
||||
isMounted,
|
||||
isUnmounting,
|
||||
triggerDelayedRender,
|
||||
} = useDelayedRender({
|
||||
show: overlays.isLoading,
|
||||
});
|
||||
|
||||
const toggleLoadingOverlay = React.useCallback<ToggleLoadingOverlay>(({ type, key, isLoading }) => {
|
||||
if (isLoading) {
|
||||
suspendDisplay();
|
||||
triggerDelayedRender();
|
||||
dispatchOverlay({
|
||||
type: 'add',
|
||||
payload: {
|
||||
@@ -41,7 +48,7 @@ export const LoadingOverlayProvider: React.FC<{ children?: React.ReactNode }> =
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [suspendDisplay]);
|
||||
}, [triggerDelayedRender]);
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
|
||||
@@ -6,8 +6,7 @@ export const defaultLoadingOverlayState = {
|
||||
loaders: [],
|
||||
};
|
||||
|
||||
// react reducer return type
|
||||
export const reducer = (state: State, action: Action) => {
|
||||
export const reducer = (state: State, action: Action): State => {
|
||||
const loadersCopy = [...state.loaders];
|
||||
const { type = 'fullscreen', key = 'user' } = action.payload;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import PerPage from '../../../elements/PerPage';
|
||||
import { Gutter } from '../../../elements/Gutter';
|
||||
import { RelationshipProvider } from './RelationshipProvider';
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation';
|
||||
import { StaggeredShimmers } from '../../../elements/ShimmerEffect';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -96,6 +97,18 @@ const DefaultList: React.FC<Props> = (props) => {
|
||||
handleSortChange={handleSortChange}
|
||||
handleWhereChange={handleWhereChange}
|
||||
/>
|
||||
|
||||
{!data.docs && (
|
||||
<StaggeredShimmers
|
||||
className={[
|
||||
`${baseClass}__shimmer`,
|
||||
upload ? `${baseClass}__shimmer--uploads` : `${baseClass}__shimmer--rows`,
|
||||
].filter(Boolean).join(' ')}
|
||||
count={6}
|
||||
width={upload ? 'unset' : '100%'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(data.docs && data.docs.length > 0) && (
|
||||
<React.Fragment>
|
||||
{!upload && (
|
||||
|
||||
@@ -55,6 +55,34 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__shimmer {
|
||||
margin-top: base(1.75);
|
||||
}
|
||||
|
||||
&__shimmer--rows {
|
||||
>div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__shimmer--uploads {
|
||||
// match upload cards
|
||||
margin: base(2) -#{base(.5)};
|
||||
width: calc(100% + #{$baseline});
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
>div {
|
||||
min-width: 0;
|
||||
width: calc(16.66%);
|
||||
|
||||
>div {
|
||||
margin: base(.5);
|
||||
padding-bottom: 110%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
&__wrap {
|
||||
padding-top: 0;
|
||||
@@ -83,5 +111,19 @@
|
||||
width: 100%;
|
||||
margin-bottom: $baseline;
|
||||
}
|
||||
|
||||
&__shimmer--uploads {
|
||||
>div {
|
||||
width: 33.33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__shimmer--uploads {
|
||||
>div {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type useDelayedRenderT = (props: DelayedRenderProps) => {
|
||||
/** `true` if the component is unmounting. */
|
||||
isUnmounting: boolean;
|
||||
/** Call this function to trigger the timeout delay before rendering. */
|
||||
triggerRenderTimeout: () => void;
|
||||
triggerDelayedRender: () => void;
|
||||
};
|
||||
export const useDelayedRender: useDelayedRenderT = ({ show, delayBeforeShow = 1000, inTimeout = 500, minShowTime = 500, outTimeout = 500 }) => {
|
||||
const totalMountTime = inTimeout + minShowTime + outTimeout;
|
||||
@@ -57,6 +57,6 @@ export const useDelayedRender: useDelayedRenderT = ({ show, delayBeforeShow = 10
|
||||
return {
|
||||
isMounted,
|
||||
isUnmounting,
|
||||
triggerRenderTimeout: triggerDelay,
|
||||
triggerDelayedRender: triggerDelay,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user