From 1547dd7583562cd7cbdee6355bb67e294b463863 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 16 Jun 2023 12:51:21 +0800 Subject: [PATCH 01/18] Improved labels picker for the web --- .../web/components/elements/LabelChip.tsx | 22 +- .../templates/article/ArticleContainer.tsx | 3 + .../templates/article/SetLabelsControl.tsx | 201 ++++++++---------- .../templates/article/SetLabelsModal.tsx | 7 +- .../lib/networking/fragments/labelFragment.ts | 2 +- .../mutations/createLabelMutation.ts | 17 +- packages/web/package.json | 4 +- yarn.lock | 40 ++-- 8 files changed, 141 insertions(+), 155 deletions(-) diff --git a/packages/web/components/elements/LabelChip.tsx b/packages/web/components/elements/LabelChip.tsx index 96b76e9cf..e32ac2e37 100644 --- a/packages/web/components/elements/LabelChip.tsx +++ b/packages/web/components/elements/LabelChip.tsx @@ -8,6 +8,7 @@ import { isDarkTheme } from '../../lib/themeUpdater' type LabelChipProps = { text: string color: string // expected to be a RGB hex color string + isSelected?: boolean useAppAppearance?: boolean } @@ -15,25 +16,10 @@ export function LabelChip(props: LabelChipProps): JSX.Element { const router = useRouter() const isDark = isDarkTheme() - const hexToRgb = (hex: string) => { - const bigint = parseInt(hex.substring(1), 16) - const r = (bigint >> 16) & 255 - const g = (bigint >> 8) & 255 - const b = bigint & 255 - - return [r, g, b] - } - - function f(x: number) { - const channel = x / 255 - return channel <= 0.03928 - ? channel / 12.92 - : Math.pow((channel + 0.055) / 1.055, 2.4) - } - const luminance = getLuminance(props.color) - const backgroundColor = hexToRgb(props.color) const textColor = luminance > 0.5 ? '#000000' : '#ffffff' + const selectedBorder = isDark ? 'white' : 'black' + const unSelectedBorder = isDark ? '#6A6968' : '#D9D9D9' if (props.useAppAppearance) { return ( @@ -52,7 +38,7 @@ export function LabelChip(props: LabelChipProps): JSX.Element { borderWidth: '1px', borderStyle: 'solid', color: isDark ? '#EBEBEB' : '#2A2A2A', - borderColor: isDark ? '#6A6968' : '#D9D9D9', + borderColor: props.isSelected ? selectedBorder : unSelectedBorder, backgroundColor: isDark ? '#2A2A2A' : '#F5F5F5', }} > diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx index 3e362fcd7..5960dd17d 100644 --- a/packages/web/components/templates/article/ArticleContainer.tsx +++ b/packages/web/components/templates/article/ArticleContainer.tsx @@ -23,6 +23,8 @@ import { Label } from '../../../lib/networking/fragments/labelFragment' import { Recommendation } from '../../../lib/networking/queries/useGetLibraryItemsQuery' import { Avatar } from '../../elements/Avatar' import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' +import Downshift from 'downshift' +import { LabelsPicker } from '../../elements/LabelsPicker' type ArticleContainerProps = { viewer: UserBasicData @@ -421,6 +423,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element { author={props.article.author} href={props.article.url} /> + {labels ? ( void setFilterText: (text: string) => void + + selectedLabels: Label[] + setSelectedLabels: (labels: Label[]) => void } const FormInput = styled('input', { @@ -52,18 +56,8 @@ const StyledLabel = styled('label', { }) function Header(props: HeaderProps): JSX.Element { - const inputRef = useRef(null) - - useEffect(() => { - if (!isTouchScreenDevice() && props.focused && inputRef.current) { - inputRef.current.focus() - } - }, [props.focused]) - return ( - + - { - props.setFilterText(event.target.value) + { + props.setFilterText(filterText) }} onFocus={() => { props.resetFocusedIndex() }} - css={{ - border: '1px solid $grayBorder', - borderRadius: '8px', - width: '100%', - bg: 'transparent', - fontSize: '16px', - textIndent: '8px', - marginBottom: '2px', - color: '$grayTextContrast', - '&:focus': { - outline: 'none', - boxShadow: '0px 0px 2px 2px rgba(255, 234, 159, 0.56)', - }, - }} /> @@ -127,8 +104,11 @@ function LabelListItem(props: LabelListItemProps): JSX.Element { css={{ width: '100%', height: '42px', - borderBottom: '1px solid $grayBorder', + p: '15px', bg: props.focused ? '$grayBgActive' : 'unset', + '&:focus-visible': { + outline: 'none', + }, }} tabIndex={props.focused ? 0 : -1} onClick={(event) => { @@ -144,22 +124,6 @@ function LabelListItem(props: LabelListItemProps): JSX.Element { checked={selected} readOnly /> - - {selected && ( - - )} - {selected && ( - + )} @@ -199,6 +166,7 @@ function LabelListItem(props: LabelListItemProps): JSX.Element { type FooterProps = { focused: boolean + filterText: string } function Footer(props: FooterProps): JSX.Element { @@ -228,12 +196,39 @@ function Footer(props: FooterProps): JSX.Element { }, }} > - - - Edit labels - + {props.filterText.length > 0 ? ( + + ) : ( + + + Edit labels + + )} ) } @@ -296,21 +291,20 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element { undefined ) - const createLabelFromFilterText = useCallback(async () => { - const label = await createLabelMutation( - filterText, - randomLabelColorHex(), - '' - ) - if (label) { - showSuccessToast(`Created label ${label.name}`, { - position: 'bottom-right', - }) - toggleLabel(label) - } else { - showErrorToast('Failed to create label', { position: 'bottom-right' }) - } - }, [filterText, toggleLabel]) + const createLabelFromFilterText = useCallback( + async (text: string) => { + const label = await createLabelMutation(text, randomLabelColorHex(), '') + if (label) { + showSuccessToast(`Created label ${label.name}`, { + position: 'bottom-right', + }) + toggleLabel(label) + } else { + showErrorToast('Failed to create label', { position: 'bottom-right' }) + } + }, + [filterText, toggleLabel] + ) const handleKeyDown = useCallback( async (event: React.KeyboardEvent) => { @@ -330,7 +324,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element { } setFocusedIndex(newIndex) } - if (event.key === 'ArrowDown' || event.key === 'Tab') { + if (event.key === 'ArrowDown') { event.preventDefault() let newIndex = focusedIndex if (focusedIndex === undefined) { @@ -352,7 +346,9 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element { return } if (focusedIndex === maxIndex - 1) { - await createLabelFromFilterText() + const _filterText = filterText + setFilterText('') + await createLabelFromFilterText(_filterText) return } if (focusedIndex !== undefined) { @@ -387,53 +383,34 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element { resetFocusedIndex={() => setFocusedIndex(undefined)} setFilterText={setFilterText} filterText={filterText} + selectedLabels={props.selectedLabels} + setSelectedLabels={props.setSelectedLabels} /> - {filteredLabels && - filteredLabels.map((label, idx) => ( - - ))} + {filteredLabels.map((label, idx) => ( + + ))} - {filterText && ( - - )} - -