Merge pull request #1500 from omnivore-app/feat/web-highlight-labels

Allow setting labels on highlights on the web
This commit is contained in:
Jackson Harper 2022-12-05 11:02:07 +08:00 committed by GitHub
commit ce49287444
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 787 additions and 528 deletions

View file

@ -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%' }}
>
<StyledText style="modalHeadline" css={{ }}>
{props.title}
</StyledText>
<StyledText style="modalHeadline">{props.title}</StyledText>
<Button
css={{ ml: 'auto' }}
style="ghost"
@ -67,10 +66,7 @@ export const ModalTitleBar = (props: ModalTitleBarProps) => {
props.onOpenChange(false)
}}
>
<X
size={24}
color={theme.colors.textNonessential.toString()}
/>
<X size={24} color={theme.colors.textNonessential.toString()} />
</Button>
</HStack>
)
@ -98,7 +94,10 @@ export const ModalButtonBar = (props: ModalButtonBarProps) => {
},
}}
>
<Button style={'ctaOutlineYellow'} type="button" onClick={(event) => {
<Button
style={'ctaOutlineYellow'}
type="button"
onClick={(event) => {
event.preventDefault()
props.onOpenChange(false)
}}
@ -110,4 +109,4 @@ export const ModalButtonBar = (props: ModalButtonBarProps) => {
</Button>
</HStack>
)
}
}

View file

@ -1,5 +1,6 @@
import { Fragment, useMemo } from 'react'
import type { Highlight } from '../../lib/networking/fragments/highlightFragment'
import { LabelChip } from '../elements/LabelChip'
import { Box, VStack, Blockquote, SpanBox } from '../elements/LayoutPrimitives'
import { StyledText } from '../elements/StyledText'
import { styled } from '../tokens/stitches.config'
@ -48,6 +49,11 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
</Fragment>
))}
</SpanBox>
<Box css={{ display: 'block', pt: '16px' }}>
{props.highlight.labels?.map(({ name, color }, index) => (
<LabelChip key={index} text={name || ''} color={color} />
))}
</Box>
</StyledQuote>
</VStack>
)

View file

