Black mode

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-03-24 17:38:34 +01:00
parent c24fc10c93
commit 067fc9e45d
44 changed files with 77 additions and 63 deletions

View file

@ -293,7 +293,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
data-testid='dropdown-menu' data-testid='dropdown-menu'
ref={refs.setFloating} ref={refs.setFloating}
className={ className={
clsx('z-[1001] w-56 rounded-md bg-white py-1 shadow-lg transition-opacity duration-100 focus:outline-none dark:bg-gray-900 dark:ring-2 dark:ring-primary-700', { clsx('z-[1001] w-56 rounded-md bg-white py-1 shadow-lg transition-opacity duration-100 focus:outline-none black:bg-black dark:bg-gray-900 dark:ring-2 dark:ring-primary-700', {
'opacity-0 pointer-events-none': !isOpen, 'opacity-0 pointer-events-none': !isOpen,
}) })
} }
@ -318,7 +318,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
<div <div
ref={arrowRef} ref={arrowRef}
style={arrowProps} style={arrowProps}
className='pointer-events-none absolute z-[-1] h-3 w-3 bg-white dark:bg-gray-900' className='pointer-events-none absolute z-[-1] h-3 w-3 bg-white black:bg-black dark:bg-gray-900'
/> />
</div> </div>
</Portal> </Portal>

View file

