mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1500 from omnivore-app/feat/web-highlight-labels
Allow setting labels on highlights on the web
This commit is contained in:
commit
ce49287444
12 changed files with 787 additions and 528 deletions
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue