From 16654bafefe8c3a76e5a2b3ab02f2d7d769b7ae4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 2 Dec 2022 15:30:27 +0800 Subject: [PATCH] Refactor the SetLabels components to allow them to take a highlight or article --- .../components/elements/ModalPrimitives.tsx | 19 +- .../templates/article/ArticleActionsMenu.tsx | 297 ++++++++------- .../templates/article/HighlightsModal.tsx | 2 +- .../templates/article/SetLabelsControl.tsx | 337 +++++++++++------- .../templates/article/SetLabelsModal.tsx | 37 +- .../templates/homeFeed/HomeFeedContainer.tsx | 40 ++- .../web/pages/[username]/[slug]/index.tsx | 16 +- 7 files changed, 442 insertions(+), 306 deletions(-) diff --git a/packages/web/components/elements/ModalPrimitives.tsx b/packages/web/components/elements/ModalPrimitives.tsx index 2790897e9..a9c0975c3 100644 --- a/packages/web/components/elements/ModalPrimitives.tsx +++ b/packages/web/components/elements/ModalPrimitives.tsx @@ -29,7 +29,7 @@ const Modal = styled(Content, { boxShadow: theme.shadows.cardBoxShadow.toString(), position: 'fixed', '&:focus': { outline: 'none' }, - zIndex:'1', + zIndex: '1', }) export const ModalContent = styled(Modal, { @@ -43,6 +43,7 @@ export const ModalContent = styled(Modal, { maxWidth: '95%', width: '95%', }, + zIndex: '10', }) export type ModalTitleBarProps = { @@ -57,9 +58,7 @@ export const ModalTitleBar = (props: ModalTitleBarProps) => { alignment="center" css={{ height: '68px', width: '100%' }} > - - {props.title} - + {props.title} ) @@ -98,7 +94,10 @@ export const ModalButtonBar = (props: ModalButtonBarProps) => { }, }} > - ) -} \ No newline at end of file +} diff --git a/packages/web/components/templates/article/ArticleActionsMenu.tsx b/packages/web/components/templates/article/ArticleActionsMenu.tsx index c90cb6262..2469f5798 100644 --- a/packages/web/components/templates/article/ArticleActionsMenu.tsx +++ b/packages/web/components/templates/article/ArticleActionsMenu.tsx @@ -1,15 +1,25 @@ -import { Separator } from "@radix-ui/react-separator" -import { ArchiveBox, DotsThree, HighlighterCircle, TagSimple, TextAa, Trash, Tray } from "phosphor-react" -import { ArticleAttributes } from "../../../lib/networking/queries/useGetArticleQuery" -import { Button } from "../../elements/Button" -import { Dropdown } from "../../elements/DropdownElements" -import { Box, SpanBox } from "../../elements/LayoutPrimitives" -import { TooltipWrapped } from "../../elements/Tooltip" -import { styled, theme } from "../../tokens/stitches.config" -import { SetLabelsControl } from "./SetLabelsControl" -import { DisplaySettingsModal } from "./DisplaySettingsModal" -import { useReaderSettings } from "../../../lib/hooks/useReaderSettings" -import { useRef } from "react" +import { Separator } from '@radix-ui/react-separator' +import { + ArchiveBox, + DotsThree, + HighlighterCircle, + TagSimple, + TextAa, + Trash, + Tray, +} from 'phosphor-react' +import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery' +import { Button } from '../../elements/Button' +import { Dropdown } from '../../elements/DropdownElements' +import { Box, SpanBox } from '../../elements/LayoutPrimitives' +import { TooltipWrapped } from '../../elements/Tooltip' +import { styled, theme } from '../../tokens/stitches.config' +import { SetLabelsControl } from './SetLabelsControl' +import { DisplaySettingsModal } from './DisplaySettingsModal' +import { useReaderSettings } from '../../../lib/hooks/useReaderSettings' +import { useRef } from 'react' +import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation' +import { Label } from '../../../lib/networking/fragments/labelFragment' export type ArticleActionsMenuLayout = 'top' | 'side' @@ -31,7 +41,7 @@ const MenuSeparator = (props: MenuSeparatorProps): JSX.Element => { borderBottom: `1px solid ${theme.colors.grayLine.toString()}`, my: '8px', }) - return (props.layout == 'side' ? : <>) + return props.layout == 'side' ? : <> } type ActionDropdownProps = { @@ -41,126 +51,166 @@ type ActionDropdownProps = { } const ActionDropdown = (props: ActionDropdownProps): JSX.Element => { - return - {props.children} - + return ( + + {props.children} + + ) } -export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element { +export function ArticleActionsMenu( + props: ArticleActionsMenuProps +): JSX.Element { const readerSettings = useReaderSettings() const displaySettingsButtonRef = useRef(null) return ( <> - - {props.showReaderDisplaySettings && ( - <> - - - - )} - - {props.article ? ( - + + + + )} + + {props.article ? ( + + + + } + > + { + if (props.article?.id) { + return setLabelsMutation( + props.article?.id, + labels.map((label) => label.id) + ) + } + return Promise.resolve(undefined) + }} + onSave={(labels) => { + props.articleActionHandler('refreshLabels', labels) + }} + /> + + ) : ( + - )} - - - + - + + + + - + - + + + + - {!props.article?.isArchived ? ( + {!props.article?.isArchived ? ( )} - {/* + {/* */} - - {readerSettings.showEditDisplaySettingsModal && ( - readerSettings.setShowEditDisplaySettingsModal(false)} - /> - )} + + {readerSettings.showEditDisplaySettingsModal && ( + + readerSettings.setShowEditDisplaySettingsModal(false) + } + /> + )} ) } diff --git a/packages/web/components/templates/article/HighlightsModal.tsx b/packages/web/components/templates/article/HighlightsModal.tsx index 8e5fa0607..8336472c4 100644 --- a/packages/web/components/templates/article/HighlightsModal.tsx +++ b/packages/web/components/templates/article/HighlightsModal.tsx @@ -42,7 +42,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element { event.preventDefault() props.onOpenChange(false) }} - css={{ overflow: 'auto', px: '24px', zIndex: '10' }} + css={{ overflow: 'auto', px: '24px' }} > diff --git a/packages/web/components/templates/article/SetLabelsControl.tsx b/packages/web/components/templates/article/SetLabelsControl.tsx index 2afb1e06e..cb364a65a 100644 --- a/packages/web/components/templates/article/SetLabelsControl.tsx +++ b/packages/web/components/templates/article/SetLabelsControl.tsx @@ -9,18 +9,19 @@ import { Label } from '../../../lib/networking/fragments/labelFragment' import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery' import { Check, Circle, PencilSimple, Plus } from 'phosphor-react' import { isTouchScreenDevice } from '../../../lib/deviceType' -import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation' import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation' import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects' import { useRouter } from 'next/router' -import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery' + +export interface LabelsProvider { + labels?: Label[] +} type SetLabelsControlProps = { - linkId: string - labels: Label[] | undefined - article?: ArticleAttributes - articleActionHandler: (action: string, arg?: unknown) => void + provider?: LabelsProvider + onSave: (labels: Label[] | undefined) => void + save: (labels: Label[]) => Promise } type HeaderProps = { @@ -57,12 +58,16 @@ function Header(props: HeaderProps): JSX.Element { }, [props.focused]) return ( - - + + - - ) + + + ) } type LabelListItemProps = { @@ -128,21 +134,61 @@ function LabelListItem(props: LabelListItemProps): JSX.Element { ref.current?.blur() }} > - - - {selected && } + + + {selected && ( + + )} - - + + - + {label.name} - - {selected && } + + {selected && ( + + )} ) @@ -164,9 +210,11 @@ function Footer(props: FooterProps): JSX.Element { return ( - - - Edit labels - - + > + + + Edit labels + + ) } @@ -189,41 +239,47 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element { const router = useRouter() const [filterText, setFilterText] = useState('') const { labels, revalidate } = useGetLabelsQuery() - const [selectedLabels, setSelectedLabels] = useState(props.labels || []) + const [selectedLabels, setSelectedLabels] = useState( + props.provider?.labels || [] + ) useEffect(() => { setFocusedIndex(undefined) }, [filterText]) - const isSelected = useCallback((label: Label): boolean => { - return selectedLabels.some((other) => { - return other.id === label.id - }) - }, [selectedLabels]) - - const toggleLabel = useCallback(async (label: Label) => { - let newSelectedLabels = [...selectedLabels] - if (isSelected(label)) { - newSelectedLabels = selectedLabels.filter((other) => { - return other.id !== label.id + const isSelected = useCallback( + (label: Label): boolean => { + return selectedLabels.some((other) => { + return other.id === label.id }) - } else { - newSelectedLabels = [...selectedLabels, label] - } - setSelectedLabels(newSelectedLabels) + }, + [selectedLabels] + ) - const result = await setLabelsMutation( - props.linkId, - newSelectedLabels.map((label) => label.id) - ) + const toggleLabel = useCallback( + async (label: Label) => { + let newSelectedLabels = [...selectedLabels] + if (isSelected(label)) { + newSelectedLabels = selectedLabels.filter((other) => { + return other.id !== label.id + }) + } else { + newSelectedLabels = [...selectedLabels, label] + } + setSelectedLabels(newSelectedLabels) - if (props.article) { - props.article.labels = result - } - props.articleActionHandler('refreshLabels', result) + const result = await props.save(newSelectedLabels) + if (props.provider) { + props.provider.labels = result + } + if (props.onSave) { + props.onSave(result) + } - revalidate() - }, [isSelected, selectedLabels, setSelectedLabels]) + revalidate() + }, + [isSelected, selectedLabels, setSelectedLabels] + ) const filteredLabels = useMemo(() => { if (!labels) { @@ -235,62 +291,80 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element { }, [labels, filterText]) // Move focus through the labels list on tab or arrow up/down keys - const [focusedIndex, setFocusedIndex] = useState(undefined) - const handleKeyDown = useCallback(async (event: React.KeyboardEvent) => { - const maxIndex = filteredLabels.length + 1 - if (event.key === 'ArrowUp') { - event.preventDefault() - let newIndex = focusedIndex - if (focusedIndex) { - newIndex = Math.max(0, focusedIndex - 1) - } else { - newIndex = undefined + const [focusedIndex, setFocusedIndex] = useState( + undefined + ) + const handleKeyDown = useCallback( + async (event: React.KeyboardEvent) => { + const maxIndex = filteredLabels.length + 1 + if (event.key === 'ArrowUp') { + event.preventDefault() + let newIndex = focusedIndex + if (focusedIndex) { + newIndex = Math.max(0, focusedIndex - 1) + } else { + newIndex = undefined + } + // If the `Create New label` button isn't visible we skip it + // when navigating with the arrow keys + if (focusedIndex === maxIndex && !filterText) { + newIndex = maxIndex - 2 + } + setFocusedIndex(newIndex) } - // If the `Create New label` button isn't visible we skip it - // when navigating with the arrow keys - if (focusedIndex === maxIndex && !filterText) { - newIndex = maxIndex - 2 + if (event.key === 'ArrowDown' || event.key === 'Tab') { + event.preventDefault() + let newIndex = focusedIndex + if (focusedIndex === undefined) { + newIndex = 0 + } else { + newIndex = Math.min(maxIndex, focusedIndex + 1) + } + // If the `Create New label` button isn't visible we skip it + // when navigating with the arrow keys + if (focusedIndex === maxIndex - 2 && !filterText) { + newIndex = maxIndex + } + setFocusedIndex(newIndex) } - setFocusedIndex(newIndex) - } - if (event.key === 'ArrowDown' || event.key === 'Tab') { - event.preventDefault() - let newIndex = focusedIndex - if (focusedIndex === undefined) { - newIndex = 0 - } else { - newIndex = Math.min(maxIndex, focusedIndex + 1) - } - // If the `Create New label` button isn't visible we skip it - // when navigating with the arrow keys - if (focusedIndex === maxIndex - 2 && !filterText) { - newIndex = maxIndex - } - setFocusedIndex(newIndex) - } - if (event.key === 'Enter') { - event.preventDefault() - if (focusedIndex === maxIndex) { - router.push('/settings/labels') - return - } - if (focusedIndex === maxIndex - 1) { - await createLabelFromFilterText() - return - } - if (focusedIndex !== undefined) { - const label = filteredLabels[focusedIndex] - if (label) { - toggleLabel(label) + if (event.key === 'Enter') { + event.preventDefault() + if (focusedIndex === maxIndex) { + router.push('/settings/labels') + return + } + if (focusedIndex === maxIndex - 1) { + await createLabelFromFilterText() + return + } + if (focusedIndex !== undefined) { + const label = filteredLabels[focusedIndex] + if (label) { + toggleLabel(label) + } } } - } - }, [filterText, filteredLabels, focusedIndex, isSelected, selectedLabels, setSelectedLabels]) + }, + [ + filterText, + filteredLabels, + focusedIndex, + isSelected, + selectedLabels, + setSelectedLabels, + ] + ) const createLabelFromFilterText = useCallback(async () => { - const label = await createLabelMutation(filterText, randomLabelColorHex(), '') + const label = await createLabelMutation( + filterText, + randomLabelColorHex(), + '' + ) if (label) { - showSuccessToast(`Created label ${label.name}`, { position: 'bottom-right' }) + showSuccessToast(`Created label ${label.name}`, { + position: 'bottom-right', + }) toggleLabel(label) } else { showErrorToast('Failed to create label', { position: 'bottom-right' }) @@ -301,20 +375,28 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element { + }} + >
setFocusedIndex(undefined)} - setFilterText={setFilterText} filterText={filterText} + setFilterText={setFilterText} + filterText={filterText} /> + css={{ + flexGrow: '1', + overflow: 'scroll', + width: '100%', + height: '100%', + maxHeight: '294px', + }} + > {filteredLabels && filteredLabels.map((label, idx) => ( {filterText && ( - )} -