@ -51,7 +51,7 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
)); ));
return ( return (
<div className={clsx('relative w-full overflow-hidden rounded-lg bg-gray-100 dark:bg-primary-800', className)}> <div className={clsx('relative w-full overflow-hidden rounded-lg bg-gray-100 black:border black:border-gray-800 black:bg-black dark:bg-primary-800', className)}>
<div className='absolute right-3 top-28'> <div className='absolute right-3 top-28'>
{floatingAction && action} {floatingAction && action}
</div> </div>

View file

@ -17,7 +17,7 @@ interface IGroupCard {
const GroupCard: React.FC<IGroupCard> = ({ group }) => { const GroupCard: React.FC<IGroupCard> = ({ group }) => {
return ( return (
<Stack <Stack
className='relative h-[240px] rounded-lg border border-solid border-gray-300 bg-white dark:border-primary-800 dark:bg-primary-900' className='relative h-[240px] rounded-lg border border-solid border-gray-300 bg-white black:bg-white dark:border-primary-800 dark:bg-primary-900'
data-testid='group-card' data-testid='group-card'
> >
{/* Group Cover Image */} {/* Group Cover Image */}

View file

@ -45,7 +45,7 @@ const GroupPopover = (props: IGroupPopoverContainer) => {
content={ content={
<Stack space={4} className='w-80 pb-4'> <Stack space={4} className='w-80 pb-4'>
<Stack <Stack
className='relative h-60 rounded-lg bg-white dark:border-primary-800 dark:bg-primary-900' className='relative h-60 rounded-lg bg-white black:bg-white dark:border-primary-800 dark:bg-primary-900'
data-testid='group-card' data-testid='group-card'
> >
{/* Group Cover Image */} {/* Group Cover Image */}

View file

@ -2,7 +2,7 @@ import React from 'react';
/** Fullscreen gradient used as a backdrop to public pages. */ /** Fullscreen gradient used as a backdrop to public pages. */
const LandingGradient: React.FC = () => ( const LandingGradient: React.FC = () => (
<div className='fixed h-screen w-full bg-gradient-to-tr from-primary-50 via-white to-gradient-end/10 dark:from-primary-900/50 dark:via-primary-900 dark:to-primary-800/50' /> <div className='fixed h-screen w-full bg-gradient-to-tr from-primary-50 via-white to-gradient-end/10 black:hidden dark:from-primary-900/50 dark:via-primary-900 dark:to-primary-800/50' />
); );
export default LandingGradient; export default LandingGradient;

View file

@ -232,7 +232,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
<div <div
role='presentation' role='presentation'
id='modal-overlay' id='modal-overlay'
className='fixed inset-0 bg-gray-500/90 backdrop-blur dark:bg-gray-700/90' className='fixed inset-0 bg-gray-500/90 backdrop-blur black:bg-gray-900/90 dark:bg-gray-700/90'
onClick={handleOnClose} onClick={handleOnClose}
/> />

View file

@ -144,7 +144,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
} }
> >
<div <div
className='fixed inset-0 bg-gray-500/90 dark:bg-gray-700/90' className='fixed inset-0 bg-gray-500/90 black:bg-gray-900/90 dark:bg-gray-700/90'
role='button' role='button'
onClick={handleClose} onClick={handleClose}
/> />
@ -153,7 +153,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<div <div
className={ className={
clsx({ clsx({
'flex flex-col flex-1 bg-white dark:bg-primary-900 -translate-x-full rtl:translate-x-full w-full max-w-xs': true, 'flex flex-col flex-1 bg-white black:bg-black dark:bg-primary-900 -translate-x-full rtl:translate-x-full w-full max-w-xs': true,
'!translate-x-0': sidebarOpen, '!translate-x-0': sidebarOpen,
}) })
} }
@ -350,7 +350,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
</button> </button>
{switcher && ( {switcher && (
<div className='border-t-2 border-solid border-gray-100 dark:border-gray-800'> <div className='border-t-2 border-solid border-gray-100 black:border-t dark:border-gray-800'>
{otherAccounts.map(account => renderAccount(account))} {otherAccounts.map(account => renderAccount(account))}
<NavLink className='flex items-center space-x-1 py-2' to='/login/add' onClick={handleClose}> <NavLink className='flex items-center space-x-1 py-2' to='/login/add' onClick={handleClose}>

View file

@ -49,7 +49,7 @@ const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, r
count={count} count={count}
countMax={countMax} countMax={countMax}
className={clsx('h-5 w-5', { className={clsx('h-5 w-5', {
'text-gray-600 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400': !isActive, 'text-gray-600 black:text-white dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400': !isActive,
'text-primary-500 dark:text-primary-400': isActive, 'text-primary-500 dark:text-primary-400': isActive,
})} })}
/> />

View file

@ -15,7 +15,7 @@ const SiteLogo: React.FC<ISiteLogo> = ({ className, theme, ...rest }) => {
const { logo, logoDarkMode } = useSoapboxConfig(); const { logo, logoDarkMode } = useSoapboxConfig();
const { demo } = useSettings(); const { demo } = useSettings();
let darkMode = useTheme() === 'dark'; let darkMode = ['dark', 'black'].includes(useTheme());
if (theme === 'dark') darkMode = true; if (theme === 'dark') darkMode = true;
/** Soapbox logo. */ /** Soapbox logo. */

View file

@ -232,7 +232,7 @@ const StatusList: React.FC<IStatusList> = ({
placeholderCount={20} placeholderCount={20}
ref={node} ref={node}
className={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', { className={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', {
'divide-none': divideType !== 'border', 'divide-none black:divide-solid': divideType !== 'border',
})} })}
itemClassName={clsx({ itemClassName={clsx({
'pb-3': divideType !== 'border', 'pb-3': divideType !== 'border',

View file

@ -35,7 +35,7 @@ const ThumbNavigationLink: React.FC<IThumbNavigationLink> = ({ count, countMax,
src={src} src={src}
className={clsx({ className={clsx({
'h-5 w-5': true, 'h-5 w-5': true,
'text-gray-600': !active, 'text-gray-600 black:text-white': !active,
'text-primary-500': active, 'text-primary-500': active,
})} })}
count={count} count={count}
@ -46,7 +46,7 @@ const ThumbNavigationLink: React.FC<IThumbNavigationLink> = ({ count, countMax,
src={src} src={src}
className={clsx({ className={clsx({
'h-5 w-5': true, 'h-5 w-5': true,
'text-gray-600': !active, 'text-gray-600 black:text-white': !active,
'text-primary-500': active, 'text-primary-500': active,
})} })}
/> />

View file

@ -38,9 +38,10 @@ const Card = React.forwardRef<HTMLDivElement, ICard>(({ children, variant = 'def
ref={ref} ref={ref}
{...filteredProps} {...filteredProps}
className={clsx({ className={clsx({
'bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none': variant === 'rounded', 'bg-white dark:bg-primary-900 black:bg-black text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none': variant === 'rounded',
[sizes[size]]: variant === 'rounded', [sizes[size]]: variant === 'rounded',
'py-4': variant === 'slim', 'py-4': variant === 'slim',
'black:rounded-none': size !== 'xl',
}, className)} }, className)}
> >
{children} {children}

View file

@ -102,8 +102,8 @@ const Column: React.FC<IColumn> = React.forwardRef((props, ref: React.ForwardedR
backHref={backHref} backHref={backHref}
className={clsx({ className={clsx({
'rounded-t-3xl': !isScrolled && !transparent, 'rounded-t-3xl': !isScrolled && !transparent,
'sticky top-12 z-10 bg-white/90 dark:bg-primary-900/90 backdrop-blur lg:top-16': !transparent, 'sticky top-12 z-10 bg-white/90 dark:bg-primary-900/90 black:bg-black/90 backdrop-blur lg:top-16': !transparent,
'p-4 sm:p-0 sm:pb-4': transparent, 'p-4 sm:p-0 sm:pb-4 black:p-4': transparent,
'-mt-4 -mx-4 p-4': size !== 'lg' && !transparent, '-mt-4 -mx-4 p-4': size !== 'lg' && !transparent,
'-mt-4 -mx-4 p-4 sm:-mt-6 sm:-mx-6 sm:p-6': size === 'lg' && !transparent, '-mt-4 -mx-4 p-4 sm:-mt-6 sm:-mx-6 sm:p-6': size === 'lg' && !transparent,
})} })}

View file

@ -13,7 +13,7 @@ interface IDivider {
const Divider = ({ text, textSize = 'md' }: IDivider) => ( const Divider = ({ text, textSize = 'md' }: IDivider) => (
<div className='relative' data-testid='divider'> <div className='relative' data-testid='divider'>
<div className='absolute inset-0 flex items-center' aria-hidden='true'> <div className='absolute inset-0 flex items-center' aria-hidden='true'>
<div className='w-full border-t-2 border-solid border-gray-100 dark:border-gray-800' /> <div className='w-full border-t-2 border-solid border-gray-100 black:border-t dark:border-gray-800' />
</div> </div>
{text && ( {text && (

View file

@ -21,7 +21,7 @@ interface LayoutComponent extends React.FC<ILayout> {
/** Layout container, to hold Sidebar, Main, and Aside. */ /** Layout container, to hold Sidebar, Main, and Aside. */
const Layout: LayoutComponent = ({ children }) => ( const Layout: LayoutComponent = ({ children }) => (
<div className='relative sm:pt-4'> <div className='relative grow black:pt-0 sm:pt-4'>
<div className='mx-auto max-w-3xl sm:px-6 md:grid md:max-w-7xl md:grid-cols-12 md:gap-8 md:px-8'> <div className='mx-auto max-w-3xl sm:px-6 md:grid md:max-w-7xl md:grid-cols-12 md:gap-8 md:px-8'>
{children} {children}
</div> </div>
@ -41,7 +41,7 @@ const Sidebar: React.FC<ISidebar> = ({ children }) => (
const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, className }) => ( const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, className }) => (
<main <main
className={clsx({ className={clsx({
'md:col-span-12 lg:col-span-9 xl:col-span-6 pb-36': true, 'md:col-span-12 lg:col-span-9 xl:col-span-6 pb-36 black:border-gray-800 lg:black:border-l xl:black:border-r': true,
}, className)} }, className)}
> >
{children} {children}

View file

@ -28,7 +28,7 @@ const MenuList: React.FC<IMenuList> = (props) => {
<MenuItems <MenuItems
onKeyDown={(event) => event.nativeEvent.stopImmediatePropagation()} onKeyDown={(event) => event.nativeEvent.stopImmediatePropagation()}
className={ className={
clsx(className, 'shadow-menu rounded-lg bg-white py-1 dark:bg-primary-900') clsx(className, 'shadow-menu rounded-lg bg-white py-1 black:bg-black dark:bg-primary-900')
} }
{...filteredProps} {...filteredProps}
/> />
@ -37,6 +37,6 @@ const MenuList: React.FC<IMenuList> = (props) => {
}; };
/** Divides menu items. */ /** Divides menu items. */
const MenuDivider = () => <hr className='mx-2 my-1 border-t-2 border-gray-100 dark:border-gray-800' />; const MenuDivider = () => <hr className='mx-2 my-1 border-t-2 border-gray-100 black:border-t dark:border-gray-800' />;
export { Menu, MenuButton, MenuDivider, MenuItems, MenuItem, MenuList, MenuLink }; export { Menu, MenuButton, MenuDivider, MenuItems, MenuItem, MenuList, MenuLink };

View file

@ -95,7 +95,7 @@ const Modal = React.forwardRef<HTMLDivElement, IModal>(({
<div <div
ref={ref} ref={ref}
data-testid='modal' data-testid='modal'
className={clsx(className, 'pointer-events-auto mx-auto block w-full rounded-2xl bg-white p-6 text-start align-middle text-gray-900 shadow-xl transition-all dark:bg-primary-900 dark:text-gray-100', widths[width])} className={clsx(className, 'pointer-events-auto mx-auto block w-full rounded-2xl bg-white p-6 text-start align-middle text-gray-900 shadow-xl transition-all black:bg-black dark:bg-primary-900 dark:text-gray-100', widths[width])}
> >
<div className='w-full justify-between sm:flex sm:items-start'> <div className='w-full justify-between sm:flex sm:items-start'>
<div className='w-full'> <div className='w-full'>

View file

@ -106,7 +106,7 @@ const Toast = (props: IToast) => {
data-testid='toast' data-testid='toast'
className={ className={
clsx({ clsx({
'p-4 pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white dark:bg-gray-900 shadow-lg dark:ring-2 dark:ring-gray-800': true, 'p-4 pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white black:bg-black dark:bg-gray-900 shadow-lg dark:ring-2 dark:ring-gray-800': true,
'animate-enter': t.visible, 'animate-enter': t.visible,
'animate-leave': !t.visible, 'animate-leave': !t.visible,
}) })

View file

@ -110,7 +110,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
return ( return (
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6'> <div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6'>
<div> <div>
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' /> <div className='relative h-32 w-full bg-gray-200 black:rounded-t-none md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' />
</div> </div>
<div className='px-4 sm:px-6'> <div className='px-4 sm:px-6'>
@ -620,7 +620,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
)} )}
<div> <div>
<div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50'> <div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 black:rounded-t-none md:rounded-t-xl lg:h-48 dark:bg-gray-900/50'>
{renderHeader()} {renderHeader()}
<div className='absolute left-2 top-2'> <div className='absolute left-2 top-2'>

View file

@ -60,15 +60,15 @@ const ChatPage: React.FC<IChatPage> = ({ chatId }) => {
<div <div
ref={containerRef} ref={containerRef}
style={{ height }} style={{ height }}
className='h-screen overflow-hidden bg-white text-gray-900 shadow-lg sm:rounded-t-xl dark:bg-primary-900 dark:text-gray-100 dark:shadow-none' className='h-screen overflow-hidden bg-white text-gray-900 shadow-lg black:bg-transparent sm:rounded-t-xl dark:bg-primary-900 dark:text-gray-100 dark:shadow-none'
> >
{isOnboarded ? ( {isOnboarded ? (
<div <div
className='grid h-full grid-cols-9 overflow-hidden dark:divide-x-2 dark:divide-solid dark:divide-gray-800' className='grid h-full grid-cols-9 overflow-hidden black:divide-x dark:divide-x-2 dark:divide-solid dark:divide-gray-800'
data-testid='chat-page' data-testid='chat-page'
> >
<Stack <Stack
className={clsx('dark:inset col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 sm:col-span-3 dark:bg-gray-900 dark:bg-none', { className={clsx('dark:inset col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 black:bg-black sm:col-span-3 dark:bg-gray-900 dark:bg-none', {
'hidden sm:block': isSidebarHidden, 'hidden sm:block': isSidebarHidden,
})} })}
> >

View file

@ -12,7 +12,7 @@ interface IPane {
const Pane: React.FC<IPane> = ({ isOpen = false, children }) => { const Pane: React.FC<IPane> = ({ isOpen = false, children }) => {
return ( return (
<div <div
className={clsx('fixed bottom-0 z-[99] flex w-96 flex-col rounded-t-lg bg-white shadow-3xl ltr:right-5 rtl:left-5 dark:bg-gray-900', { className={clsx('fixed bottom-0 z-[99] flex w-96 flex-col rounded-t-lg bg-white shadow-3xl black:border black:border-b-0 black:border-gray-800 black:bg-black ltr:right-5 rtl:left-5 dark:bg-gray-900', {
'h-[550px] max-h-[100vh]': isOpen, 'h-[550px] max-h-[100vh]': isOpen,
'h-16': !isOpen, 'h-16': !isOpen,
})} })}

View file

@ -80,7 +80,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
return ( return (
<> <>
<div className='-mx-4 -mt-4'> <div className='-mx-4 -mt-4'>
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' /> <div className='relative h-32 w-full bg-gray-200 black:rounded-t-none md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' />
</div> </div>
<PlaceholderEventHeader /> <PlaceholderEventHeader />
@ -363,13 +363,13 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
return ( return (
<> <>
<div className='-mx-4 -mt-4'> <div className='-mx-4 -mt-4'>
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50'> <div className='relative h-32 w-full bg-gray-200 black:rounded-t-none md:rounded-t-xl lg:h-48 dark:bg-gray-900/50'>
{banner && ( {banner && (
<a href={banner.url} onClick={handleHeaderClick} target='_blank'> <a href={banner.url} onClick={handleHeaderClick} target='_blank'>
<StillImage <StillImage
src={banner.url} src={banner.url}
alt={intl.formatMessage(messages.bannerHeader)} alt={intl.formatMessage(messages.bannerHeader)}
className='absolute inset-0 h-full object-cover md:rounded-t-xl' className='absolute inset-0 h-full object-cover black:rounded-t-none md:rounded-t-xl'
/> />
</a> </a>
)} )}

View file

@ -36,7 +36,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
return ( return (
<div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6' data-testid='group-header-missing'> <div className='-mx-4 -mt-4 sm:-mx-6 sm:-mt-6' data-testid='group-header-missing'>
<div> <div>
<div className='relative h-32 w-full bg-gray-200 md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' /> <div className='relative h-32 w-full bg-gray-200 black:rounded-t-none md:rounded-t-xl lg:h-48 dark:bg-gray-900/50' />
</div> </div>
<div className='px-4 sm:px-6'> <div className='px-4 sm:px-6'>

View file

@ -9,7 +9,7 @@ const PlaceholderEventPreview = () => {
const nameLength = randomIntFromInterval(5, 15); const nameLength = randomIntFromInterval(5, 15);
return ( return (
<div className='relative w-full animate-pulse overflow-hidden rounded-lg bg-gray-100 text-primary-50 dark:bg-primary-800 dark:text-primary-800'> <div className='relative w-full animate-pulse overflow-hidden rounded-lg bg-gray-100 text-primary-50 black:border black:border-gray-800 black:bg-black dark:bg-primary-800 dark:text-primary-800'>
<div className='h-40 bg-primary-200 dark:bg-gray-600' /> <div className='h-40 bg-primary-200 dark:bg-gray-600' />
<Stack className='p-2.5' space={2}> <Stack className='p-2.5' space={2}>
<Text weight='semibold'>{generateText(eventNameLength)}</Text> <Text weight='semibold'>{generateText(eventNameLength)}</Text>

View file

@ -9,7 +9,7 @@ const PlaceholderGroupCard = () => {
return ( return (
<div className='animate-pulse'> <div className='animate-pulse'>
<Stack className='relative h-[240px] rounded-lg border border-solid border-gray-300 bg-white dark:border-primary-800 dark:bg-primary-900'> <Stack className='relative h-[240px] rounded-lg border border-solid border-gray-300 bg-white black:bg-white dark:border-primary-800 dark:bg-primary-900'>
{/* Group Cover Image */} {/* Group Cover Image */}
<div className='relative grow basis-1/2 rounded-t-lg bg-gray-300 dark:bg-gray-800' /> <div className='relative grow basis-1/2 rounded-t-lg bg-gray-300 dark:bg-gray-800' />

View file

@ -8,7 +8,7 @@ import PlaceholderStatusContent from './placeholder-status-content';
/** Fake notification to display while data is loading. */ /** Fake notification to display while data is loading. */
const PlaceholderNotification = () => ( const PlaceholderNotification = () => (
<div className='bg-white px-4 py-6 sm:p-6 dark:bg-primary-900'> <div className='bg-white px-4 py-6 black:bg-black sm:p-6 dark:bg-primary-900'>
<div className='w-full animate-pulse'> <div className='w-full animate-pulse'>
<div className='mb-2'> <div className='mb-2'>
<PlaceholderStatusContent minLength={20} maxLength={20} /> <PlaceholderStatusContent minLength={20} maxLength={20} />

View file

@ -15,7 +15,7 @@ interface IPlaceholderStatus {
const PlaceholderStatus: React.FC<IPlaceholderStatus> = ({ variant }) => ( const PlaceholderStatus: React.FC<IPlaceholderStatus> = ({ variant }) => (
<div <div
className={clsx({ className={clsx({
'status-placeholder bg-white dark:bg-primary-900': true, 'status-placeholder bg-white black:bg-black dark:bg-primary-900': true,
'shadow-xl dark:shadow-none sm:rounded-xl px-4 py-6 sm:p-5': variant === 'rounded', 'shadow-xl dark:shadow-none sm:rounded-xl px-4 py-6 sm:p-5': variant === 'rounded',
'py-4': variant === 'slim', 'py-4': variant === 'slim',
})} })}

View file

@ -57,7 +57,7 @@ const CommunityTimeline = () => {
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent> <Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent>
<PinnedHostsPicker /> <PinnedHostsPicker />
{showExplanationBox && <div className='mb-4'> {showExplanationBox && <div className='mb-4 black:mx-4'>
<Accordion <Accordion
headline={<FormattedMessage id='fediverse_tab.explanation_box.title' defaultMessage='What is the Fediverse?' />} headline={<FormattedMessage id='fediverse_tab.explanation_box.title' defaultMessage='What is the Fediverse?' />}
action={dismissExplanationBox} action={dismissExplanationBox}

View file

@ -22,7 +22,7 @@ const SitePreview: React.FC<ISitePreview> = ({ soapbox }) => {
const userTheme = settings.get('themeMode'); const userTheme = settings.get('themeMode');
const systemTheme = useSystemTheme(); const systemTheme = useSystemTheme();
const dark = userTheme === 'dark' || (userTheme === 'system' && systemTheme === 'dark'); const dark = ['dark', 'black'].includes(userTheme as string) || (userTheme === 'system' && systemTheme === 'dark');
const bodyClass = clsx( const bodyClass = clsx(
'site-preview', 'site-preview',

View file

@ -8,7 +8,7 @@ interface IBackgroundShapes {
/** Gradient that appears in the background of the UI. */ /** Gradient that appears in the background of the UI. */
const BackgroundShapes: React.FC<IBackgroundShapes> = ({ position = 'fixed' }) => ( const BackgroundShapes: React.FC<IBackgroundShapes> = ({ position = 'fixed' }) => (
<div className={clsx(position, 'pointer-events-none inset-x-0 top-0 flex justify-center overflow-hidden')}> <div className={clsx(position, 'pointer-events-none inset-x-0 top-0 flex justify-center overflow-hidden black:hidden')}>
<div className='bg-gradient-sm lg:bg-gradient-light lg:dark:bg-gradient-dark h-screen w-screen' /> <div className='bg-gradient-sm lg:bg-gradient-light lg:dark:bg-gradient-dark h-screen w-screen' />
</div> </div>
); );

View file

@ -70,7 +70,7 @@ const Navbar = () => {
if (mfaToken) return <Redirect to={`/login?token=${encodeURIComponent(mfaToken)}`} />; if (mfaToken) return <Redirect to={`/login?token=${encodeURIComponent(mfaToken)}`} />;
return ( return (
<nav className='sticky top-0 z-50 bg-white shadow dark:bg-primary-900' ref={node} data-testid='navbar'> <nav className='sticky top-0 z-50 bg-white shadow black:border-b black:border-b-gray-800 black:bg-black dark:bg-primary-900' ref={node} data-testid='navbar'>
<div className='mx-auto max-w-7xl px-2 sm:px-6 lg:px-8'> <div className='mx-auto max-w-7xl px-2 sm:px-6 lg:px-8'>
<div className='relative flex h-12 justify-between lg:h-16'> <div className='relative flex h-12 justify-between lg:h-16'>
{account && ( {account && (

View file

@ -124,7 +124,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
{visible && ( {visible && (
<div <div
ref={refs.setFloating} ref={refs.setFloating}
className='z-[1003] mt-2 max-w-xs rounded-md bg-white shadow-lg focus:outline-none dark:bg-gray-900 dark:ring-2 dark:ring-primary-700' className='z-[1003] mt-2 max-w-xs rounded-md bg-white shadow-lg focus:outline-none black:bg-black dark:bg-gray-900 dark:ring-2 dark:ring-primary-700'
style={{ style={{
position: strategy, position: strategy,
top: y ?? 0, top: y ?? 0,

View file

@ -6,6 +6,7 @@ import { Icon, Select } from 'soapbox/components/ui';
const messages = defineMessages({ const messages = defineMessages({
light: { id: 'theme_toggle.light', defaultMessage: 'Light' }, light: { id: 'theme_toggle.light', defaultMessage: 'Light' },
dark: { id: 'theme_toggle.dark', defaultMessage: 'Dark' }, dark: { id: 'theme_toggle.dark', defaultMessage: 'Dark' },
black: { id: 'theme_toggle.black', defaultMessage: 'Pure Black' },
system: { id: 'theme_toggle.system', defaultMessage: 'System' }, system: { id: 'theme_toggle.system', defaultMessage: 'System' },
}); });
@ -26,6 +27,8 @@ const ThemeSelector: React.FC<IThemeSelector> = ({ value, onChange }) => {
return require('@tabler/icons/sun.svg'); return require('@tabler/icons/sun.svg');
case 'dark': case 'dark':
return require('@tabler/icons/moon.svg'); return require('@tabler/icons/moon.svg');
case 'black':
return require('@tabler/icons/moon-off.svg');
default: default:
return null; return null;
} }
@ -50,6 +53,7 @@ const ThemeSelector: React.FC<IThemeSelector> = ({ value, onChange }) => {
<option value='system'>{intl.formatMessage(messages.system)}</option> <option value='system'>{intl.formatMessage(messages.system)}</option>
<option value='light'>{intl.formatMessage(messages.light)}</option> <option value='light'>{intl.formatMessage(messages.light)}</option>
<option value='dark'>{intl.formatMessage(messages.dark)}</option> <option value='dark'>{intl.formatMessage(messages.dark)}</option>
<option value='black'>{intl.formatMessage(messages.black)}</option>
</Select> </Select>
<div className='pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3'> <div className='pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3'>

View file

@ -480,7 +480,7 @@ const UI: React.FC<IUI> = ({ children }) => {
<BackgroundShapes /> <BackgroundShapes />
<div className='z-10 flex flex-col'> <div className='z-10 flex min-h-screen flex-col'>
<Navbar /> <Navbar />
<Layout> <Layout>

View file

@ -1,7 +1,7 @@
import { useSettings } from './useSettings'; import { useSettings } from './useSettings';
import { useSystemTheme } from './useSystemTheme'; import { useSystemTheme } from './useSystemTheme';
type Theme = 'light' | 'dark'; type Theme = 'light' | 'dark' | 'black';
/** /**
* Returns the actual theme being displayed (eg "light" or "dark") * Returns the actual theme being displayed (eg "light" or "dark")
@ -11,8 +11,7 @@ const useTheme = (): Theme => {
const { themeMode } = useSettings(); const { themeMode } = useSettings();
const systemTheme = useSystemTheme(); const systemTheme = useSystemTheme();
const darkMode = themeMode === 'dark' || (themeMode === 'system' && systemTheme === 'dark'); return themeMode === 'system' ? systemTheme : themeMode;
return darkMode ? 'dark' : 'light';
}; };
export { useTheme }; export { useTheme };

View file

@ -22,12 +22,12 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
const { locale, direction } = useLocale(); const { locale, direction } = useLocale();
const { demo, reduceMotion, underlineLinks, demetricator } = useSettings(); const { demo, reduceMotion, underlineLinks, demetricator } = useSettings();
const soapboxConfig = useSoapboxConfig(); const soapboxConfig = useSoapboxConfig();
const theme = useTheme();
const darkMode = useTheme() === 'dark';
const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig); const themeCss = generateThemeCss(demo ? normalizeSoapboxConfig({ brandColor: '#0482d8' }) : soapboxConfig);
const dsn = soapboxConfig.sentryDsn; const dsn = soapboxConfig.sentryDsn;
const bodyClass = clsx('h-full bg-white text-base dark:bg-gray-800', { const bodyClass = clsx('h-full bg-white text-base black:bg-black dark:bg-gray-800', {
'no-reduce-motion': !reduceMotion, 'no-reduce-motion': !reduceMotion,
'underline-links': underlineLinks, 'underline-links': underlineLinks,
'demetricator': demetricator, 'demetricator': demetricator,
@ -42,10 +42,10 @@ const SoapboxHead: React.FC<ISoapboxHead> = ({ children }) => {
return ( return (
<> <>
<Helmet> <Helmet>
<html lang={locale} className={clsx('h-full', { dark: darkMode })} /> <html lang={locale} className={clsx('h-full', { 'dark': theme === 'dark', 'dark black': theme === 'black' })} />
<body className={bodyClass} dir={direction} /> <body className={bodyClass} dir={direction} />
{themeCss && <style id='theme' type='text/css'>{`:root{${themeCss}}`}</style>} {themeCss && <style id='theme' type='text/css'>{`:root{${themeCss}}`}</style>}
{darkMode && <style type='text/css'>{':root { color-scheme: dark; }'}</style>} {['dark', 'black'].includes(theme) && <style type='text/css'>{':root { color-scheme: dark; }'}</style>}
<meta name='theme-color' content={soapboxConfig.brandColor} /> <meta name='theme-color' content={soapboxConfig.brandColor} />
</Helmet> </Helmet>

View file

@ -7,7 +7,7 @@ interface IChatsPage {
/** Custom layout for chats on desktop. */ /** Custom layout for chats on desktop. */
const ChatsPage: React.FC<IChatsPage> = ({ children }) => { const ChatsPage: React.FC<IChatsPage> = ({ children }) => {
return ( return (
<div className='md:col-span-12 lg:col-span-9'> <div className='black:border-gray-800 md:col-span-12 lg:col-span-9 lg:black:border-l'>
{children} {children}
</div> </div>
); );

View file

@ -50,10 +50,10 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
return ( return (
<> <>
<Layout.Main className='space-y-3 pt-3 sm:pt-0 dark:divide-gray-800'> <Layout.Main className='space-y-3 pt-3 black:space-y-0 sm:pt-0 dark:divide-gray-800'>
{me && ( {me && (
<Card <Card
className={clsx('relative z-[1] transition', { className={clsx('relative z-[1] transition black:border-b black:border-gray-800', {
'border-2 border-primary-600 border-dashed z-[99]': isDragging, 'border-2 border-primary-600 border-dashed z-[99]': isDragging,
'ring-2 ring-offset-2 ring-primary-600': isDraggedOver, 'ring-2 ring-offset-2 ring-primary-600': isDraggedOver,
})} })}

View file

@ -23,7 +23,7 @@ const settingsSchema = z.object({
missingDescriptionModal: z.boolean().catch(false), missingDescriptionModal: z.boolean().catch(false),
defaultPrivacy: z.enum(['public', 'unlisted', 'private', 'direct']).catch('public'), defaultPrivacy: z.enum(['public', 'unlisted', 'private', 'direct']).catch('public'),
defaultContentType: z.enum(['text/plain', 'text/markdown']).catch('text/plain'), defaultContentType: z.enum(['text/plain', 'text/markdown']).catch('text/plain'),
themeMode: z.enum(['system', 'light', 'dark']).catch('system'), themeMode: z.enum(['system', 'light', 'dark', 'black']).catch('system'),
locale: z.string().catch(navigator.language).pipe(z.enum(locales)).catch('en'), locale: z.string().catch(navigator.language).pipe(z.enum(locales)).catch('en'),
showExplanationBox: z.boolean().catch(true), showExplanationBox: z.boolean().catch(true),
explanationBox: z.boolean().catch(true), explanationBox: z.boolean().catch(true),

View file

@ -1,9 +1,9 @@
.react-datepicker { .react-datepicker {
@apply dark:bg-gray-900 dark:border-gray-700 p-4 font-sans text-xs text-gray-900 dark:text-gray-300 border border-solid border-gray-200 rounded-lg; @apply black:bg-black dark:bg-gray-900 dark:border-gray-700 p-4 font-sans text-xs text-gray-900 dark:text-gray-300 border border-solid border-gray-200 rounded-lg;
} }
.react-datepicker__input-container > input { .react-datepicker__input-container > input {
@apply dark:bg-gray-900 dark:text-gray-100 block w-full sm:text-sm border-gray-400 dark:border-gray-800 rounded-md focus:ring-primary-500 focus:border-primary-500; @apply black:bg-black dark:bg-gray-900 dark:text-gray-100 block w-full sm:text-sm border-gray-400 dark:border-gray-800 rounded-md focus:ring-primary-500 focus:border-primary-500;
&.has-error { &.has-error {
@apply text-red-600 border-red-600; @apply text-red-600 border-red-600;
@ -24,7 +24,7 @@
} }
.react-datepicker__header { .react-datepicker__header {
@apply bg-white dark:bg-gray-900 border-b-0 py-1 px-0; @apply bg-white black:bg-black dark:bg-gray-900 border-b-0 py-1 px-0;
} }
.react-datepicker__current-month, .react-datepicker__current-month,
@ -85,11 +85,13 @@
} }
.react-datepicker__time { .react-datepicker__time {
@apply dark:bg-gray-900; &-container & {
@apply dark:bg-gray-900 black:bg-black;
}
} }
.react-datepicker__time-container { .react-datepicker__time-container {
@apply dark:border-gray-700; @apply dark:border-gray-700 black:border-gray-800;
} }
.react-datepicker__day-name, .react-datepicker__day-name,

View file

@ -1,5 +1,5 @@
.thread { .thread {
@apply bg-white dark:bg-primary-900; @apply bg-white black:bg-black dark:bg-primary-900;
&__status { &__status {
@apply relative pb-4; @apply relative pb-4;

View file

@ -8,3 +8,7 @@ em-emoji-picker {
.dark em-emoji-picker { .dark em-emoji-picker {
--rgb-background: var(--color-primary-900); --rgb-background: var(--color-primary-900);
} }
.black em-emoji-picker {
--rgb-background: var(--color-gray-900);
}

View file

@ -1,5 +1,5 @@
.thumb-navigation { .thumb-navigation {
@apply fixed lg:hidden bottom-0 bg-white/90 dark:bg-primary-900/90 backdrop-blur-md border-t border-solid border-gray-200 dark:border-gray-800 left-0 right-0 shadow-2xl w-full flex z-50; @apply fixed lg:hidden bottom-0 bg-white/90 black:bg-black/90 dark:bg-primary-900/90 backdrop-blur-md border-t border-solid border-gray-200 dark:border-gray-800 left-0 right-0 shadow-2xl w-full flex z-50;
padding-bottom: env(safe-area-inset-bottom); /* iOS PWA */ padding-bottom: env(safe-area-inset-bottom); /* iOS PWA */
overflow-x: auto; overflow-x: auto;
scrollbar-width: thin; scrollbar-width: thin;

View file

@ -2,9 +2,12 @@ import aspectRatioPlugin from '@tailwindcss/aspect-ratio';
import formsPlugin from '@tailwindcss/forms'; import formsPlugin from '@tailwindcss/forms';
import typographyPlugin from '@tailwindcss/typography'; import typographyPlugin from '@tailwindcss/typography';
import { type Config } from 'tailwindcss'; import { type Config } from 'tailwindcss';
import plugin from 'tailwindcss/plugin';
import { parseColorMatrix } from './tailwind/colors'; import { parseColorMatrix } from './tailwind/colors';
const blackVariantPlugin = plugin(({ addVariant }) => addVariant('black', '.black &'));
const config: Config = { const config: Config = {
content: ['./src/**/*.{html,js,ts,tsx}', './custom/instance/**/*.html', './index.html'], content: ['./src/**/*.{html,js,ts,tsx}', './custom/instance/**/*.html', './index.html'],
darkMode: 'class', darkMode: 'class',
@ -105,6 +108,7 @@ const config: Config = {
aspectRatioPlugin, aspectRatioPlugin,
formsPlugin, formsPlugin,
typographyPlugin, typographyPlugin,
blackVariantPlugin,
], ],
}; };