Add custom spoiler component
This commit is contained in:
parent
7c25d12639
commit
095edfd49f
4 changed files with 78 additions and 37 deletions
|
@ -1,41 +1,39 @@
|
||||||
import { ReactNode } from 'react';
|
import clsx from 'clsx';
|
||||||
import { Spoiler as MantineSpoiler } from '@mantine/core';
|
import { HTMLAttributes, ReactNode, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import styles from './spoiler.module.scss';
|
import styles from './spoiler.module.scss';
|
||||||
|
import { useIsOverflow } from '/@/renderer/hooks';
|
||||||
|
|
||||||
type SpoilerProps = {
|
interface SpoilerProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
children: ReactNode;
|
children?: ReactNode;
|
||||||
hideLabel?: boolean;
|
defaultOpened?: boolean;
|
||||||
initialState?: boolean;
|
maxHeight?: number;
|
||||||
maxHeight: number;
|
}
|
||||||
showLabel?: ReactNode;
|
|
||||||
transitionDuration?: number;
|
export const Spoiler = ({ maxHeight, defaultOpened, children, ...props }: SpoilerProps) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const isOverflow = useIsOverflow(ref);
|
||||||
|
const [isExpanded, setIsExpanded] = useState(!!defaultOpened);
|
||||||
|
|
||||||
|
const spoilerClassNames = clsx(styles.spoiler, {
|
||||||
|
[styles.canExpand]: isOverflow,
|
||||||
|
[styles.isExpanded]: isExpanded,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleToggleExpand = () => {
|
||||||
|
setIsExpanded((val) => !val);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Spoiler = ({
|
|
||||||
hideLabel,
|
|
||||||
initialState,
|
|
||||||
maxHeight,
|
|
||||||
showLabel,
|
|
||||||
transitionDuration,
|
|
||||||
children,
|
|
||||||
}: SpoilerProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MantineSpoiler
|
<div
|
||||||
classNames={{
|
ref={ref}
|
||||||
content: styles.content,
|
className={spoilerClassNames}
|
||||||
control: styles.control,
|
role="button"
|
||||||
root: styles.root,
|
style={{ maxHeight: maxHeight ?? '100px' }}
|
||||||
}}
|
tabIndex={-1}
|
||||||
hideLabel={hideLabel ?? t('common.collapse', { postProcess: 'sentenceCase' })}
|
onClick={handleToggleExpand}
|
||||||
initialState={initialState}
|
{...props}
|
||||||
maxHeight={maxHeight ?? 75}
|
|
||||||
showLabel={showLabel ?? t('common.expand', { postProcess: 'sentenceCase' })}
|
|
||||||
transitionDuration={transitionDuration}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</MantineSpoiler>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,31 @@
|
||||||
.control {
|
|
||||||
color: var(--btn-subtle-fg);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control:hover {
|
.control:hover {
|
||||||
color: var(--btn-subtle-fg-hover);
|
color: var(--btn-subtle-fg-hover);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spoiler {
|
||||||
|
position: relative;
|
||||||
|
text-align: justify;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spoiler:not(.is-expanded).can-expand:after {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
content: '';
|
||||||
|
background: linear-gradient(to top, var(--main-bg) 10%, transparent 60%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spoiler.can-expand {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spoiler.is-expanded {
|
||||||
|
max-height: 2500px !important;
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,4 @@ export * from './use-container-query';
|
||||||
export * from './use-fast-average-color';
|
export * from './use-fast-average-color';
|
||||||
export * from './use-hide-scrollbar';
|
export * from './use-hide-scrollbar';
|
||||||
export * from './use-app-focus';
|
export * from './use-app-focus';
|
||||||
|
export * from './use-is-overflow';
|
||||||
|
|
20
src/renderer/hooks/use-is-overflow.ts
Normal file
20
src/renderer/hooks/use-is-overflow.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { MutableRefObject, useState, useLayoutEffect } from 'react';
|
||||||
|
|
||||||
|
export const useIsOverflow = (ref: MutableRefObject<HTMLDivElement | null>) => {
|
||||||
|
const [isOverflow, setIsOverflow] = useState<Boolean | undefined>(undefined);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const { current } = ref;
|
||||||
|
|
||||||
|
const trigger = () => {
|
||||||
|
const hasOverflow = (current?.scrollHeight || 0) > (current?.clientHeight || 0);
|
||||||
|
setIsOverflow(hasOverflow);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
trigger();
|
||||||
|
}
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return isOverflow;
|
||||||
|
};
|
Reference in a new issue