Merge branch 'language-dropdown' into 'main'

Add Language Dropdown Feature

Closes #1726

See merge request soapbox-pub/soapbox!3128
This commit is contained in:
Alex Gleason 2024-09-29 00:28:30 +00:00
commit 8730794ae8
4 changed files with 89 additions and 6 deletions

View File

@ -19,6 +19,7 @@ export type Menu = Array<MenuItem | null>;
interface IDropdownMenu {
children?: React.ReactElement;
modal?: boolean;
disabled?: boolean;
items: Menu;
onClose?: () => void;
@ -35,6 +36,7 @@ const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
const DropdownMenu = (props: IDropdownMenu) => {
const {
children,
modal = false,
disabled,
items,
onClose,
@ -90,7 +92,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
* On mobile screens, let's replace the Popper dropdown with a Modal.
*/
const handleOpen = () => {
if (userTouching.matches) {
if (userTouching.matches || modal) {
dispatch(
openModal('ACTIONS', {
status: filteredProps.status,
@ -111,7 +113,7 @@ const DropdownMenu = (props: IDropdownMenu) => {
const handleClose = () => {
(refs.reference.current as HTMLButtonElement)?.focus();
if (userTouching.matches) {
if (userTouching.matches || modal) {
dispatch(closeModal('ACTIONS'));
} else {
closeDropdownMenu();

View File

@ -59,13 +59,15 @@ export interface IColumn {
children?: React.ReactNode;
/** Action for the ColumnHeader, displayed at the end. */
action?: React.ReactNode;
/** Determines if the action for the ColumnHeader is displayed on the right. */
actionRightPosition?: boolean;
/** Column size, inherited from Card. */
size?: CardSizes;
}
/** A backdrop for the main section of the UI. */
const Column = React.forwardRef<HTMLDivElement, IColumn>((props, ref): JSX.Element => {
const { backHref, children, label, transparent = false, withHeader = true, className, bodyClassName, action, size } = props;
const { backHref, children, label, transparent = false, withHeader = true, className, bodyClassName, action, actionRightPosition, size } = props;
const soapboxConfig = useSoapboxConfig();
const [isScrolled, setIsScrolled] = useState(false);
@ -106,11 +108,11 @@ const Column = React.forwardRef<HTMLDivElement, IColumn>((props, ref): JSX.Eleme
'p-4 sm:p-0 sm:pb-4 black:p-4': transparent,
'-mt-4 p-4': size !== 'lg' && !transparent,
'-mt-4 p-4 sm:-mt-6 sm:-mx-6 sm:p-6': size === 'lg' && !transparent,
'w-full': actionRightPosition,
})}
action={action}
/>
)}
<CardBody className={bodyClassName}>
{children}
</CardBody>

View File

@ -0,0 +1,73 @@
import React, { useState } from 'react';
import { openDropdownMenu } from 'soapbox/actions/dropdown-menu';
import { clearTimeline, expandPublicTimeline } from 'soapbox/actions/timelines';
import DropdownMenu from 'soapbox/components/dropdown-menu';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import { languages } from 'soapbox/features/preferences';
import { useAppDispatch } from 'soapbox/hooks';
const formatterLanguage = (lang: {}) => {
const sigLanguage = Object.keys(lang).sort().map((sig) => {
return sig.substring(0, 2);
});
return [...new Set(sigLanguage)];
};
/**
*
*/
const LanguageDropdown = () => {
const dispatch = useAppDispatch();
const [languageIcon, setLanguageIcon] = useState('');
const handleChangeLanguage = (language?: string) => {
if (language) {
dispatch(clearTimeline('public'));
dispatch(expandPublicTimeline({ url: `/api/v1/timelines/public?language=${language}` }));
} else {
dispatch(clearTimeline('public'));
dispatch(expandPublicTimeline({ url: '/api/v1/timelines/public' }));
}
};
const formattedLanguage = formatterLanguage(languages);
const newMenu: any[] = [{ icon: require('@tabler/icons/outline/world.svg'), text: 'Default', action: () => {
setLanguageIcon('');
handleChangeLanguage();
} }];
formattedLanguage.map((lg: string) => {
const languageText = languages[lg as keyof typeof languages];
if (languageText !== undefined) {
newMenu.push({
text: `${lg.toUpperCase()} - ${languageText}`,
action: () => {
setLanguageIcon(lg.toUpperCase());
handleChangeLanguage(lg);
},
});
}
});
return (
<DropdownMenu items={newMenu} modal>
{ languageIcon === '' ?
<SvgIcon src={require('@tabler/icons/outline/world.svg')} className='text-gray-700 hover:cursor-pointer hover:text-gray-500 black:absolute black:right-0 black:top-4 black:text-white black:hover:text-gray-600 sm:mr-4' />
:
<button type='button' className='flex h-full rounded-lg border-2 border-gray-700 px-1 text-gray-700 hover:cursor-pointer hover:border-gray-500 hover:text-gray-500 sm:mr-4 dark:border-white dark:text-white dark:hover:border-gray-700' onClick={() => dispatch(openDropdownMenu())}>
{languageIcon}
</button>
}
</DropdownMenu>
);
};
export { LanguageDropdown };

View File

@ -3,10 +3,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { changeSetting } from 'soapbox/actions/settings';
import { expandPublicTimeline } from 'soapbox/actions/timelines';
import { clearTimeline, expandPublicTimeline } from 'soapbox/actions/timelines';
import { usePublicStream } from 'soapbox/api/hooks';
import PullToRefresh from 'soapbox/components/pull-to-refresh';
import { Accordion, Column } from 'soapbox/components/ui';
import { LanguageDropdown } from 'soapbox/components/ui/language-dropdown/language-dropdown';
import { useAppSelector, useAppDispatch, useInstance, useSettings, useTheme, useFeatures } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
@ -57,8 +58,13 @@ const PublicTimeline = () => {
dispatch(expandPublicTimeline({ onlyMedia }));
}, [onlyMedia]);
useEffect(() => {
dispatch(clearTimeline('public'));
dispatch(expandPublicTimeline({ url: '/api/v1/timelines/public' }));
}, []);
return (
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent={!isMobile}>
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent={!isMobile} action={<LanguageDropdown />} actionRightPosition>
<PinnedHostsPicker />
{showExplanationBox && (