[Omn-190] - [Settings View] - Labels

This commit is contained in:
gitstart-omnivore
2022-03-30 12:24:29 +00:00
15 changed files with 1541 additions and 80 deletions

View File

@ -792,6 +792,7 @@ export type Mutation = {
signup: SignupResult;
updateHighlight: UpdateHighlightResult;
updateHighlightReply: UpdateHighlightReplyResult;
updateLabel: UpdateLabelResult;
updateLinkShareInfo: UpdateLinkShareInfoResult;
updateReminder: UpdateReminderResult;
updateSharedComment: UpdateSharedCommentResult;
@ -966,6 +967,11 @@ export type MutationUpdateHighlightReplyArgs = {
};
export type MutationUpdateLabelArgs = {
input: UpdateLabelInput;
};
export type MutationUpdateLinkShareInfoArgs = {
input: UpdateLinkShareInfoInput;
};
@ -1577,6 +1583,32 @@ export type UpdateHighlightSuccess = {
highlight: Highlight;
};
export type UpdateLabelError = {
__typename?: 'UpdateLabelError';
errorCodes: Array<UpdateLabelErrorCode>;
};
export enum UpdateLabelErrorCode {
BadRequest = 'BAD_REQUEST',
Forbidden = 'FORBIDDEN',
NotFound = 'NOT_FOUND',
Unauthorized = 'UNAUTHORIZED'
}
export type UpdateLabelInput = {
color: Scalars['String'];
description?: InputMaybe<Scalars['String']>;
labelId: Scalars['ID'];
name: Scalars['String'];
};
export type UpdateLabelResult = UpdateLabelError | UpdateLabelSuccess;
export type UpdateLabelSuccess = {
__typename?: 'UpdateLabelSuccess';
label: Label;
};
export type UpdateLinkShareInfoError = {
__typename?: 'UpdateLinkShareInfoError';
errorCodes: Array<UpdateLinkShareInfoErrorCode>;
@ -2095,6 +2127,11 @@ export type ResolversTypes = {
UpdateHighlightReplySuccess: ResolverTypeWrapper<UpdateHighlightReplySuccess>;
UpdateHighlightResult: ResolversTypes['UpdateHighlightError'] | ResolversTypes['UpdateHighlightSuccess'];
UpdateHighlightSuccess: ResolverTypeWrapper<UpdateHighlightSuccess>;
UpdateLabelError: ResolverTypeWrapper<UpdateLabelError>;
UpdateLabelErrorCode: UpdateLabelErrorCode;
UpdateLabelInput: UpdateLabelInput;
UpdateLabelResult: ResolversTypes['UpdateLabelError'] | ResolversTypes['UpdateLabelSuccess'];
UpdateLabelSuccess: ResolverTypeWrapper<UpdateLabelSuccess>;
UpdateLinkShareInfoError: ResolverTypeWrapper<UpdateLinkShareInfoError>;
UpdateLinkShareInfoErrorCode: UpdateLinkShareInfoErrorCode;
UpdateLinkShareInfoInput: UpdateLinkShareInfoInput;
@ -2326,6 +2363,10 @@ export type ResolversParentTypes = {
UpdateHighlightReplySuccess: UpdateHighlightReplySuccess;
UpdateHighlightResult: ResolversParentTypes['UpdateHighlightError'] | ResolversParentTypes['UpdateHighlightSuccess'];
UpdateHighlightSuccess: UpdateHighlightSuccess;
UpdateLabelError: UpdateLabelError;
UpdateLabelInput: UpdateLabelInput;
UpdateLabelResult: ResolversParentTypes['UpdateLabelError'] | ResolversParentTypes['UpdateLabelSuccess'];
UpdateLabelSuccess: UpdateLabelSuccess;
UpdateLinkShareInfoError: UpdateLinkShareInfoError;
UpdateLinkShareInfoInput: UpdateLinkShareInfoInput;
UpdateLinkShareInfoResult: ResolversParentTypes['UpdateLinkShareInfoError'] | ResolversParentTypes['UpdateLinkShareInfoSuccess'];
@ -2937,6 +2978,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
signup?: Resolver<ResolversTypes['SignupResult'], ParentType, ContextType, RequireFields<MutationSignupArgs, 'input'>>;
updateHighlight?: Resolver<ResolversTypes['UpdateHighlightResult'], ParentType, ContextType, RequireFields<MutationUpdateHighlightArgs, 'input'>>;
updateHighlightReply?: Resolver<ResolversTypes['UpdateHighlightReplyResult'], ParentType, ContextType, RequireFields<MutationUpdateHighlightReplyArgs, 'input'>>;
updateLabel?: Resolver<ResolversTypes['UpdateLabelResult'], ParentType, ContextType, RequireFields<MutationUpdateLabelArgs, 'input'>>;
updateLinkShareInfo?: Resolver<ResolversTypes['UpdateLinkShareInfoResult'], ParentType, ContextType, RequireFields<MutationUpdateLinkShareInfoArgs, 'input'>>;
updateReminder?: Resolver<ResolversTypes['UpdateReminderResult'], ParentType, ContextType, RequireFields<MutationUpdateReminderArgs, 'input'>>;
updateSharedComment?: Resolver<ResolversTypes['UpdateSharedCommentResult'], ParentType, ContextType, RequireFields<MutationUpdateSharedCommentArgs, 'input'>>;
@ -3257,6 +3299,20 @@ export type UpdateHighlightSuccessResolvers<ContextType = ResolverContext, Paren
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateLabelErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLabelError'] = ResolversParentTypes['UpdateLabelError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdateLabelErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateLabelResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLabelResult'] = ResolversParentTypes['UpdateLabelResult']> = {
__resolveType: TypeResolveFn<'UpdateLabelError' | 'UpdateLabelSuccess', ParentType, ContextType>;
};
export type UpdateLabelSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLabelSuccess'] = ResolversParentTypes['UpdateLabelSuccess']> = {
label?: Resolver<ResolversTypes['Label'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UpdateLinkShareInfoErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['UpdateLinkShareInfoError'] = ResolversParentTypes['UpdateLinkShareInfoError']> = {
errorCodes?: Resolver<Array<ResolversTypes['UpdateLinkShareInfoErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -3551,6 +3607,9 @@ export type Resolvers<ContextType = ResolverContext> = {
UpdateHighlightReplySuccess?: UpdateHighlightReplySuccessResolvers<ContextType>;
UpdateHighlightResult?: UpdateHighlightResultResolvers<ContextType>;
UpdateHighlightSuccess?: UpdateHighlightSuccessResolvers<ContextType>;
UpdateLabelError?: UpdateLabelErrorResolvers<ContextType>;
UpdateLabelResult?: UpdateLabelResultResolvers<ContextType>;
UpdateLabelSuccess?: UpdateLabelSuccessResolvers<ContextType>;
UpdateLinkShareInfoError?: UpdateLinkShareInfoErrorResolvers<ContextType>;
UpdateLinkShareInfoResult?: UpdateLinkShareInfoResultResolvers<ContextType>;
UpdateLinkShareInfoSuccess?: UpdateLinkShareInfoSuccessResolvers<ContextType>;

View File

@ -705,6 +705,7 @@ type Mutation {
signup(input: SignupInput!): SignupResult!
updateHighlight(input: UpdateHighlightInput!): UpdateHighlightResult!
updateHighlightReply(input: UpdateHighlightReplyInput!): UpdateHighlightReplyResult!
updateLabel(input: UpdateLabelInput!): UpdateLabelResult!
updateLinkShareInfo(input: UpdateLinkShareInfoInput!): UpdateLinkShareInfoResult!
updateReminder(input: UpdateReminderInput!): UpdateReminderResult!
updateSharedComment(input: UpdateSharedCommentInput!): UpdateSharedCommentResult!
@ -1194,6 +1195,30 @@ type UpdateHighlightSuccess {
highlight: Highlight!
}
type UpdateLabelError {
errorCodes: [UpdateLabelErrorCode!]!
}
enum UpdateLabelErrorCode {
BAD_REQUEST
FORBIDDEN
NOT_FOUND
UNAUTHORIZED
}
input UpdateLabelInput {
color: String!
description: String
labelId: ID!
name: String!
}
union UpdateLabelResult = UpdateLabelError | UpdateLabelSuccess
type UpdateLabelSuccess {
label: Label!
}
type UpdateLinkShareInfoError {
errorCodes: [UpdateLinkShareInfoErrorCode!]!
}

View File

@ -71,6 +71,7 @@ import {
updateUserResolver,
uploadFileRequestResolver,
validateUsernameResolver,
updateLabelResolver,
} from './index'
import { getShareInfoForArticle } from '../datalayer/links/share_info'
import {
@ -132,6 +133,7 @@ export const functionResolvers = {
deleteReminder: deleteReminderResolver,
setDeviceToken: setDeviceTokenResolver,
createLabel: createLabelResolver,
updateLabel: updateLabelResolver,
deleteLabel: deleteLabelResolver,
login: loginResolver,
signup: signupResolver,

View File

@ -12,9 +12,13 @@ import {
MutationCreateLabelArgs,
MutationDeleteLabelArgs,
MutationSetLabelsArgs,
MutationUpdateLabelArgs,
SetLabelsError,
SetLabelsErrorCode,
SetLabelsSuccess,
UpdateLabelError,
UpdateLabelErrorCode,
UpdateLabelSuccess,
} from '../../generated/graphql'
import { analytics } from '../../utils/analytics'
import { env } from '../../env'
@ -120,6 +124,74 @@ export const createLabelResolver = authorized<
}
})
export const updateLabelResolver = authorized<
UpdateLabelSuccess,
UpdateLabelError,
MutationUpdateLabelArgs
>(async (_, { input }, { claims: { uid }, log }) => {
log.info('updateLabelResolver')
try {
const { name, color, description, labelId } = input
const user = await getRepository(User).findOne(uid)
if (!user) {
return {
errorCodes: [UpdateLabelErrorCode.Unauthorized],
}
}
const label = await getRepository(Label).findOne({
where: {
id: labelId,
user,
},
})
if (!label) {
return {
errorCodes: [UpdateLabelErrorCode.NotFound],
}
}
const result = await getManager().transaction(async (t) => {
await setClaims(t, uid)
return await t.getRepository(Label).update(
{ id: labelId },
{
name: name,
description: description || undefined,
color: color,
}
)
})
log.info('Updating a label', {
result,
labels: {
source: 'resolver',
resolver: 'updateLabelResolver',
},
})
if (!result) {
log.info('failed to update')
return {
errorCodes: [UpdateLabelErrorCode.BadRequest],
}
}
log.info('updated successfully')
return { label: label }
} catch (error) {
log.error('error', error)
return {
errorCodes: [UpdateLabelErrorCode.BadRequest],
}
}
})
export const deleteLabelResolver = authorized<
DeleteLabelSuccess,
DeleteLabelError,

View File

@ -1310,6 +1310,30 @@ const schema = gql`
union DeleteLabelResult = DeleteLabelSuccess | DeleteLabelError
input UpdateLabelInput {
labelId: ID!
color: String!
description: String
name: String!
}
type UpdateLabelSuccess {
label: Label!
}
enum UpdateLabelErrorCode {
UNAUTHORIZED
BAD_REQUEST
NOT_FOUND
FORBIDDEN
}
type UpdateLabelError {
errorCodes: [UpdateLabelErrorCode!]!
}
union UpdateLabelResult = UpdateLabelSuccess | UpdateLabelError
input LoginInput {
password: String!
email: String!
@ -1410,6 +1434,7 @@ const schema = gql`
deleteReminder(id: ID!): DeleteReminderResult!
setDeviceToken(input: SetDeviceTokenInput!): SetDeviceTokenResult!
createLabel(input: CreateLabelInput!): CreateLabelResult!
updateLabel(input: UpdateLabelInput!): UpdateLabelResult!
deleteLabel(id: ID!): DeleteLabelResult!
login(input: LoginInput!): LoginResult!
signup(input: SignupInput!): SignupResult!

View File

@ -8,6 +8,7 @@ import {
Arrow,
Label,
} from '@radix-ui/react-dropdown-menu'
import { CSS } from '@stitches/react';
import { styled } from './../tokens/stitches.config'
const itemStyles = {
@ -43,7 +44,7 @@ const StyledTriggerItem = styled(TriggerItem, {
...itemStyles,
})
const DropdownContent = styled(Content, {
export const DropdownContent = styled(Content, {
minWidth: 130,
backgroundColor: '$grayBg',
borderRadius: '0.5em',
@ -69,13 +70,16 @@ type DropdownProps = {
labelText?: string
showArrow?: boolean
triggerElement: React.ReactNode
children: React.ReactNode
align?: DropdownAlignment
children: React.ReactNode,
styledArrow?: boolean
align?: DropdownAlignment
css?: CSS
}
export const DropdownSeparator = styled(Separator, {
height: 0,
height: '1px',
margin: 0,
backgroundColor: '$grayBorder',
})
type DropdownOptionProps = {
@ -91,6 +95,7 @@ export function DropdownOption(props: DropdownOptionProps): JSX.Element {
<StyledItem onSelect={props.onSelect}>
{props.title ?? props.children}
</StyledItem>
{props.hideSeparator ? null : <DropdownSeparator />}
</>
)
}
@ -101,12 +106,14 @@ export function Dropdown({
triggerElement,
labelText,
showArrow = true,
css
}: DropdownProps): JSX.Element {
return (
<Root modal={false}>
<DropdownTrigger>{triggerElement}</DropdownTrigger>
<DropdownContent
onInteractOutside={() => {
css={css}
onInteractOutside={(event) => {
// remove focus from dropdown
;(document.activeElement as HTMLElement).blur()
}}

View File

@ -0,0 +1,312 @@
import React, { useState } from 'react'
import { styled } from '../tokens/stitches.config'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { HexColorPicker } from 'react-colorful'
import { Button } from './Button'
import { HStack, SpanBox } from './LayoutPrimitives'
import { CaretDown } from 'phosphor-react'
import { StyledText } from './StyledText'
import {
ColorDetailsProps,
LabelColor,
LabelColorDropdownProps,
LabelColorHex,
LabelColorObject,
LabelOptionProps,
} from '../../utils/settings-page/labels/types'
import { labelColorObjects } from '../../utils/settings-page/labels/labelColorObjects'
import { DropdownOption } from './DropdownElements'
import { isDarkTheme } from '../../lib/themeUpdater'
const DropdownMenuContent = styled(DropdownMenuPrimitive.Content, {
maxWidth: 190,
borderRadius: 6,
backgroundColor: '$grayBg',
padding: 5,
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
})
const itemStyles = {
all: 'unset',
fontSize: '$3',
lineHeight: 1,
borderRadius: 3,
display: 'flex',
alignItems: 'center',
height: 25,
position: 'relative',
userSelect: 'none',
}
const DropdownMenuTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
outline: 'none',
backgroundColor: '$grayBgHover',
},
...itemStyles,
padding: '$2 0',
'&:focus': {
outline: 'none',
backgroundColor: '$grayBgHover',
},
})
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = styled(DropdownMenuPrimitive.Trigger, {
backgroundColor: 'transparent',
border: 0,
padding: 0,
marginRight: '$2',
'&[data-state="open"]': {
border: '2px solid $omnivoreYellow',
borderRadius: 6,
},
})
const Box = styled('div', {})
export const LabelColorDropdown = (props: LabelColorDropdownProps) => {
const {
isCreateMode,
canEdit,
labelColorHexRowId,
labelId,
labelColor,
labelColorHexValue,
setLabelColorHex,
} = props
const isDarkMode = isDarkTheme()
const iconColor = isDarkMode ? '#FFFFFF': '#0A0806'
const [open, setOpen] = useState<boolean | undefined>(false);
const handleCustomColorChange = (color: string) => {
setLabelColorHex({
rowId: labelId,
value: color.toUpperCase() as LabelColor,
})
}
const handleOpen = (open: boolean) => {
if (canEdit && open) setOpen(true)
else if((isCreateMode && !canEdit) && open) setOpen(true)
else setOpen(false)
}
return (
<DropdownMenu onOpenChange={handleOpen} open={open}>
<DropdownMenuTrigger
css={{
minWidth: '64px',
width: '100%',
'@md': {
minWidth: '170px',
width: 'auto'
},
}}
>
<Button
style="ctaWhite"
css={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '$grayBg',
borderColor: '$grayBorder',
borderWidth: '1px',
width: '100%',
}}
>
<SpanBox css={{ paddingRight: '$3' }}>
{labelId !== '' && labelId === labelColorHexRowId ? (
<LabelOption
isCreateMode={isCreateMode}
labelId={labelId}
color={labelColorHexValue}
isDropdownOption={false}
/>
) : (
<>
{labelId ? (
<LabelOption
isCreateMode={isCreateMode}
labelId={labelId}
color={labelColor}
isDropdownOption={false}
/>
) : (
<LabelOption
isCreateMode={isCreateMode}
labelId={''}
color={labelColorHexValue}
isDropdownOption={false}
/>
)}
</>
)}
</SpanBox>
<CaretDown size={16} color={iconColor} weight="bold" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={5}>
{Object.keys(labelColorObjects)
.filter((labelColor) => labelColor !== 'custom color')
.map((labelColor) => (
<DropdownOption
key={labelColor}
onSelect={() =>
setLabelColorHex({
rowId: labelId,
value: labelColor as LabelColor,
})
}
>
<LabelOption
isCreateMode={isCreateMode}
labelId={labelId}
color={labelColor}
isDropdownOption
/>
</DropdownOption>
))}
<DropdownMenu>
<DropdownMenuTriggerItem>
<DropdownOption onSelect={() => null}>
<LabelOption
isCreateMode={isCreateMode}
labelId={labelId}
color="custom color"
isDropdownOption
/>
</DropdownOption>
</DropdownMenuTriggerItem>
<DropdownMenuContent
sideOffset={-25}
alignOffset={-5}
css={{ minWidth: 'unset' }}
>
<HexColorPicker
color={labelColor}
onChange={handleCustomColorChange}
/>
</DropdownMenuContent>
</DropdownMenu>
</DropdownMenuContent>
</DropdownMenu>
)
}
function LabelOption(props: LabelOptionProps): JSX.Element {
const { color, isDropdownOption, isCreateMode, labelId } = props
// const colorDetails = getColorDetails(
// color as LabelColor,
// labelId,
// Boolean(isCreateMode)
// )
const isCreating = isCreateMode && !labelId
const textDisplay = !isCreating && !isDropdownOption ? 'none' : 'unset'
const { text, border, colorName, background } = getLabelColorObject(
color as LabelColor
)
let colorNameText = colorName
if (!labelId && isCreateMode) {
colorNameText = 'Select Color'
colorNameText = isDropdownOption ? colorName : colorNameText
}
colorNameText = color === 'custom color' ? colorNameText : colorName
let colorHex = !labelId && isCreateMode && !isDropdownOption ? '' : text
colorHex =
!labelId && isCreateMode && !isDropdownOption && color !== 'custom color'
? text
: colorHex
return (
<HStack
alignment="center"
distribution="start"
css={{ width: '100%', padding: isDropdownOption ? '' : '$2 0' }}
>
<Box
css={{
m: '$1',
textTransform: 'capitalize',
fontSize: '$3',
whiteSpace: 'nowrap',
}}
>
<LabelColorIcon fillColor={text} strokeColor={border} />
</Box>
<StyledText
css={{
m: '$1',
color: '$grayText',
display: textDisplay,
fontSize: '$3',
whiteSpace: 'nowrap',
textTransform: 'capitalize',
'@md': {
display: 'unset',
},
}}
>
{colorNameText}
</StyledText>
<StyledText
css={{
m: '$1',
color: '$grayText',
display: textDisplay,
fontSize: '$3',
whiteSpace: 'nowrap',
'@md': {
display: 'unset',
},
}}
>
{colorNameText === 'custom color' ? '' : colorHex}
</StyledText>
{isDropdownOption ? <Box css={{ pr: '$2' }} /> : null}
</HStack>
)
// colorName,
// color: hexCode,
// icon: <LabelColorIcon fillColor={fillColor} strokeColor={strokeColor} />,
}
function getLabelColorObject(color: LabelColor) {
if (labelColorObjects[color]) {
return labelColorObjects[color]
}
const colorObject: LabelColorObject = {
colorName: 'Custom',
text: color,
border: color + '66',
background: color + '0D',
}
return colorObject
}
function LabelColorIcon(props: {
fillColor: string
strokeColor: string
}): JSX.Element {
return (
<Box
css={{
width: '14px',
height: '14px',
borderRadius: '50%',
border: '2px solid',
borderColor: props.strokeColor,
backgroundColor: props.fillColor,
}}
/>
)
}

View File

@ -0,0 +1,21 @@
type PlusIconProps = {
size: number
strokeColor: string
}
export function PlusIcon(props: PlusIconProps): JSX.Element {
return (
<svg
width={props.size}
height={props.size}
viewBox={`0 0 ${props.size} ${props.size}`}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 7.25C2.08579 7.25 1.75 7.58579 1.75 8C1.75 8.41421 2.08579 8.75 2.5 8.75V7.25ZM13.5 8.75C13.9142 8.75 14.25 8.41421 14.25 8C14.25 7.58579 13.9142 7.25 13.5 7.25V8.75ZM8.75 2.5C8.75 2.08579 8.41421 1.75 8 1.75C7.58579 1.75 7.25 2.08579 7.25 2.5H8.75ZM7.25 13.5C7.25 13.9142 7.58579 14.25 8 14.25C8.41421 14.25 8.75 13.9142 8.75 13.5H7.25ZM2.5 8.75H13.5V7.25H2.5V8.75ZM7.25 2.5V13.5H8.75V2.5H7.25Z"
fill={props.strokeColor}
fillOpacity="0.8"/>
</svg>
)
}

View File

@ -161,6 +161,8 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
// Avatar Fallback color
avatarBg: '#FFFFFF',
avatarFont: '#0A0806',
labelButtonsBg: '#F5F5F4',
tooltipIcons: '#FDFAEC'
},
},
@ -211,6 +213,8 @@ const darkThemeSpec = {
tooltipIcons: '#5F5E58',
avatarBg: '#000000',
avatarFont: 'rgba(255, 255, 255, 0.8)',
labelButtonsBg: '#5F5E58',
},
shadows: {
cardBoxShadow: '0px 0px 9px -2px rgba(32, 31, 29, 0.09), 0px 7px 12px rgba(32, 31, 29, 0.07)'

View File

@ -0,0 +1,52 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
export type UpdateLabelInput = {
labelId: string
name: string,
color: string,
description?: string
}
export async function updateLabelMutation(
input: UpdateLabelInput
): Promise<string | undefined> {
const mutation = gql`
mutation {
updateLabel(
input: {
color: "${input.color}"
name: "${input.name}"
description: "${input.description}"
labelId: "${input.labelId}"
}
) {
... on UpdateLabelSuccess {
label {
id
name
color
description
createdAt
}
}
... on UpdateLabelError {
errorCodes
}
}
}
`
try {
console.log('here', input)
const data = await gqlFetcher(mutation)
console.log('here 3')
console.log(input, data);
const output = data as any
console.log(output)
return output?.updatedLabel
} catch (err) {
console.log('here 3', err)
return undefined
}
}

View File

@ -1,5 +1,6 @@
import { gql } from 'graphql-request'
import useSWR from 'swr'
import { LabelColor } from '../../../utils/settings-page/labels/types';
import { Label, labelFragment } from '../fragments/labelFragment'
import { publicGqlFetcher } from '../networkHelpers'
@ -17,6 +18,14 @@ type LabelsData = {
labels?: unknown
}
export type Label = {
id: string
name: string
color: LabelColor
description?: string
createdAt: Date
}
export function useGetLabelsQuery(): LabelsQueryResponse {
const query = gql`
query GetLabels {

View File

@ -36,6 +36,7 @@
"pspdfkit": "^2021.6.0",
"react": "^17.0.2",
"react-apple-login": "^1.1.3",
"react-colorful": "^5.5.1",
"react-dom": "^17.0.2",
"react-hot-toast": "^2.1.1",
"react-intl": "^5.20.12",

View File

@ -1,32 +1,198 @@
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import { Button } from '../../components/elements/Button'
import { Box, VStack } from '../../components/elements/LayoutPrimitives'
import { PlusIcon } from '../../components/elements/images/PlusIcon'
import { styled } from '../../components/tokens/stitches.config'
import {
Box,
SpanBox,
HStack,
VStack,
} from '../../components/elements/LayoutPrimitives'
import { Toaster } from 'react-hot-toast'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import { createLabelMutation } from '../../lib/networking/mutations/createLabelMutation'
import { updateLabelMutation } from '../../lib/networking/mutations/updateLabelMutation'
import { deleteLabelMutation } from '../../lib/networking/mutations/deleteLabelMutation'
import { useState } from 'react'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { Label } from '../../lib/networking/queries/useGetLabelsQuery'
import { isDarkTheme } from '../../lib/themeUpdater'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
export default function LabelsPage(): JSX.Element {
const { labels, revalidate, isValidating } = useGetLabelsQuery()
const [name, setName] = useState('')
const [color, setColor] = useState('')
const [description, setDescription] = useState('')
import { useEffect, useState } from 'react'
import { StyledText } from '../../components/elements/StyledText'
import {
ArrowClockwise,
DotsThree,
PencilSimple,
Trash,
Plus,
DotsSixVertical,
} from 'phosphor-react'
import {
LabelColor,
GenericTableCardProps,
LabelColorHex,
} from '../../utils/settings-page/labels/types'
import { labelColorObjects, } from '../../utils/settings-page/labels/labelColorObjects'
import {
TooltipWrapped
} from '../../components/elements/Tooltip'
import { LabelColorDropdown } from '../../components/elements/LabelColorDropdown'
import {
Dropdown,
DropdownOption,
} from '../../components/elements/DropdownElements'
applyStoredTheme(false)
const HeaderWrapper = styled(Box, {
width: '100%',
})
const TableCard = styled(Box, {
backgroundColor: '$grayBg',
display: 'flex',
alignItems: 'center',
padding: '8px 12px',
border: '0.3px solid $grayBgActive',
width: '100%',
'&:hover': {
border: '0.3px solid #FFD234',
backgroundColor: '#FFFDF4',
},
'@md': {
paddingLeft: '0',
},
})
const TableCardBox = styled(Box, {
display: 'grid',
width: '100%',
gridGap: '$1',
gridTemplateColumns: '3fr 1fr',
'.showHidden': {
display: 'none',
},
'&:hover': {
'.showHidden': {
display: 'unset',
gridColumn: 'span 2',
width: '100%',
padding: '$2 $3 0 $3',
},
},
'@md': {
gridTemplateColumns: '20% 15% 1fr 1fr 1fr',
'&:hover': {
'.showHidden': {
display: 'none',
},
},
},
})
const TableHeading = styled(Box, {
backgroundColor: '$grayBgActive',
// gridTemplateColumns: '20% 30% 1fr 230px 1fr',
gridTemplateColumns: '20% 30% 1fr 1fr',
alignItems: 'center',
padding: '12px 0px',
borderRadius: '5px 5px 0px 0px',
width: '100%',
textTransform: 'uppercase',
display: 'none',
'@md': {
display: 'grid',
},
})
const inputStyles = {
backgroundColor: 'transparent',
color: '$grayTextContrast',
padding: '13px 6px',
margin: '$2 0',
border: '1px solid $grayBorder',
borderRadius: '6px',
fontSize: '13px',
FontFamily: '$fontFamily',
width: '100%',
'@md': {
width: 'auto',
minWidth: '180px',
},
'&[disabled]': {
border: 'none',
},
'&:focus': {
outlineColor: '$omnivoreYellow',
outlineStyle: 'solid',
},
}
const IconButton = styled(Button, {
variants: {
style: {
ctaWhite: {
color: 'red',
padding: '14px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: '1px solid $grayBorder',
boxSizing: 'border-box',
borderRadius: 6,
},
},
},
})
const Input = styled('input', { ...inputStyles })
const TextArea = styled('textarea', { ...inputStyles })
export default function LabelsPage(): JSX.Element {
const { labels, revalidate } = useGetLabelsQuery()
const [labelColorHex, setLabelColorHex] = useState<LabelColorHex>({
rowId: '',
value: 'custom color',
})
console.log('LabelsPage ~ labelColorHex', labelColorHex)
const [editingLabelId, setEditingLabelId] = useState<string | null>(null)
const [nameInputText, setNameInputText] = useState<string>('')
const [descriptionInputText, setDescriptionInputText] = useState<string>('')
const [isCreateMode, setIsCreateMode] = useState<boolean>(false)
const [windowWidth, setWindowWidth] = useState<number>(0)
const breakpoint = 768
useEffect(() => {
const handleResizeWindow = () => setWindowWidth(window.innerWidth)
if (windowWidth === 0) {
setWindowWidth(window.innerWidth)
}
window.addEventListener('resize', handleResizeWindow)
return () => {
window.removeEventListener('resize', handleResizeWindow)
}
}, [windowWidth])
const resetLabelState = () => {
setIsCreateMode(false)
setEditingLabelId('')
setNameInputText('')
setDescriptionInputText('')
setLabelColorHex({ rowId: '', value: 'custom color' })
}
async function createLabel(): Promise<void> {
const res = await createLabelMutation(name, color, description)
const res = await createLabelMutation(
nameInputText,
labelColorHex.value,
descriptionInputText
)
if (res) {
if (res.createLabel.errorCodes && res.createLabel.errorCodes.length > 0) {
showErrorToast(res.createLabel.errorCodes[0])
} else {
showSuccessToast('Label created')
setName('')
setColor('')
setDescription('')
resetLabelState()
revalidate()
}
} else {
@ -34,78 +200,667 @@ export default function LabelsPage(): JSX.Element {
}
}
async function updateLabel(id: string): Promise<void> {
await updateLabelMutation({
labelId: id,
name: nameInputText,
color: labelColorHex.value,
description: descriptionInputText,
})
revalidate()
}
const onEditPress = (label : Label | null) => {
if (label) {
setEditingLabelId(label.id)
setNameInputText(label.name)
setDescriptionInputText(label.description || '')
setLabelColorHex({ rowId: '', value: label.color })
}
else {
resetLabelState()
}
}
async function deleteLabel(id: string): Promise<void> {
await deleteLabelMutation(id)
revalidate()
}
const handleGenerateRandomColor = (rowId?: string) => {
const colorHexes = Object.keys(labelColorObjects).slice(
0,
-1
) as LabelColor[]
const randomColorHex =
colorHexes[Math.floor(Math.random() * colorHexes.length)]
setLabelColorHex((prevState) => ({
...prevState,
rowId: rowId || '',
value: randomColorHex,
}))
}
return (
<PrimaryLayout pageTestId="settings-labels-tag">
<Toaster />
<VStack css={{ mx: '42px' }}>
<h2>Create a new label</h2>
<form
onSubmit={async (e): Promise<void> => {
e.preventDefault()
await createLabel()
}}
>
<input
type="text"
name="name"
placeholder="Name"
required
value={name}
onChange={(event) => {
setName(event.target.value)
}}
/>
<input
type="text"
name="color"
placeholder="Color"
required
value={color}
onChange={(event) => {
setColor(event.target.value)
}}
/>
<input
type="text"
name="description"
placeholder="Description"
value={description}
onChange={(event) => {
setDescription(event.target.value)
}}
/>
<Button type="submit" disabled={isValidating}>
Create
</Button>
</form>
<h2>Labels</h2>
{labels &&
labels.map((label) => {
return (
<Box key={label.id}>
<form
onSubmit={async (e): Promise<void> => {
e.preventDefault()
await deleteLabel(label.id)
}}
>
<input type="text" value={label.name} disabled />
<input type="text" value={label.color} disabled />
<input type="text" value={label.description} disabled />
<Button type="submit" disabled={isValidating}>
Delete
<Toaster
containerStyle={{
top: '5rem',
}}
/>
<VStack
css={{
mx: '10px',
color: '$grayText',
}}
>
<HeaderWrapper>
<Box style={{ display: 'flex', alignItems: 'center' }}>
<Box style={{ flex: '1' }}>
<StyledText style="fixedHeadline">Labels</StyledText>
</Box>
<Box css={{ minWidth: '178px', display: 'flex', justifyContent: 'flex-end' }}>
{editingLabelId || isCreateMode ? null : (
<>
<Button
onClick={() => {
resetLabelState()
setIsCreateMode(true)
}}
style="ctaDarkYellow"
css={{
display: 'none',
'@md': {
display: 'flex',
},
}}
>
<PlusIcon size={20} strokeColor={'#0A0806'} />
<SpanBox>Create New Label</SpanBox>
</Button>
</form>
</Box>
<Box
css={{
position: 'fixed',
bottom: '58px',
right: '16px',
zIndex: '1',
'@md': {
display: 'none',
},
}}
>
<Button
style="ctaDarkYellow"
onClick={() => {
setIsCreateMode(true)
}}
>
<Plus size={24} />
</Button>
</Box>
</>
)}
</Box>
</Box>
<TableHeading>
<Box>
<StyledText
style="highlightTitle"
css={{
color: '$grayTextContrast',
padding: '0 5px 0 60px',
}}
>
Name
</StyledText>
</Box>
<Box style={{ flex: '35%' }}>
<StyledText
style="highlightTitle"
css={{
color: '$grayTextContrast',
}}
>
Description
</StyledText>
</Box>
{/* <Box>
<StyledText
style="highlightTitle"
css={{
color: '$grayTextContrast',
textAlign: 'right',
paddingRight: '40px',
}}
>
Uses
</StyledText>
</Box> */}
<Box style={{ flex: '30%' }}>
<StyledText
style="highlightTitle"
css={{
color: '$grayTextContrast',
paddingLeft: '15px',
}}
>
Color
</StyledText>
</Box>
<Box>
<StyledText
style="highlightTitle"
css={{
color: '$grayTextContrast',
display: 'flex',
justifyContent: 'center',
}}
>
Actions
</StyledText>
</Box>
</TableHeading>
</HeaderWrapper>
<>
{isCreateMode ? (
windowWidth > breakpoint ? (
<GenericTableCard
label={null}
labelColorHex={labelColorHex}
editingLabelId={editingLabelId}
isCreateMode={isCreateMode}
handleGenerateRandomColor={handleGenerateRandomColor}
setEditingLabelId={setEditingLabelId}
setLabelColorHex={setLabelColorHex}
deleteLabel={deleteLabel}
nameInputText={nameInputText}
descriptionInputText={descriptionInputText}
setNameInputText={setNameInputText}
setDescriptionInputText={setDescriptionInputText}
setIsCreateMode={setIsCreateMode}
createLabel={createLabel}
updateLabel={updateLabel}
onEditPress={onEditPress}
resetState={resetLabelState}
/>
) : (
<MobileEditCard
label={null}
labelColorHex={labelColorHex}
editingLabelId={editingLabelId}
isCreateMode={isCreateMode}
handleGenerateRandomColor={handleGenerateRandomColor}
setEditingLabelId={setEditingLabelId}
setLabelColorHex={setLabelColorHex}
deleteLabel={deleteLabel}
nameInputText={nameInputText}
descriptionInputText={descriptionInputText}
setNameInputText={setNameInputText}
setDescriptionInputText={setDescriptionInputText}
setIsCreateMode={setIsCreateMode}
createLabel={createLabel}
resetState={resetLabelState}
/>
)
})}
) : null}
</>
{labels
? labels.map((label, i) => {
const isLastChild = i === labels.length - 1
const isFirstChild = i === 0
return (
<GenericTableCard
key={label.id}
isLastChild={isLastChild}
isFirstChild={isFirstChild}
label={label as unknown as Label}
labelColorHex={labelColorHex}
editingLabelId={editingLabelId}
isCreateMode={isCreateMode}
handleGenerateRandomColor={handleGenerateRandomColor}
setEditingLabelId={setEditingLabelId}
setLabelColorHex={setLabelColorHex}
deleteLabel={deleteLabel}
nameInputText={nameInputText}
descriptionInputText={descriptionInputText}
setNameInputText={setNameInputText}
setDescriptionInputText={setDescriptionInputText}
setIsCreateMode={setIsCreateMode}
createLabel={createLabel}
updateLabel={updateLabel}
onEditPress={onEditPress}
resetState={resetLabelState}
/>
)
})
: null}
</VStack>
</PrimaryLayout>
)
}
function GenericTableCard(props: GenericTableCardProps & { isLastChild?: boolean; isFirstChild?: boolean}) {
const {
label,
isLastChild,
isFirstChild,
editingLabelId,
labelColorHex,
isCreateMode,
nameInputText,
descriptionInputText,
handleGenerateRandomColor,
setLabelColorHex,
setEditingLabelId,
deleteLabel,
setNameInputText,
setDescriptionInputText,
createLabel,
updateLabel,
onEditPress,
resetState,
} = props
const colorObject =
labelColorObjects[label?.color || ''] || labelColorObjects['custom color']
const { text, border, background } = colorObject
const showInput =
editingLabelId === label?.id || (isCreateMode && !label)
const labelName = label?.name || nameInputText
const isDarkMode = isDarkTheme()
const iconColor = isDarkMode ? '#D8D7D5': '#5F5E58'
const handleEdit = () => {
editingLabelId && updateLabel(editingLabelId)
setEditingLabelId(null)
}
const moreActionsButton = () => {
return (
<Button
style="plainIcon"
css={{
mr: '$1',
display: 'flex',
background: 'transparent',
border: 'none',
}}
onClick={() => true}
disabled={isCreateMode}
>
<Dropdown
triggerElement={<DotsThree size={24} color={iconColor} />}
>
<DropdownOption onSelect={() => null}>
<Button
style="plainIcon"
css={{
mr: '$1',
display: 'flex',
alignItems: 'center',
backgroundColor: 'transparent',
border: 0,
}}
onClick={() => onEditPress(label)}
disabled={isCreateMode}
>
<PencilSimple size={24} color={iconColor} />
<StyledText
color="$grayText"
css={{ fontSize: '$5', marginLeft: '$2' }}
>
Edit
</StyledText>
</Button>
</DropdownOption>
<DropdownOption onSelect={() => null}>
<Button
style="plainIcon"
css={{
mr: '$1',
display: 'flex',
alignItems: 'center',
backgroundColor: 'transparent',
border: 0,
}}
onClick={() => (label ? deleteLabel(label.id) : null)}
disabled={isCreateMode}
>
<Trash size={24} color="#AA2D11" />
<StyledText
css={{ fontSize: '$5', marginLeft: '$2', color: '#AA2D11' }}
>
Delete
</StyledText>
</Button>
</DropdownOption>
</Dropdown>
</Button>
)
}
return (
<TableCard
css={{
'&:hover': {
background: 'rgba(255, 234, 159, 0.12)',
},
'@mdDown': {
borderTopLeftRadius: isFirstChild ? '5px' : '',
borderTopRightRadius: isFirstChild ? '5px' : '',
},
borderBottomLeftRadius: isLastChild ? '5px' : '',
borderBottomRightRadius: isLastChild ? '5px' : '',
}}>
<TableCardBox
css={{
display: 'grid',
width: '100%',
gridGap: '$1',
gridTemplateColumns: '3fr 1fr',
'.showHidden': {
display: 'none',
},
'&:hover': {
'.showHidden': {
display: 'unset',
gridColumn: 'span 2',
width: '100%',
padding: '$2 $3 0 $3',
},
},
'@md': {
// gridTemplateColumns: '20% 15% 1fr 1fr 1fr',
gridTemplateColumns: '20% 30% 1fr 1fr',
},
}}
>
<HStack
distribution="start"
alignment="center"
css={{
padding: '0 5px',
}}
>
{(showInput && !label) ? null : (
<HStack alignment="center">
<Button style="plainIcon" css={{marginRight: '10px'}}>
<DotsSixVertical size={16} />
</Button>
<StyledText
style="body"
css={{
color: text,
fontSize: '14px',
whiteSpace: 'nowrap',
textAlign: 'left',
}}
>
<SpanBox
css={{
border: '1px solid',
borderColor: border,
backgroundColor: background,
borderRadius: '32px',
padding: '4px 8px',
}}
>
{labelName}
</SpanBox>
</StyledText>
</HStack>
)}
{(showInput && !label) ? (
<Input
type="text"
value={nameInputText}
onChange={(event) => setNameInputText(event.target.value)}
required
autoFocus
/>
) : null}
</HStack>
<HStack
distribution="start"
alignment="center"
css={{
display: 'none',
'@md': {
display: 'flex',
},
}}
>
{showInput ? (
<Input
type="text"
placeholder='What this label is about...'
value={descriptionInputText}
onChange={(event) => setDescriptionInputText(event.target.value)}
autoFocus={!!label}
/>
) : (
<StyledText
style="body"
css={{
color: '$grayTextContrast',
fontSize: '14px',
}}
>
{editingLabelId === label?.id ? descriptionInputText : label?.description || ''}
</StyledText>
)}
</HStack>
{/* <HStack
distribution="end"
alignment="center"
css={{
display: 'none',
'@md': {
display: 'flex',
paddingRight: '30px',
},
}}
>
<StyledText css={{ fontSize: '$2' }}>
{isCreateMode && !label ? '-' : 536}
</StyledText>
</HStack> */}
<HStack
distribution="start"
css={{
padding: '4px 8px',
paddingLeft: '10px',
alignItems: 'center',
}}
>
<LabelColorDropdown
isCreateMode={isCreateMode && !label}
canEdit={editingLabelId === label?.id}
labelColorHexRowId={labelColorHex.rowId}
labelColorHexValue={labelColorHex.value}
labelId={label?.id || ''}
labelColor={label?.color || 'custom color'}
setLabelColorHex={setLabelColorHex}
/>
<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: 46,
height: 46,
background: '$labelButtonsBg',
}}
onClick={() => handleGenerateRandomColor(label?.id)}
disabled={!(isCreateMode && !label) && !(editingLabelId === label?.id)}
>
<ArrowClockwise size={16} color={iconColor} />
</IconButton>
</Box>
</TooltipWrapped>
<Box css={{'@md': { display: 'none' }}}>
{moreActionsButton()}
</Box>
</HStack>
<HStack
distribution="start"
alignment="center"
css={{
padding: '4px 8px',
'@md': {
justifyContent: 'center',
},
}}
>
{editingLabelId === label?.id || !label ? (
<>
<Button
style="ctaDarkYellow"
css={{ mr: '$1' }}
onClick={() =>
label ? handleEdit() : createLabel()
}
>
Save
</Button>
<Button
style="plainIcon"
css={{ mr: '$1' }}
onClick={() => {
resetState()
}}
>
Cancel
</Button>
</>
) : (
<HStack
distribution="end"
alignment="center"
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>
{moreActionsButton()}
</HStack>
)}
</HStack>
{/* <Box className="showHidden">
<StyledText
style="body"
css={{
color: '$grayTextContrast',
fontSize: '14px',
marginBottom: '$2',
}}
>
{label?.description}
</StyledText>
<StyledText
css={{ fontSize: '$2', textAlign: 'right', color: '$grayText' }}
>
{536} Uses
</StyledText>
</Box> */}
</TableCardBox>
</TableCard>
)
}
function MobileEditCard(props: any) {
const {
label,
editingLabelId,
labelColorHex,
isCreateMode,
nameInputText,
descriptionInputText,
setLabelColorHex,
setEditingLabelId,
setNameInputText,
setDescriptionInputText,
createLabel,
resetState
} = props
return (
<TableCard>
<VStack distribution="center" css={{ width: '100%' }}>
<StyledText>{editingLabelId ? 'Edit Label' : 'New Label'}</StyledText>
<Input
type="text"
value={nameInputText || label?.name}
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}
/>
<TextArea
value={descriptionInputText || label?.description}
onChange={(event) => setDescriptionInputText(event.target.value)}
rows={5}
/>
<HStack
distribution="end"
alignment="center"
css={{ width: '100%', margin: '$1 0' }}
>
<Button
style="plainIcon"
css={{ mr: '$1' }}
onClick={() => {
resetState()
}}
>
Cancel
</Button>
<Button
style="ctaDarkYellow"
css={{ mr: '$1' }}
onClick={() => (label ? setEditingLabelId(null) : createLabel())}
>
Save
</Button>
</HStack>
</VStack>
</TableCard>
)
}

View File

@ -0,0 +1,46 @@
import { LabelColorObjects } from './types'
export const labelColorObjects: LabelColorObjects = {
'#FF5D99': {
colorName: 'red',
text: '#B20042',
border: '#FF5D9966',
background: '#FF5D990D',
},
'#7CFF7B': {
colorName: 'green',
text: '#01A800',
border: '#7CFF7B66',
background: '#7CFF7B0D',
},
'#FFD234': {
colorName: 'yellow',
text: '#947300',
border: '#FFD23466',
background: '#FFD2340D',
},
'#7BE4FF': {
colorName: 'blue',
text: '#007E9E',
border: '#7BE4FF66',
background: '#7BE4FF0D',
},
'#CE88EF': {
colorName: 'purple',
text: '#B759E3',
border: '#CE88EF66',
background: '#CE88EF0D',
},
'#EF8C43': {
colorName: 'orange',
text: '#F37417',
border: '#EF8C4366',
background: '#EF8C430D',
},
'custom color': {
colorName: 'custom color',
text: '#A5A4A1',
border: '#D8D7D566',
background: '#D8D7D50D',
},
}

View File

@ -0,0 +1,71 @@
import React from "react";
import { Label } from "../../../lib/networking/queries/useGetLabelsQuery";
export type LabelColor =
| '#FF5D99'
| '#7CFF7B'
| '#FFD234'
| '#7BE4FF'
| '#CE88EF'
| '#EF8C43'
| 'custom color';
export type LabelOptionProps = {
color: string;
isDropdownOption?: boolean;
isCreateMode: boolean | undefined;
labelId: string;
};
export type ColorDetailsProps = {
colorName: string;
color: string;
icon: JSX.Element;
};
export type LabelColorObject = {
colorName: string;
text: string;
border: string;
background: string;
};
export type LabelColorObjects = {
[key: string]: LabelColorObject;
};
export type LabelColorHex = {
rowId: string;
value: LabelColor;
};
export type GenericTableCardProps = {
label: Label | null;
editingLabelId: string | null;
labelColorHex: LabelColorHex;
isCreateMode: boolean;
nameInputText: string,
descriptionInputText: string,
isMobileView?: boolean;
handleGenerateRandomColor: (rowId?: string) => void;
setEditingLabelId: (id: string | null) => void;
setLabelColorHex: (color: LabelColorHex) => void;
deleteLabel: (id: string) => void;
setNameInputText: (text: string) => void,
setDescriptionInputText: (text: string) => void,
resetState: () => void,
createLabel: () => void,
updateLabel: (id: string) => void;
setIsCreateMode: (isCreateMode: boolean) => void,
onEditPress: (label: Label | null) => void,
};
export type LabelColorDropdownProps = {
isCreateMode: boolean;
canEdit: boolean;
labelColorHexRowId: string;
labelColorHexValue: string;
labelId: string;
labelColor: LabelColor;
setLabelColorHex: (color: LabelColorHex) => void;
};