Add custom spoiler component

This commit is contained in:
jeffvli 2024-02-02 01:38:58 -08:00
parent 7c25d12639
commit 095edfd49f
4 changed files with 78 additions and 37 deletions

View file

@ -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>
); );
}; };

View file

@ -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;
}

View file

@ -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';

View 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;
};