From 064e051273f200e16c445d080bbb74ba6df75224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 31 Aug 2024 00:03:06 +0200 Subject: [PATCH] Simplify privacy dropdown code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../dropdown-menu/dropdown-menu-item.tsx | 28 ++- packages/pl-fe/src/components/icon-button.tsx | 1 - .../compose/components/privacy-dropdown.tsx | 171 ++---------------- 3 files changed, 43 insertions(+), 157 deletions(-) diff --git a/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx b/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx index d08ba1588..0b757b54b 100644 --- a/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx +++ b/packages/pl-fe/src/components/dropdown-menu/dropdown-menu-item.tsx @@ -4,20 +4,23 @@ import { useHistory } from 'react-router-dom'; import { userTouching } from 'pl-fe/is-mobile'; -import { Counter, Icon } from '../ui'; +import { Counter, Icon, Toggle } from '../ui'; interface MenuItem { action?: React.EventHandler; active?: boolean; + checked?: boolean; count?: number; destructive?: boolean; href?: string; icon?: string; meta?: string; middleClick?(event: React.MouseEvent): void; + onChange?: (value: boolean) => void; target?: React.HTMLAttributeAnchorTarget; text: string; to?: string; + type?: 'toggle'; } interface IDropdownMenuItem { @@ -67,6 +70,15 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus }: IDropdownMenuItem } }; + const handleChange: React.ChangeEventHandler = (event) => { + event.preventDefault(); + event.stopPropagation(); + + if (!item) return; + + if (item.onChange) item.onChange(event.target.checked); + }; + useEffect(() => { const firstItem = index === 0; @@ -93,21 +105,29 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus }: IDropdownMenuItem target={item.target} title={item.text} className={ - clsx({ - 'flex px-4 py-2.5 text-sm text-gray-700 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gray-100 dark:focus:bg-gray-800 focus:outline-none cursor-pointer black:hover:bg-gray-900 black:focus:bg-gray-900': true, + clsx('flex cursor-pointer items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-800 focus:bg-gray-100 focus:text-gray-800 focus:outline-none black:hover:bg-gray-900 black:focus:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-200 dark:focus:bg-gray-800 dark:focus:text-gray-200', { 'text-danger-600 dark:text-danger-400': item.destructive, }) } > {item.icon && } - {item.text} +
+
{item.text}
+
{item.meta}
+
{item.count ? ( ) : null} + + {item.type === 'toggle' && ( +
+ +
+ )} ); diff --git a/packages/pl-fe/src/components/icon-button.tsx b/packages/pl-fe/src/components/icon-button.tsx index f560bcbf8..d249c8a22 100644 --- a/packages/pl-fe/src/components/icon-button.tsx +++ b/packages/pl-fe/src/components/icon-button.tsx @@ -33,7 +33,6 @@ const IconButton: React.FC = ({ text, title, }) => { - const handleClick: React.MouseEventHandler = (e) => { e.preventDefault(); diff --git a/packages/pl-fe/src/features/compose/components/privacy-dropdown.tsx b/packages/pl-fe/src/features/compose/components/privacy-dropdown.tsx index bd2f261f2..5d910be43 100644 --- a/packages/pl-fe/src/features/compose/components/privacy-dropdown.tsx +++ b/packages/pl-fe/src/features/compose/components/privacy-dropdown.tsx @@ -1,10 +1,9 @@ -import clsx from 'clsx'; -import React, { useEffect, useMemo, useRef } from 'react'; -import { useIntl, defineMessages, FormattedMessage, IntlShape } from 'react-intl'; +import React from 'react'; +import { useIntl, defineMessages, IntlShape } from 'react-intl'; import { changeComposeFederated, changeComposeVisibility } from 'pl-fe/actions/compose'; -import DropdownMenu from 'pl-fe/components/dropdown-menu'; -import { Button, Icon, Toggle } from 'pl-fe/components/ui'; +import DropdownMenu, { MenuItem } from 'pl-fe/components/dropdown-menu'; +import { Button } from 'pl-fe/components/ui'; import { useAppDispatch, useCompose, useFeatures } from 'pl-fe/hooks'; import type { Features } from 'pl-api'; @@ -34,10 +33,6 @@ interface Option { meta: string; } -interface IPrivacyDropdownMenu { - handleClose: () => any; -} - const getItems = (features: Features, intl: IntlShape) => [ { icon: require('@tabler/icons/outline/world.svg'), value: 'public', text: intl.formatMessage(messages.public_short), meta: intl.formatMessage(messages.public_long) }, { icon: require('@tabler/icons/outline/lock-open.svg'), value: 'unlisted', text: intl.formatMessage(messages.unlisted_short), meta: intl.formatMessage(messages.unlisted_long) }, @@ -47,143 +42,6 @@ const getItems = (features: Features, intl: IntlShape) => [ features.visibilityLocalOnly ? { icon: require('@tabler/icons/outline/affiliate.svg'), value: 'local', text: intl.formatMessage(messages.local_short), meta: intl.formatMessage(messages.local_long) } : undefined, ].filter((option): option is Option => !!option); -const getPrivacyDropdown = (composeId: string): React.FC => ({ handleClose: handleMenuClose }) => { - const dispatch = useAppDispatch(); - const intl = useIntl(); - const features = useFeatures(); - - const compose = useCompose(composeId); - - const value = compose.privacy; - - const items = getItems(features, intl); - - const onChange = (value: string | null) => value && dispatch(changeComposeVisibility(composeId, value)); - - const onChangeFederated = () => dispatch(changeComposeFederated(composeId)); - - const node = useRef(null); - const focusedItem = useRef(null); - - const handleKeyDown: React.KeyboardEventHandler = e => { - const index = [...e.currentTarget.parentElement!.children].indexOf(e.currentTarget); - let element: ChildNode | null | undefined = null; - - switch (e.key) { - case 'Escape': - handleMenuClose(); - break; - case 'Enter': - handleClick(e); - break; - case 'ArrowDown': - element = node.current?.childNodes[index + 1] || node.current?.firstChild; - break; - case 'ArrowUp': - element = node.current?.childNodes[index - 1] || node.current?.lastChild; - break; - case 'Tab': - if (e.shiftKey) { - element = node.current?.childNodes[index - 1] || node.current?.lastChild; - } else { - element = node.current?.childNodes[index + 1] || node.current?.firstChild; - } - break; - case 'Home': - element = node.current?.firstChild; - break; - case 'End': - element = node.current?.lastChild; - break; - } - - if (element) { - (element as HTMLElement).focus(); - const value = (element as HTMLElement).getAttribute('data-index'); - if (value !== 'local_switch') onChange(value); - e.preventDefault(); - e.stopPropagation(); - } - }; - - const handleClick: React.EventHandler = (e: MouseEvent | KeyboardEvent) => { - const value = (e.currentTarget as HTMLElement)?.getAttribute('data-index'); - - e.preventDefault(); - - if (value === 'local_switch') onChangeFederated(); - else { - handleMenuClose(); - onChange(value); - } - }; - - useEffect(() => { - if (node.current) { - (node.current?.querySelector('li[aria-selected=true]') as HTMLDivElement)?.focus(); - } - }, [node.current]); - - return ( -
    - {items.map(item => { - const active = item.value === value; - return ( -
  • - - -
    - {item.text} - {item.meta} -
    -
  • - ); - })} - {features.localOnlyStatuses && ( -
  • - - -
    - - - - -
    - - -
  • - )} - -
- ); -}; - interface IPrivacyDropdown { composeId: string; } @@ -193,26 +51,35 @@ const PrivacyDropdown: React.FC = ({ }) => { const intl = useIntl(); const features = useFeatures(); + const dispatch = useAppDispatch(); const compose = useCompose(composeId); const value = compose.privacy; const unavailable = compose.id; - const items = getItems(features, intl); + const onChange = (value: string) => value && dispatch(changeComposeVisibility(composeId, value)); - const PrivacyDropdownMenu = useMemo(() => getPrivacyDropdown(composeId), [composeId]); + const options = getItems(features, intl); + const items: Array = options.map(item => ({ ...item, action: () => onChange(item.value), active: item.value === value })); + + if (features.localOnlyStatuses) items.push({ + icon: require('@tabler/icons/outline/affiliate.svg'), + text: intl.formatMessage(messages.local_short), + meta: intl.formatMessage(messages.local_long), + type: 'toggle', + checked: !compose.federated, + onChange: () => dispatch(changeComposeFederated(composeId)), + }); if (unavailable) { return null; } - const valueOption = items.find(item => item.value === value); + const valueOption = options.find(item => item.value === value); return ( - +