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 { Spoiler as MantineSpoiler } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import clsx from 'clsx';
import { HTMLAttributes, ReactNode, useRef, useState } from 'react';
import styles from './spoiler.module.scss';
import { useIsOverflow } from '/@/renderer/hooks';
type SpoilerProps = {
children: ReactNode;
hideLabel?: boolean;
initialState?: boolean;
maxHeight: number;
showLabel?: ReactNode;
transitionDuration?: number;
};
interface SpoilerProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
defaultOpened?: boolean;
maxHeight?: number;
}
export const Spoiler = ({
hideLabel,
initialState,
maxHeight,
showLabel,
transitionDuration,
children,
}: SpoilerProps) => {
const { t } = useTranslation();
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);
};
return (
<MantineSpoiler
classNames={{
content: styles.content,
control: styles.control,
root: styles.root,
}}
hideLabel={hideLabel ?? t('common.collapse', { postProcess: 'sentenceCase' })}
initialState={initialState}
maxHeight={maxHeight ?? 75}
showLabel={showLabel ?? t('common.expand', { postProcess: 'sentenceCase' })}
transitionDuration={transitionDuration}
<div
ref={ref}
className={spoilerClassNames}
role="button"
style={{ maxHeight: maxHeight ?? '100px' }}
tabIndex={-1}
onClick={handleToggleExpand}
{...props}
>
{children}
</MantineSpoiler>
</div>
);
};

View file

@ -1,9 +1,31 @@
.control {
color: var(--btn-subtle-fg);
font-weight: 600;
}
.control:hover {
color: var(--btn-subtle-fg-hover);
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-hide-scrollbar';
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;
};