@ -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' ? <LineSeparator /> : <></>)
return props.layout == 'side' ? <LineSeparator /> : <></>
}
type ActionDropdownProps = {
@ -41,126 +51,166 @@ type ActionDropdownProps = {
}
const ActionDropdown = (props: ActionDropdownProps): JSX.Element => {
return <Dropdown
css={{ m: '0px', p: '0px', overflow: 'hidden', width: '265px', maxWidth: '265px', '@smDown': { width: '230px' } }}
side={props.layout == 'side' ? 'right' : 'bottom'}
sideOffset={props.layout == 'side' ? 8 : 0}
align={props.layout == 'side' ? 'start' : 'center'}
alignOffset={props.layout == 'side' ? -18 : undefined}
triggerElement={props.triggerElement}
>
{props.children}
</Dropdown>
return (
<Dropdown
css={{
m: '0px',
p: '0px',
overflow: 'hidden',
width: '265px',
maxWidth: '265px',
'@smDown': { width: '230px' },
}}
side={props.layout == 'side' ? 'right' : 'bottom'}
sideOffset={props.layout == 'side' ? 8 : 0}
align={props.layout == 'side' ? 'start' : 'center'}
alignOffset={props.layout == 'side' ? -18 : undefined}
triggerElement={props.triggerElement}
>
{props.children}
</Dropdown>
)
}
export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element {
export function ArticleActionsMenu(
props: ArticleActionsMenuProps
): JSX.Element {
const readerSettings = useReaderSettings()
const displaySettingsButtonRef = useRef<HTMLElement | null>(null)
return (
<>
<Box
css={{
display: 'flex',
alignItems: 'center',
flexDirection: props.layout == 'side' ? 'column' : 'row',
justifyContent: props.layout == 'side' ? 'center' : 'flex-end',
gap: props.layout == 'side' ? '8px' : '24px',
paddingTop: '6px',
}}
>
{props.showReaderDisplaySettings && (
<>
<Button style='articleActionIcon' onClick={() => readerSettings.setShowEditDisplaySettingsModal(true)}>
<TooltipWrapped
tooltipContent="Adjust Display Settings"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<SpanBox ref={displaySettingsButtonRef}>
<TextAa size={24} color={theme.colors.readerFont.toString()} />
</SpanBox>
</TooltipWrapped>
</Button>
<MenuSeparator layout={props.layout} />
</>
)}
<SpanBox css={{
'display': 'flex',
'@smDown': {
display: 'none',
}}}
<Box
css={{
display: 'flex',
alignItems: 'center',
flexDirection: props.layout == 'side' ? 'column' : 'row',
justifyContent: props.layout == 'side' ? 'center' : 'flex-end',
gap: props.layout == 'side' ? '8px' : '24px',
paddingTop: '6px',
}}
>
{props.article ? (
<ActionDropdown
layout={props.layout}
triggerElement={
{props.showReaderDisplaySettings && (
<>
<Button
style="articleActionIcon"
onClick={() =>
readerSettings.setShowEditDisplaySettingsModal(true)
}
>
<TooltipWrapped
tooltipContent="Edit labels"
tooltipContent="Adjust Display Settings"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<SpanBox ref={displaySettingsButtonRef}>
<TextAa
size={24}
color={theme.colors.readerFont.toString()}
/>
</SpanBox>
</TooltipWrapped>
</Button>
<MenuSeparator layout={props.layout} />
</>
)}
<SpanBox
css={{
display: 'flex',
'@smDown': {
display: 'none',
},
}}
>
{props.article ? (
<ActionDropdown
layout={props.layout}
triggerElement={
<TooltipWrapped
tooltipContent="Edit labels"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<TagSimple
size={24}
color={theme.colors.readerFont.toString()}
/>
</TooltipWrapped>
}
>
<SetLabelsControl
provider={props.article}
save={(labels: Label[]) => {
if (props.article?.id) {
return setLabelsMutation(
props.article?.id,
labels.map((label) => label.id)
)
}
return Promise.resolve(undefined)
}}
onSave={(labels) => {
props.articleActionHandler('refreshLabels', labels)
}}
/>
</ActionDropdown>
) : (
<Button
style="articleActionIcon"
css={{
'@smDown': {
display: 'flex',
},
}}
>
<TagSimple size={24} color={theme.colors.readerFont.toString()} />
</TooltipWrapped>
}
>
<SetLabelsControl
article={props.article}
linkId={props.article.linkId}
labels={props.article.labels}
articleActionHandler={props.articleActionHandler}
/>
</ActionDropdown>
) : (
<Button style='articleActionIcon'
</Button>
)}
</SpanBox>
<Button
style="articleActionIcon"
onClick={() => props.articleActionHandler('setLabels')}
css={{
display: 'none',
'@smDown': {
display: 'flex',
},
}}>
<TagSimple size={24} color={theme.colors.readerFont.toString()} />
</Button>
)}
</SpanBox>
<Button style='articleActionIcon'
onClick={() => props.articleActionHandler('setLabels')}
css={{
'display': 'none',
'@smDown': {
display: 'flex',
},
}}>
}}
>
<TagSimple size={24} color={theme.colors.readerFont.toString()} />
</Button>
</Button>
<Button style='articleActionIcon' onClick={() => props.articleActionHandler('showHighlights')}>
<TooltipWrapped
tooltipContent="View Highlights"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
<Button
style="articleActionIcon"
onClick={() => props.articleActionHandler('showHighlights')}
>
<HighlighterCircle size={24} color={theme.colors.readerFont.toString()} />
</TooltipWrapped>
</Button>
<TooltipWrapped
tooltipContent="View Highlights"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<HighlighterCircle
size={24}
color={theme.colors.readerFont.toString()}
/>
</TooltipWrapped>
</Button>
<MenuSeparator layout={props.layout} />
<MenuSeparator layout={props.layout} />
<Button
style="articleActionIcon"
onClick={() => {
props.articleActionHandler('delete')
}}
>
<TooltipWrapped
tooltipContent="Delete"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
<Button
style="articleActionIcon"
onClick={() => {
props.articleActionHandler('delete')
}}
>
<Trash
size={24}
color={theme.colors.readerFont.toString()}
/>
</TooltipWrapped>
</Button>
<TooltipWrapped
tooltipContent="Delete"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Trash size={24} color={theme.colors.readerFont.toString()} />
</TooltipWrapped>
</Button>
{!props.article?.isArchived ? (
{!props.article?.isArchived ? (
<Button
style="articleActionIcon"
onClick={() => props.articleActionHandler('archive')}
@ -184,27 +234,26 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element
tooltipContent="Unarchive"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Tray
size={24}
color={theme.colors.readerFont.toString()}
/>
<Tray size={24} color={theme.colors.readerFont.toString()} />
</TooltipWrapped>
</Button>
)}
{/* <MenuSeparator layout={props.layout} />
{/* <MenuSeparator layout={props.layout} />
<Button style='articleActionIcon'>
<DotsThree size={24} color={theme.colors.readerFont.toString()} />
</Button> */}
</Box>
{readerSettings.showEditDisplaySettingsModal && (
<DisplaySettingsModal
centerX={props.layout != 'side'}
triggerElementRef={displaySettingsButtonRef}
articleActionHandler={props.articleActionHandler}
onOpenChange={() => readerSettings.setShowEditDisplaySettingsModal(false)}
/>
)}
</Box>
{readerSettings.showEditDisplaySettingsModal && (
<DisplaySettingsModal
centerX={props.layout != 'side'}
triggerElementRef={displaySettingsButtonRef}
articleActionHandler={props.articleActionHandler}
onOpenChange={() =>
readerSettings.setShowEditDisplaySettingsModal(false)
}
/>
)}
</>
)
}

View file

@ -22,6 +22,9 @@ import { StyledTextArea } from '../../elements/StyledTextArea'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { DotsThree } from 'phosphor-react'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { SetLabelsModal } from './SetLabelsModal'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
type HighlightsModalProps = {
highlights: Highlight[]
@ -33,6 +36,10 @@ type HighlightsModalProps = {
export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
useState<undefined | string>(undefined)
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
undefined
)
const [, updateState] = useState({})
return (
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
@ -42,7 +49,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' }}
>
<VStack distribution="start" css={{ height: '100%' }}>
<ModalTitleBar title="Notebook" onOpenChange={props.onOpenChange} />
@ -53,6 +60,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
highlight={highlight}
showDelete={!!props.deleteHighlightAction}
scrollToHighlight={props.scrollToHighlight}
setSetLabelsTarget={setLabelsTarget}
setShowConfirmDeleteHighlightId={
setShowConfirmDeleteHighlightId
}
@ -73,7 +81,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
</Box>
</VStack>
</ModalContent>
{showConfirmDeleteHighlightId ? (
{showConfirmDeleteHighlightId && (
<ConfirmationModal
message={'Are you sure you want to delete this highlight?'}
onAccept={() => {
@ -90,7 +98,25 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
/>
}
/>
) : null}
)}
{labelsTarget && (
<SetLabelsModal
provider={labelsTarget}
onOpenChange={function (open: boolean): void {
setLabelsTarget(undefined)
}}
onSave={function (labels: Label[] | undefined): void {
updateState({})
}}
save={function (labels: Label[]): Promise<Label[] | undefined> {
const result = setLabelsForHighlight(
labelsTarget.id,
labels.map((label) => label.id)
)
return result
}}
/>
)}
</ModalRoot>
)
}
@ -100,6 +126,8 @@ type ModalHighlightViewProps = {
showDelete: boolean
scrollToHighlight?: (arg: string) => void
deleteHighlightAction: () => void
setSetLabelsTarget: (highlight: Highlight) => void
setShowConfirmDeleteHighlightId: (id: string | undefined) => void
}
@ -125,7 +153,12 @@ function ModalHighlightView(props: ModalHighlightViewProps): JSX.Element {
}}
title="Copy"
/>
{/* <DropdownOption onSelect={() => {}} title="Labels" /> */}
<DropdownOption
onSelect={() => {
props.setSetLabelsTarget(props.highlight)
}}
title="Labels"
/>
<DropdownOption
onSelect={() => {
props.setShowConfirmDeleteHighlightId(props.highlight.id)

View file

@ -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<Label[] | undefined>
}
type HeaderProps = {
@ -57,12 +58,16 @@ function Header(props: HeaderProps): JSX.Element {
}, [props.focused])
return (
<VStack css={{ width: '100%', my: '0px', borderBottom: '1px solid $grayBorder'}}>
<Box css={{
width: '100%',
my: '14px',
px: '14px',
}}>
<VStack
css={{ width: '100%', my: '0px', borderBottom: '1px solid $grayBorder' }}
>
<Box
css={{
width: '100%',
my: '14px',
px: '14px',
}}
>
<FormInput
ref={inputRef}
type="text"
@ -91,8 +96,9 @@ function Header(props: HeaderProps): JSX.Element {
},
}}
/>
</Box>
</VStack>)
</Box>
</VStack>
)
}
type LabelListItemProps = {
@ -128,21 +134,61 @@ function LabelListItem(props: LabelListItemProps): JSX.Element {
ref.current?.blur()
}}
>
<input autoFocus={focused} hidden={true} type="checkbox" checked={selected} readOnly />
<Box css={{ pl: '10px', width: '32px', display: 'flex', alignItems: 'center' }}>
{selected && <Check size={15} color={theme.colors.grayText.toString()} weight='bold' />}
<input
autoFocus={focused}
hidden={true}
type="checkbox"
checked={selected}
readOnly
/>
<Box
css={{
pl: '10px',
width: '32px',
display: 'flex',
alignItems: 'center',
}}
>
{selected && (
<Check
size={15}
color={theme.colors.grayText.toString()}
weight="bold"
/>
)}
</Box>
<Box css={{ width: '30px', height: '100%', display: 'flex', alignItems: 'center' }}>
<Circle width={22} height={22} color={label.color} weight='fill' />
<Box
css={{
width: '30px',
height: '100%',
display: 'flex',
alignItems: 'center',
}}
>
<Circle width={22} height={22} color={label.color} weight="fill" />
</Box>
<Box css={{ overflow: 'clip', height: '100%', display: 'flex', alignItems: 'center' }}>
<Box
css={{
overflow: 'clip',
height: '100%',
display: 'flex',
alignItems: 'center',
}}
>
<StyledText style="caption">{label.name}</StyledText>
</Box>
<Box css={{ pl: '10px', width: '40px', marginLeft: 'auto', display: 'flex', alignItems: 'center' }}>
{selected && <CrossIcon
size={14}
strokeColor={theme.colors.grayText.toString()}
/>}
<Box
css={{
pl: '10px',
width: '40px',
marginLeft: 'auto',
display: 'flex',
alignItems: 'center',
}}
>
{selected && (
<CrossIcon size={14} strokeColor={theme.colors.grayText.toString()} />
)}
</Box>
</StyledLabel>
)
@ -164,9 +210,11 @@ function Footer(props: FooterProps): JSX.Element {
return (
<HStack
ref={ref}
distribution="start" alignment="center"
distribution="start"
alignment="center"
css={{
width: '100%', height: '42px',
width: '100%',
height: '42px',
bg: props.focused ? '$grayBgActive' : 'unset',
color: theme.colors.grayText.toString(),
'a:link': {
@ -176,12 +224,14 @@ function Footer(props: FooterProps): JSX.Element {
color: theme.colors.grayText.toString(),
},
}}
>
<SpanBox css={{ display: 'flex', fontSize: '12px', padding: '33px', gap: '8px' }}>
<PencilSimple size={18} color={theme.colors.grayText.toString()} />
<Link href="/settings/labels">Edit labels</Link>
</SpanBox>
</HStack>
>
<SpanBox
css={{ display: 'flex', fontSize: '12px', padding: '33px', gap: '8px' }}
>
<PencilSimple size={18} color={theme.colors.grayText.toString()} />
<Link href="/settings/labels">Edit labels</Link>
</SpanBox>
</HStack>
)
}
@ -189,41 +239,45 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const router = useRouter()
const [filterText, setFilterText] = useState('')
const { labels, revalidate } = useGetLabelsQuery()
const [selectedLabels, setSelectedLabels] = useState<Label[]>(props.labels || [])
const [selectedLabels, setSelectedLabels] = useState<Label[]>(
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)
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 +289,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<number | undefined>(undefined)
const handleKeyDown = useCallback(async (event: React.KeyboardEvent<HTMLInputElement>) => {
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<number | undefined>(
undefined
)
const handleKeyDown = useCallback(
async (event: React.KeyboardEvent<HTMLInputElement>) => {
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 +373,28 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
<VStack
distribution="start"
onKeyDown={handleKeyDown}
css={{
css={{
p: '0',
width: '100%',
}}>
}}
>
<Header
focused={focusedIndex === undefined}
resetFocusedIndex={() => setFocusedIndex(undefined)}
setFilterText={setFilterText} filterText={filterText}
setFilterText={setFilterText}
filterText={filterText}
/>
<VStack
distribution="start"
alignment="start"
css={{ flexGrow: '1', overflow: 'scroll', width: '100%', height: '100%', maxHeight: '294px'
}}>
css={{
flexGrow: '1',
overflow: 'scroll',
width: '100%',
height: '100%',
maxHeight: '294px',
}}
>
{filteredLabels &&
filteredLabels.map((label, idx) => (
<LabelListItem
@ -327,23 +407,30 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
))}
</VStack>
{filterText && (
<Button style='modalOption' css={{
pl: '26px',
color: theme.colors.grayText.toString(),
height: '42px',
borderBottom: '1px solid $grayBorder',
bg: focusedIndex === filteredLabels.length ? '$grayBgActive' : 'unset',
}}
<Button
style="modalOption"
css={{
pl: '26px',
color: theme.colors.grayText.toString(),
height: '42px',
borderBottom: '1px solid $grayBorder',
bg:
focusedIndex === filteredLabels.length
? '$grayBgActive'
: 'unset',
}}
onClick={createLabelFromFilterText}
>
<HStack alignment='center' distribution='start' css={{ gap: '8px' }}>
<HStack alignment="center" distribution="start" css={{ gap: '8px' }}>
<Plus size={18} color={theme.colors.grayText.toString()} />
<SpanBox css={{ fontSize: '12px' }}>{`Create new label "${filterText}"`}</SpanBox>
<SpanBox
css={{ fontSize: '12px' }}
>{`Create new label "${filterText}"`}</SpanBox>
</HStack>
</Button>
)}
<Footer focused={focusedIndex === (filteredLabels.length + 1)} />
<Footer focused={focusedIndex === filteredLabels.length + 1} />
</VStack>
)
}
}

View file

@ -2,22 +2,22 @@ import { Label } from '../../../lib/networking/fragments/labelFragment'
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
import { Button } from '../../elements/Button'
import { CrossIcon } from '../../elements/images/CrossIcon'
import { HStack, VStack } from '../../elements/LayoutPrimitives'
import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import {
ModalRoot,
ModalOverlay,
ModalContent,
ModalTitleBar,
} from '../../elements/ModalPrimitives'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import { SetLabelsControl } from './SetLabelsControl'
import { LabelsProvider, SetLabelsControl } from './SetLabelsControl'
type SetLabelsModalProps = {
linkId: string
labels: Label[] | undefined
article?: ArticleAttributes
provider: LabelsProvider
onOpenChange: (open: boolean) => void
articleActionHandler: (action: string, arg?: unknown) => void
onSave: (labels: Label[] | undefined) => void
save: (labels: Label[]) => Promise<Label[] | undefined>
}
export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
@ -31,27 +31,10 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
props.onOpenChange(false)
}}
>
<VStack css={{ width: '100%' }}>
<HStack
distribution="between"
alignment="center"
css={{ width: '100%' }}
>
<StyledText style="modalHeadline" css={{ pl: '16px' }}>Labels</StyledText>
<Button
css={{ pt: '16px', pr: '16px' }}
style="ghost"
onClick={() => {
props.onOpenChange(false)
}}
tabIndex={-1}
>
<CrossIcon
size={14}
strokeColor={theme.colors.grayText.toString()}
/>
</Button>
</HStack>
<VStack distribution="start" css={{ height: '100%' }}>
<SpanBox css={{ p: '16px', width: '100%' }}>
<ModalTitleBar title="Labels" onOpenChange={props.onOpenChange} />
</SpanBox>
<SetLabelsControl {...props} />
</VStack>
</ModalContent>

