mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): style settings modal
This commit is contained in:
parent
fcf2006502
commit
bbca053b48
@ -16,6 +16,7 @@ export const baseStyle = definePartsStyle(() => ({
|
|||||||
header: {
|
header: {
|
||||||
fontWeight: 'semibold',
|
fontWeight: 'semibold',
|
||||||
fontSize: 'lg',
|
fontSize: 'lg',
|
||||||
|
color: 'base.300'
|
||||||
},
|
},
|
||||||
closeButton: {
|
closeButton: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { InvHeading } from 'common/components/InvHeading/wrapper';
|
|
||||||
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
|
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
|
||||||
import { InvInput } from 'common/components/InvInput/InvInput';
|
import { InvInput } from 'common/components/InvInput/InvInput';
|
||||||
import {
|
import {
|
||||||
@ -22,6 +21,7 @@ import {
|
|||||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||||
import type { HotkeyGroup } from 'features/system/components/HotkeysModal/useHotkeyData';
|
import type { HotkeyGroup } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||||
import { useHotkeyData } from 'features/system/components/HotkeysModal/useHotkeyData';
|
import { useHotkeyData } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||||
|
import { StickyScrollable } from 'features/system/components/StickyScrollable';
|
||||||
import type { ChangeEventHandler, ReactElement } from 'react';
|
import type { ChangeEventHandler, ReactElement } from 'react';
|
||||||
import {
|
import {
|
||||||
cloneElement,
|
cloneElement,
|
||||||
@ -121,26 +121,9 @@ const HotkeysModal = ({ children }: HotkeysModalProps) => {
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
|
||||||
<ScrollableContent>
|
<ScrollableContent>
|
||||||
|
<Flex flexDir="column" gap={4}>
|
||||||
{filteredHotkeyGroups.map((group) => (
|
{filteredHotkeyGroups.map((group) => (
|
||||||
<Flex key={group.title} pb={4} flexDir="column">
|
<StickyScrollable key={group.title} title={group.title}>
|
||||||
<Flex
|
|
||||||
ps={2}
|
|
||||||
pb={4}
|
|
||||||
position="sticky"
|
|
||||||
zIndex={1}
|
|
||||||
top={0}
|
|
||||||
bg="base.800"
|
|
||||||
>
|
|
||||||
<InvHeading size="sm">{group.title}</InvHeading>
|
|
||||||
</Flex>
|
|
||||||
<Flex
|
|
||||||
key={group.title}
|
|
||||||
p={4}
|
|
||||||
borderRadius="base"
|
|
||||||
bg="base.750"
|
|
||||||
flexDir="column"
|
|
||||||
gap={4}
|
|
||||||
>
|
|
||||||
{group.hotkeyListItems.map((hotkey, i) => (
|
{group.hotkeyListItems.map((hotkey, i) => (
|
||||||
<Fragment key={i}>
|
<Fragment key={i}>
|
||||||
<HotkeyListItem
|
<HotkeyListItem
|
||||||
@ -151,8 +134,7 @@ const HotkeysModal = ({ children }: HotkeysModalProps) => {
|
|||||||
{i < group.hotkeyListItems.length - 1 && <Divider />}
|
{i < group.hotkeyListItems.length - 1 && <Divider />}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</StickyScrollable>
|
||||||
</Flex>
|
|
||||||
))}
|
))}
|
||||||
{!filteredHotkeyGroups.length && (
|
{!filteredHotkeyGroups.length && (
|
||||||
<IAINoContentFallback
|
<IAINoContentFallback
|
||||||
@ -160,6 +142,7 @@ const HotkeysModal = ({ children }: HotkeysModalProps) => {
|
|||||||
icon={null}
|
icon={null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Flex>
|
||||||
</ScrollableContent>
|
</ScrollableContent>
|
||||||
</InvModalBody>
|
</InvModalBody>
|
||||||
<InvModalFooter />
|
<InvModalFooter />
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
import { Heading } from '@chakra-ui/react';
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import { InvButton } from 'common/components/InvButton/InvButton';
|
|
||||||
import { InvText } from 'common/components/InvText/wrapper';
|
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
useClearIntermediatesMutation,
|
|
||||||
useGetIntermediatesCountQuery,
|
|
||||||
} from 'services/api/endpoints/images';
|
|
||||||
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
|
||||||
|
|
||||||
import StyledFlex from './StyledFlex';
|
|
||||||
|
|
||||||
const SettingsClearIntermediates = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const { data: intermediatesCount } = useGetIntermediatesCountQuery(
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
refetchOnMountOrArgChange: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const [clearIntermediates, { isLoading: isLoadingClearIntermediates }] =
|
|
||||||
useClearIntermediatesMutation();
|
|
||||||
|
|
||||||
const { data: queueStatus } = useGetQueueStatusQuery();
|
|
||||||
const hasPendingItems =
|
|
||||||
queueStatus &&
|
|
||||||
(queueStatus.queue.in_progress > 0 || queueStatus.queue.pending > 0);
|
|
||||||
|
|
||||||
const handleClickClearIntermediates = useCallback(() => {
|
|
||||||
if (hasPendingItems) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearIntermediates()
|
|
||||||
.unwrap()
|
|
||||||
.then((clearedCount) => {
|
|
||||||
dispatch(controlAdaptersReset());
|
|
||||||
dispatch(resetCanvas());
|
|
||||||
dispatch(
|
|
||||||
addToast({
|
|
||||||
title: t('settings.intermediatesCleared', { count: clearedCount }),
|
|
||||||
status: 'info',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
dispatch(
|
|
||||||
addToast({
|
|
||||||
title: t('settings.intermediatesClearedFailed'),
|
|
||||||
status: 'error',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [t, clearIntermediates, dispatch, hasPendingItems]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledFlex>
|
|
||||||
<Heading size="sm">{t('settings.clearIntermediates')}</Heading>
|
|
||||||
<InvButton
|
|
||||||
tooltip={
|
|
||||||
hasPendingItems ? t('settings.clearIntermediatesDisabled') : undefined
|
|
||||||
}
|
|
||||||
colorScheme="warning"
|
|
||||||
onClick={handleClickClearIntermediates}
|
|
||||||
isLoading={isLoadingClearIntermediates}
|
|
||||||
isDisabled={!intermediatesCount || hasPendingItems}
|
|
||||||
>
|
|
||||||
{t('settings.clearIntermediatesWithCount', {
|
|
||||||
count: intermediatesCount ?? 0,
|
|
||||||
})}
|
|
||||||
</InvButton>
|
|
||||||
<InvText fontWeight="bold">
|
|
||||||
{t('settings.clearIntermediatesDesc1')}
|
|
||||||
</InvText>
|
|
||||||
<InvText variant="subtext">
|
|
||||||
{t('settings.clearIntermediatesDesc2')}
|
|
||||||
</InvText>
|
|
||||||
<InvText variant="subtext">
|
|
||||||
{t('settings.clearIntermediatesDesc3')}
|
|
||||||
</InvText>
|
|
||||||
</StyledFlex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(SettingsClearIntermediates);
|
|
@ -4,7 +4,6 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InvButton } from 'common/components/InvButton/InvButton';
|
import { InvButton } from 'common/components/InvButton/InvButton';
|
||||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||||
import { InvHeading } from 'common/components/InvHeading/wrapper';
|
|
||||||
import {
|
import {
|
||||||
InvModal,
|
InvModal,
|
||||||
InvModalBody,
|
InvModalBody,
|
||||||
@ -16,8 +15,11 @@ import {
|
|||||||
} from 'common/components/InvModal/wrapper';
|
} from 'common/components/InvModal/wrapper';
|
||||||
import { InvSwitch } from 'common/components/InvSwitch/wrapper';
|
import { InvSwitch } from 'common/components/InvSwitch/wrapper';
|
||||||
import { InvText } from 'common/components/InvText/wrapper';
|
import { InvText } from 'common/components/InvText/wrapper';
|
||||||
|
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||||
import { useClearStorage } from 'common/hooks/useClearStorage';
|
import { useClearStorage } from 'common/hooks/useClearStorage';
|
||||||
import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice';
|
import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice';
|
||||||
|
import { useClearIntermediates } from 'features/system/components/SettingsModal/useClearIntermediates';
|
||||||
|
import { StickyScrollable } from 'features/system/components/StickyScrollable';
|
||||||
import {
|
import {
|
||||||
setEnableImageDebugging,
|
setEnableImageDebugging,
|
||||||
setShouldConfirmOnDelete,
|
setShouldConfirmOnDelete,
|
||||||
@ -33,10 +35,8 @@ import { cloneElement, memo, useCallback, useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
|
import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
|
||||||
|
|
||||||
import SettingsClearIntermediates from './SettingsClearIntermediates';
|
|
||||||
import { SettingsLanguageSelect } from './SettingsLanguageSelect';
|
import { SettingsLanguageSelect } from './SettingsLanguageSelect';
|
||||||
import { SettingsLogLevelSelect } from './SettingsLogLevelSelect';
|
import { SettingsLogLevelSelect } from './SettingsLogLevelSelect';
|
||||||
import StyledFlex from './StyledFlex';
|
|
||||||
|
|
||||||
const selector = createMemoizedSelector(
|
const selector = createMemoizedSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -110,6 +110,13 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
clearIntermediates,
|
||||||
|
hasPendingItems,
|
||||||
|
intermediatesCount,
|
||||||
|
isLoading: isLoadingClearIntermediates,
|
||||||
|
} = useClearIntermediates();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isSettingsModalOpen,
|
isOpen: isSettingsModalOpen,
|
||||||
onOpen: onSettingsModalOpen,
|
onOpen: onSettingsModalOpen,
|
||||||
@ -218,23 +225,22 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
isCentered
|
isCentered
|
||||||
>
|
>
|
||||||
<InvModalOverlay />
|
<InvModalOverlay />
|
||||||
<InvModalContent>
|
<InvModalContent maxH="80vh" h="80vh">
|
||||||
<InvModalHeader bg="none">{t('common.settingsLabel')}</InvModalHeader>
|
<InvModalHeader bg="none">{t('common.settingsLabel')}</InvModalHeader>
|
||||||
<InvModalCloseButton />
|
<InvModalCloseButton />
|
||||||
<InvModalBody>
|
<InvModalBody display="flex" flexDir="column" gap={4}>
|
||||||
<Flex gap={4} flexDir="column">
|
<ScrollableContent>
|
||||||
<StyledFlex>
|
<Flex flexDir="column" gap={4}>
|
||||||
<InvHeading size="sm">{t('settings.general')}</InvHeading>
|
<StickyScrollable title={t('settings.general')}>
|
||||||
<InvControl label={t('settings.confirmOnDelete')}>
|
<InvControl label={t('settings.confirmOnDelete')}>
|
||||||
<InvSwitch
|
<InvSwitch
|
||||||
isChecked={shouldConfirmOnDelete}
|
isChecked={shouldConfirmOnDelete}
|
||||||
onChange={handleChangeShouldConfirmOnDelete}
|
onChange={handleChangeShouldConfirmOnDelete}
|
||||||
/>
|
/>
|
||||||
</InvControl>
|
</InvControl>
|
||||||
</StyledFlex>
|
</StickyScrollable>
|
||||||
|
|
||||||
<StyledFlex>
|
<StickyScrollable title={t('settings.generation')}>
|
||||||
<InvHeading size="sm">{t('settings.generation')}</InvHeading>
|
|
||||||
<InvControl
|
<InvControl
|
||||||
label={t('settings.enableNSFWChecker')}
|
label={t('settings.enableNSFWChecker')}
|
||||||
isDisabled={!isNSFWCheckerAvailable}
|
isDisabled={!isNSFWCheckerAvailable}
|
||||||
@ -253,10 +259,9 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
onChange={handleChangeShouldUseWatermarker}
|
onChange={handleChangeShouldUseWatermarker}
|
||||||
/>
|
/>
|
||||||
</InvControl>
|
</InvControl>
|
||||||
</StyledFlex>
|
</StickyScrollable>
|
||||||
|
|
||||||
<StyledFlex>
|
<StickyScrollable title={t('settings.ui')}>
|
||||||
<InvHeading size="sm">{t('settings.ui')}</InvHeading>
|
|
||||||
<InvControl label={t('settings.showProgressInViewer')}>
|
<InvControl label={t('settings.showProgressInViewer')}>
|
||||||
<InvSwitch
|
<InvSwitch
|
||||||
isChecked={shouldShowProgressInViewer}
|
isChecked={shouldShowProgressInViewer}
|
||||||
@ -285,11 +290,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
onChange={handleChangeShouldEnableInformationalPopovers}
|
onChange={handleChangeShouldEnableInformationalPopovers}
|
||||||
/>
|
/>
|
||||||
</InvControl>
|
</InvControl>
|
||||||
</StyledFlex>
|
</StickyScrollable>
|
||||||
|
|
||||||
{shouldShowDeveloperSettings && (
|
{shouldShowDeveloperSettings && (
|
||||||
<StyledFlex>
|
<StickyScrollable title={t('settings.developer')}>
|
||||||
<InvHeading size="sm">{t('settings.developer')}</InvHeading>
|
|
||||||
<InvControl label={t('settings.shouldLogToConsole')}>
|
<InvControl label={t('settings.shouldLogToConsole')}>
|
||||||
<InvSwitch
|
<InvSwitch
|
||||||
isChecked={shouldLogToConsole}
|
isChecked={shouldLogToConsole}
|
||||||
@ -303,14 +307,43 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
onChange={handleChangeEnableImageDebugging}
|
onChange={handleChangeEnableImageDebugging}
|
||||||
/>
|
/>
|
||||||
</InvControl>
|
</InvControl>
|
||||||
</StyledFlex>
|
</StickyScrollable>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldShowClearIntermediates && <SettingsClearIntermediates />}
|
{shouldShowClearIntermediates && (
|
||||||
|
<StickyScrollable title={t('settings.clearIntermediates')}>
|
||||||
|
<InvButton
|
||||||
|
tooltip={
|
||||||
|
hasPendingItems
|
||||||
|
? t('settings.clearIntermediatesDisabled')
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
colorScheme="warning"
|
||||||
|
onClick={clearIntermediates}
|
||||||
|
isLoading={isLoadingClearIntermediates}
|
||||||
|
isDisabled={!intermediatesCount || hasPendingItems}
|
||||||
|
>
|
||||||
|
{t('settings.clearIntermediatesWithCount', {
|
||||||
|
count: intermediatesCount ?? 0,
|
||||||
|
})}
|
||||||
|
</InvButton>
|
||||||
|
<InvText fontWeight="bold">
|
||||||
|
{t('settings.clearIntermediatesDesc1')}
|
||||||
|
</InvText>
|
||||||
|
<InvText variant="subtext">
|
||||||
|
{t('settings.clearIntermediatesDesc2')}
|
||||||
|
</InvText>
|
||||||
|
<InvText variant="subtext">
|
||||||
|
{t('settings.clearIntermediatesDesc3')}
|
||||||
|
</InvText>
|
||||||
|
</StickyScrollable>
|
||||||
|
)}
|
||||||
|
|
||||||
<StyledFlex>
|
<StickyScrollable title={t('settings.resetWebUI')}>
|
||||||
<InvHeading size="sm">{t('settings.resetWebUI')}</InvHeading>
|
<InvButton
|
||||||
<InvButton colorScheme="error" onClick={handleClickResetWebUI}>
|
colorScheme="error"
|
||||||
|
onClick={handleClickResetWebUI}
|
||||||
|
>
|
||||||
{t('settings.resetWebUI')}
|
{t('settings.resetWebUI')}
|
||||||
</InvButton>
|
</InvButton>
|
||||||
{shouldShowResetWebUiText && (
|
{shouldShowResetWebUiText && (
|
||||||
@ -323,15 +356,12 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
</InvText>
|
</InvText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</StyledFlex>
|
</StickyScrollable>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</ScrollableContent>
|
||||||
</InvModalBody>
|
</InvModalBody>
|
||||||
|
|
||||||
<InvModalFooter>
|
<InvModalFooter />
|
||||||
<InvButton onClick={onSettingsModalClose}>
|
|
||||||
{t('common.close')}
|
|
||||||
</InvButton>
|
|
||||||
</InvModalFooter>
|
|
||||||
</InvModalContent>
|
</InvModalContent>
|
||||||
</InvModal>
|
</InvModal>
|
||||||
|
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
useClearIntermediatesMutation,
|
||||||
|
useGetIntermediatesCountQuery,
|
||||||
|
} from 'services/api/endpoints/images';
|
||||||
|
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||||
|
|
||||||
|
export type UseClearIntermediatesReturn = {
|
||||||
|
intermediatesCount: number | undefined;
|
||||||
|
clearIntermediates: () => void;
|
||||||
|
isLoading: boolean;
|
||||||
|
hasPendingItems: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useClearIntermediates = (): UseClearIntermediatesReturn => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { data: intermediatesCount } = useGetIntermediatesCountQuery(
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
refetchOnMountOrArgChange: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [_clearIntermediates, { isLoading }] = useClearIntermediatesMutation();
|
||||||
|
|
||||||
|
const { data: queueStatus } = useGetQueueStatusQuery();
|
||||||
|
const hasPendingItems = useMemo(
|
||||||
|
() =>
|
||||||
|
Boolean(
|
||||||
|
queueStatus &&
|
||||||
|
(queueStatus.queue.in_progress > 0 || queueStatus.queue.pending > 0)
|
||||||
|
),
|
||||||
|
[queueStatus]
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearIntermediates = useCallback(() => {
|
||||||
|
if (hasPendingItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearIntermediates()
|
||||||
|
.unwrap()
|
||||||
|
.then((clearedCount) => {
|
||||||
|
dispatch(controlAdaptersReset());
|
||||||
|
dispatch(resetCanvas());
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: t('settings.intermediatesCleared', { count: clearedCount }),
|
||||||
|
status: 'info',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: t('settings.intermediatesClearedFailed'),
|
||||||
|
status: 'error',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [t, _clearIntermediates, dispatch, hasPendingItems]);
|
||||||
|
|
||||||
|
return { intermediatesCount, clearIntermediates, isLoading, hasPendingItems };
|
||||||
|
};
|
@ -0,0 +1,49 @@
|
|||||||
|
import { Flex } from '@chakra-ui/layout';
|
||||||
|
import { InvHeading } from 'common/components/InvHeading/wrapper';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
export type StickyScrollableHeadingProps = {
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StickyScrollableHeading = memo(
|
||||||
|
(props: StickyScrollableHeadingProps) => {
|
||||||
|
return (
|
||||||
|
<Flex ps={2} pb={4} position="sticky" zIndex={1} top={0} bg="base.800">
|
||||||
|
<InvHeading size="sm">{props.title}</InvHeading>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
StickyScrollableHeading.displayName = 'StickyScrollableHeading';
|
||||||
|
|
||||||
|
export type StickyScrollableContentProps = PropsWithChildren;
|
||||||
|
|
||||||
|
export const StickyScrollableContent = memo(
|
||||||
|
(props: StickyScrollableContentProps) => {
|
||||||
|
return (
|
||||||
|
<Flex p={4} borderRadius="base" bg="base.750" flexDir="column" gap={4}>
|
||||||
|
{props.children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
StickyScrollableContent.displayName = 'StickyScrollableContent';
|
||||||
|
|
||||||
|
export type StickyScrollableProps = PropsWithChildren<{
|
||||||
|
title: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const StickyScrollable = memo((props: StickyScrollableProps) => {
|
||||||
|
return (
|
||||||
|
<Flex key={props.title} flexDir="column">
|
||||||
|
<StickyScrollableHeading title={props.title} />
|
||||||
|
<StickyScrollableContent>{props.children}</StickyScrollableContent>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
StickyScrollable.displayName = 'StickyScrollable';
|
Loading…
Reference in New Issue
Block a user