View file

@ -54,6 +54,7 @@ import {
} from '../../../lib/networking/queries/typeaheadSearch'
import axios from 'axios'
import { uploadFileRequestMutation } from '../../../lib/networking/mutations/uploadFileMutation'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
@ -100,14 +101,17 @@ export function HomeFeedContainer(): JSX.Element {
const gridContainerRef = useRef<HTMLDivElement>(null)
const [shareTarget, setShareTarget] =
useState<LibraryItem | undefined>(undefined)
const [shareTarget, setShareTarget] = useState<LibraryItem | undefined>(
undefined
)
const [snoozeTarget, setSnoozeTarget] =
useState<LibraryItem | undefined>(undefined)
const [snoozeTarget, setSnoozeTarget] = useState<LibraryItem | undefined>(
undefined
)
const [labelsTarget, setLabelsTarget] =
useState<LibraryItem | undefined>(undefined)
const [labelsTarget, setLabelsTarget] = useState<LibraryItem | undefined>(
undefined
)
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
const [showEditTitleModal, setShowEditTitleModal] = useState(false)
@ -1200,18 +1204,22 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
)}
{props.labelsTarget?.node.id && (
<SetLabelsModal
linkId={props.labelsTarget.node.id}
labels={props.labelsTarget.node.labels}
articleActionHandler={(action, value) => {
switch (action) {
case 'refreshLabels':
if (props.labelsTarget) {
props.labelsTarget.node.labels = value as Label[] | undefined
updateState({})
}
break
provider={props.labelsTarget.node}
onSave={(labels: Label[] | undefined) => {
if (props.labelsTarget) {
props.labelsTarget.node.labels = labels
updateState({})
}
}}
save={(labels: Label[]) => {
if (props.labelsTarget?.node.id) {
return setLabelsMutation(
props.labelsTarget.node.id,
labels.map((label) => label.id)
)
}
return Promise.resolve(undefined)
}}
onOpenChange={() => {
if (props.labelsTarget) {
const activate = props.labelsTarget

View file

@ -1,4 +1,5 @@
import { gql } from 'graphql-request'
import { Label, labelFragment } from './labelFragment'
export const highlightFragment = gql`
fragment HighlightFields on Highlight {
@ -12,6 +13,12 @@ export const highlightFragment = gql`
createdByMe
updatedAt
sharedAt
labels {
id
name
color
createdAt
}
}
`
@ -26,6 +33,7 @@ export type Highlight = {
createdByMe: boolean
updatedAt: string
sharedAt: string
labels?: Label[]
}
export type User = {

View file

@ -0,0 +1,43 @@
import { gql } from 'graphql-request'
import { Label, labelFragment } from '../fragments/labelFragment'
import { gqlFetcher } from '../networkHelpers'
export type SetLabelsForHighlightResult = {
setLabelsForHighlight: SetLabelsForHighlight
errorCodes?: unknown[]
}
type SetLabelsForHighlight = {
labels: Label[]
}
export async function setLabelsForHighlight(
highlightId: string,
labelIds: string[]
): Promise<Label[] | undefined> {
const mutation = gql`
mutation SetLabelsForHighlight($input: SetLabelsForHighlightInput!) {
setLabelsForHighlight(input: $input) {
... on SetLabelsSuccess {
labels {
...LabelFields
}
}
... on SetLabelsError {
errorCodes
}
}
}
${labelFragment}
`
try {
const data = (await gqlFetcher(mutation, {
input: { highlightId, labelIds },
})) as SetLabelsForHighlightResult
return data.errorCodes ? undefined : data.setLabelsForHighlight.labels
} catch (error) {
console.log('setLabelsForHighlightInput error', error)
return undefined
}
}

View file

@ -32,7 +32,9 @@ export async function setLabelsMutation(
`
try {
const data = await gqlFetcher(mutation, { input: { pageId, labelIds } }) as SetLabelsResult
const data = (await gqlFetcher(mutation, {
input: { pageId, labelIds },
})) as SetLabelsResult
return data.errorCodes ? undefined : data.setLabels.labels
} catch (error) {
console.log('SetLabelsOutput error', error)

View file

@ -37,6 +37,7 @@ import { SkeletonArticleContainer } from '../../../components/templates/article/
import { useRegisterActions } from 'kbar'
import { deleteLinkMutation } from '../../../lib/networking/mutations/deleteLinkMutation'
import { ConfirmationModal } from '../../../components/patterns/ConfirmationModal'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
() => import('./../../../components/templates/article/PdfArticleContainer'),
@ -365,10 +366,16 @@ export default function Home(): JSX.Element {
{article && readerSettings.showSetLabelsModal && (
<SetLabelsModal
article={article}
linkId={article.id}
labels={article.labels}
articleActionHandler={actionHandler}
provider={article}
onSave={(labels: Label[] | undefined) => {
actionHandler('refreshLabels', labels)
}}
save={(labels: Label[]) => {
return setLabelsMutation(
article.linkId,
labels.map((label) => label.id)
)
}}
onOpenChange={() => readerSettings.setShowSetLabelsModal(false)}
/>
)}

View file

@ -28,10 +28,8 @@ import {
GenericTableCardProps,
LabelColorHex,
} from '../../utils/settings-page/labels/types'
import { labelColorObjects, } from '../../utils/settings-page/labels/labelColorObjects'
import {
TooltipWrapped
} from '../../components/elements/Tooltip'
import { labelColorObjects } from '../../utils/settings-page/labels/labelColorObjects'
import { TooltipWrapped } from '../../components/elements/Tooltip'
import { LabelColorDropdown } from '../../components/elements/LabelColorDropdown'
import {
Dropdown,
@ -157,7 +155,9 @@ export default function LabelsPage(): JSX.Element {
const [descriptionInputText, setDescriptionInputText] = useState<string>('')
const [isCreateMode, setIsCreateMode] = useState<boolean>(false)
const [windowWidth, setWindowWidth] = useState<number>(0)
const [confirmRemoveLabelId, setConfirmRemoveLabelId] = useState<string | null>(null)
const [confirmRemoveLabelId, setConfirmRemoveLabelId] = useState<
string | null
>(null)
const breakpoint = 768
applyStoredTheme(false)
@ -206,14 +206,13 @@ export default function LabelsPage(): JSX.Element {
revalidate()
}
const onEditPress = (label : Label | null) => {
const onEditPress = (label: Label | null) => {
if (label) {
setEditingLabelId(label.id)
setNameInputText(label.name)
setDescriptionInputText(label.description || '')
setLabelColorHex({ rowId: '', value: label.color })
}
else {
} else {
resetLabelState()
}
}
@ -261,25 +260,23 @@ export default function LabelsPage(): JSX.Element {
>
{confirmRemoveLabelId ? (
<ConfirmationModal
message={'Are you sure? Deleting a label will remove it from all pages.'}
message={
'Are you sure? Deleting a label will remove it from all pages.'
}
onAccept={() => {
onDeleteLabel(confirmRemoveLabelId)
setConfirmRemoveLabelId(null)}
}
setConfirmRemoveLabelId(null)
}}
onOpenChange={() => setConfirmRemoveLabelId(null)}
/>
) : null}
<HeaderWrapper>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<Box>
<StyledText
style="fixedHeadline"
>
Labels{' '}
</StyledText>
<StyledText style="fixedHeadline">Labels </StyledText>
</Box>
<InfoLink href="/help/labels" />
<Box css={{display: 'flex', justifyContent: 'flex-end' }}>
<Box css={{ display: 'flex', justifyContent: 'flex-end' }}>
{isCreateMode ? null : (
<>
<Button
@ -294,21 +291,25 @@ export default function LabelsPage(): JSX.Element {
alignItems: 'center',
}}
>
<SpanBox css={{
display: 'none',
'@md': {
display: 'flex',
},
}}>
<SpanBox
css={{
display: 'none',
'@md': {
display: 'flex',
},
}}
>
<SpanBox>Add Label</SpanBox>
</SpanBox>
<SpanBox css={{
p: '0',
display: 'flex',
'@md': {
display: 'none',
},
}}>
<SpanBox
css={{
p: '0',
display: 'flex',
'@md': {
display: 'none',
},
}}
>
<Plus size={24} />
</SpanBox>
</Button>
@ -363,46 +364,58 @@ export default function LabelsPage(): JSX.Element {
</>
{labels
? labels.map((label, i) => {
const isLastChild = i === labels.length - 1
const isFirstChild = i === 0
const cardProps = {
label: label,
labelColorHex: labelColorHex,
editingLabelId: editingLabelId,
isCreateMode: isCreateMode,
isLastChild: isLastChild,
isFirstChild: isFirstChild,
handleGenerateRandomColor: handleGenerateRandomColor,
setEditingLabelId: setEditingLabelId,
setLabelColorHex: setLabelColorHex,
deleteLabel: deleteLabel,
nameInputText: nameInputText,
descriptionInputText: descriptionInputText,
setNameInputText: setNameInputText,
setDescriptionInputText: setDescriptionInputText,
setIsCreateMode: setIsCreateMode,
createLabel: createLabel,
resetState: resetLabelState,
updateLabel: updateLabel,
}
if (editingLabelId == label.id) {
if (windowWidth >= breakpoint) {
return <DesktopEditCard {...cardProps} />
} else {
return <MobileEditCard {...cardProps} />
const isLastChild = i === labels.length - 1
const isFirstChild = i === 0
const cardProps = {
label: label,
labelColorHex: labelColorHex,
editingLabelId: editingLabelId,
isCreateMode: isCreateMode,
isLastChild: isLastChild,
isFirstChild: isFirstChild,
handleGenerateRandomColor: handleGenerateRandomColor,
setEditingLabelId: setEditingLabelId,
setLabelColorHex: setLabelColorHex,
deleteLabel: deleteLabel,
nameInputText: nameInputText,
descriptionInputText: descriptionInputText,
setNameInputText: setNameInputText,
setDescriptionInputText: setDescriptionInputText,
setIsCreateMode: setIsCreateMode,
createLabel: createLabel,
resetState: resetLabelState,
updateLabel: updateLabel,
}
}
return (<GenericTableCard key={label.id} {...cardProps} onEditPress={onEditPress} />)
}) : null}
if (editingLabelId == label.id) {
if (windowWidth >= breakpoint) {
return <DesktopEditCard {...cardProps} />
} else {
return <MobileEditCard {...cardProps} />
}
}
return (
<GenericTableCard
key={label.id}
{...cardProps}
onEditPress={onEditPress}
/>
)
})
: null}
</VStack>
<Box css={{ height: '120px' }} />
</PrimaryLayout>
)
}
function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean; isFirstChild?: boolean}) {
function GenericTableCard(
props: GenericTableCardProps & {
isLastChild?: boolean
isFirstChild?: boolean
}
) {
const {
label,
isLastChild,
@ -423,10 +436,10 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
onEditPress,
resetState,
} = props
const showInput =
editingLabelId === label?.id || (isCreateMode && !label)
const labelColor = editingLabelId === label?.id ? labelColorHex.value : label?.color
const iconColor = isDarkTheme() ? '#D8D7D5': '#5F5E58'
const showInput = editingLabelId === label?.id || (isCreateMode && !label)
const labelColor =
editingLabelId === label?.id ? labelColorHex.value : label?.color
const iconColor = isDarkTheme() ? '#D8D7D5' : '#5F5E58'
const handleEdit = () => {
editingLabelId && updateLabel(editingLabelId)
@ -477,7 +490,12 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
>
<Trash size={24} color="#AA2D11" />
<StyledText
css={{ m: '0px', fontSize: '$5', marginLeft: '$2', color: '#AA2D11' }}
css={{
m: '0px',
fontSize: '$5',
marginLeft: '$2',
color: '#AA2D11',
}}
>
Delete
</StyledText>
@ -490,15 +508,16 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
return (
<TableCard
css={{
'&:hover': {
background: 'rgba(255, 234, 159, 0.12)',
},
borderTopLeftRadius: isFirstChild ? '5px' : '',
borderTopRightRadius: isFirstChild ? '5px' : '',
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}>
css={{
'&:hover': {
background: 'rgba(255, 234, 159, 0.12)',
},
borderTopLeftRadius: isFirstChild ? '5px' : '',
borderTopRightRadius: isFirstChild ? '5px' : '',
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}
>
<TableCardBox
css={{
display: 'grid',
@ -530,17 +549,22 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
padding: '0 5px',
}}
>
{(showInput && !label) ? null : (
<HStack alignment="center" css={{ ml: '16px', '@smDown': { ml: '0px' } }}>
{showInput && !label ? null : (
<HStack
alignment="center"
css={{ ml: '16px', '@smDown': { ml: '0px' } }}
>
<LabelChip color={labelColor || ''} text={label?.name || ''} />
</HStack>
)}
{(showInput && !label) ? (
<SpanBox css={{
'@smDown': {
display: 'none',
},
}}>
{showInput && !label ? (
<SpanBox
css={{
'@smDown': {
display: 'none',
},
}}
>
<Input
type="text"
value={nameInputText}
@ -565,7 +589,7 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
{showInput ? (
<Input
type="text"
placeholder='Description (optional)'
placeholder="Description (optional)"
value={descriptionInputText}
onChange={(event) => setDescriptionInputText(event.target.value)}
autoFocus={!!label}
@ -576,9 +600,14 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
css={{
color: '$grayTextContrast',
fontSize: '14px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{editingLabelId === label?.id ? descriptionInputText : label?.description || ''}
{editingLabelId === label?.id
? descriptionInputText
: label?.description || ''}
</StyledText>
)}
</HStack>
@ -603,31 +632,33 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
/>
)}
{showInput && (
<TooltipWrapped
tooltipSide={'top'}
tooltipContent='Random Color'
arrowStyles={{fill: '#F9D354'}}
style={{backgroundColor: '#F9D354', color: 'black'}}
>
<Box css={{py: 4}}>
<IconButton
style="ctaWhite"
css={{
mr: '$1',
width: 40,
height: 40,
background: '$labelButtonsBg',
}}
onClick={() => handleGenerateRandomColor(label?.id)}
disabled={!(isCreateMode && !label) && !(editingLabelId === label?.id)}
>
<ArrowClockwise size={16} color={iconColor} />
</IconButton>
</Box>
</TooltipWrapped>
<TooltipWrapped
tooltipSide={'top'}
tooltipContent="Random Color"
arrowStyles={{ fill: '#F9D354' }}
style={{ backgroundColor: '#F9D354', color: 'black' }}
>
<Box css={{ py: 4 }}>
<IconButton
style="ctaWhite"
css={{
mr: '$1',
width: 40,
height: 40,
background: '$labelButtonsBg',
}}
onClick={() => handleGenerateRandomColor(label?.id)}
disabled={
!(isCreateMode && !label) && !(editingLabelId === label?.id)
}
>
<ArrowClockwise size={16} color={iconColor} />
</IconButton>
</Box>
</TooltipWrapped>
)}
{!showInput && (
<Box css={{ marginLeft: 'auto', '@md': { display: 'none' }}}>
<Box css={{ marginLeft: 'auto', '@md': { display: 'none' } }}>
{moreActionsButton()}
</Box>
)}
@ -647,7 +678,7 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
{showInput && (
<Input
type="text"
placeholder='What this label is about...'
placeholder="What this label is about..."
value={descriptionInputText}
onChange={(event) => setDescriptionInputText(event.target.value)}
autoFocus={!!label}
@ -662,57 +693,55 @@ function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean
padding: '0px 8px',
}}
>
{editingLabelId === label?.id || !label ? (
<>
<Button
style="plainIcon"
css={{ mr: '$1' }}
onClick={() => {
resetState()
}}
>
Cancel
</Button>
<Button
style="ctaDarkYellow"
css={{ my: '0px', mr: '$1' }}
onClick={() =>
label ? handleEdit() : createLabel()
}
>
Save
</Button>
</>
) : (
<HStack
distribution="end"
alignment="end"
css={{
display: 'none',
'@md': {
display: 'flex',
width: '100%'
},
{editingLabelId === label?.id || !label ? (
<>
<Button
style="plainIcon"
css={{ mr: '$1' }}
onClick={() => {
resetState()
}}
>
<IconButton
style="ctaWhite"
css={{ mr: '$1', background: '$labelButtonsBg' }}
onClick={() => onEditPress(label)}
disabled={isCreateMode}
>
<PencilSimple size={16} color={iconColor} />
</IconButton>
<IconButton
style="ctaWhite"
css={{ mr: '$1', background: '$labelButtonsBg' }}
onClick={() => deleteLabel(label.id)}
disabled={isCreateMode}
>
<Trash size={16} color={iconColor} />
</IconButton>
</HStack>
)}
Cancel
</Button>
<Button
style="ctaDarkYellow"
css={{ my: '0px', mr: '$1' }}
onClick={() => (label ? handleEdit() : createLabel())}
>
Save
</Button>
</>
) : (
<HStack
distribution="end"
alignment="end"
css={{
display: 'none',
'@md': {
display: 'flex',
width: '100%',
},
}}
>
<IconButton
style="ctaWhite"
css={{ mr: '$1', background: '$labelButtonsBg' }}
onClick={() => onEditPress(label)}
disabled={isCreateMode}
>
<PencilSimple size={16} color={iconColor} />
</IconButton>
<IconButton
style="ctaWhite"
css={{ mr: '$1', background: '$labelButtonsBg' }}
onClick={() => deleteLabel(label.id)}
disabled={isCreateMode}
>
<Trash size={16} color={iconColor} />
</IconButton>
</HStack>
)}
</HStack>
</TableCardBox>
</TableCard>
@ -745,12 +774,13 @@ function MobileEditCard(props: any) {
return (
<TableCard
css={{
borderTopLeftRadius: isFirstChild ? '5px' : '',
borderTopRightRadius: isFirstChild ? '5px' : '',
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}>
css={{
borderTopLeftRadius: isFirstChild ? '5px' : '',
borderTopRightRadius: isFirstChild ? '5px' : '',
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}
>
<VStack distribution="center" css={{ width: '100%', margin: '8px' }}>
{nameInputText && (
<SpanBox css={{ ml: '-2px', mt: '0px' }}>
@ -773,7 +803,7 @@ function MobileEditCard(props: any) {
setLabelColorHex={setLabelColorHex}
/>
<TextArea
placeholder='Description (optional)'
placeholder="Description (optional)"
value={descriptionInputText}
onChange={(event) => setDescriptionInputText(event.target.value)}
rows={5}
@ -795,7 +825,7 @@ function MobileEditCard(props: any) {
<Button
style="ctaDarkYellow"
css={{ mr: '$1' }}
onClick={() => label ? handleEdit() : createLabel()}
onClick={() => (label ? handleEdit() : createLabel())}
>
Save
</Button>
@ -830,15 +860,19 @@ function DesktopEditCard(props: any) {
}
return (
<TableCard
css={{
width: '100%',
borderTopLeftRadius: isFirstChild ? '5px' : '',
borderTopRightRadius: isFirstChild ? '5px' : '',
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}>
<VStack distribution="center" css={{ width: '100%', my: '8px', ml: '8px', mr: '0px' }}>
<TableCard
css={{
width: '100%',
borderTopLeftRadius: isFirstChild ? '5px' : '',
borderTopRightRadius: isFirstChild ? '5px' : '',
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}
>
<VStack
distribution="center"
css={{ width: '100%', my: '8px', ml: '8px', mr: '0px' }}
>
{nameInputText && (
<SpanBox css={{ px: '11px', mt: '3px' }}>
<LabelChip color={labelColorHex.value} text={nameInputText} />
@ -849,49 +883,49 @@ function DesktopEditCard(props: any) {
alignment="center"
css={{ pt: '6px', px: '13px', width: '100%', gap: '16px' }}
>
<Input
type="text"
value={nameInputText}
onChange={(event) => setNameInputText(event.target.value)}
autoFocus
/>
<LabelColorDropdown
isCreateMode={isCreateMode && !label}
canEdit={editingLabelId === label?.id}
labelColorHexRowId={labelColorHex.rowId}
labelColorHexValue={labelColorHex.value}
labelId={label?.id || ''}
labelColor={label?.color || 'custom color'}
setLabelColorHex={setLabelColorHex}
/>
<Input
type="text"
placeholder='Description (optional)'
value={descriptionInputText}
onChange={(event) => setDescriptionInputText(event.target.value)}
/>
<HStack
distribution="end"
alignment="center"
css={{ marginLeft: 'auto', width: '100%' }}
>
<Button
style="ctaOutlineYellow"
css={{ mr: '12px' }}
onClick={() => {
resetState()
}}
<Input
type="text"
value={nameInputText}
onChange={(event) => setNameInputText(event.target.value)}
autoFocus
/>
<LabelColorDropdown
isCreateMode={isCreateMode && !label}
canEdit={editingLabelId === label?.id}
labelColorHexRowId={labelColorHex.rowId}
labelColorHexValue={labelColorHex.value}
labelId={label?.id || ''}
labelColor={label?.color || 'custom color'}
setLabelColorHex={setLabelColorHex}
/>
<Input
type="text"
placeholder="Description (optional)"
value={descriptionInputText}
onChange={(event) => setDescriptionInputText(event.target.value)}
/>
<HStack
distribution="end"
alignment="center"
css={{ marginLeft: 'auto', width: '100%' }}
>
Cancel
</Button>
<Button
style="ctaDarkYellow"
css={{ }}
onClick={() => label ? handleEdit() : createLabel()}
>
Save
</Button>
</HStack>
<Button
style="ctaOutlineYellow"
css={{ mr: '12px' }}
onClick={() => {
resetState()
}}
>
Cancel
</Button>
<Button
style="ctaDarkYellow"
css={{}}
onClick={() => (label ? handleEdit() : createLabel())}
>
Save
</Button>
</HStack>
</HStack>
</VStack>
</TableCard>