feat(ui): add edit/search help popover

This commit is contained in:
FoxxMD 2026-10-09 21:00:56 +00:00
parent 591888e97a
commit 698f56dbe8
3 changed files with 77 additions and 9 deletions

View file

@ -6,8 +6,9 @@ import {
Tooltip as ChakraTooltip,
Span,
} from "@chakra-ui/react"
import * as React from "react"
import { HiOutlineInformationCircle } from "react-icons/hi"
import * as React from "react";
import { BsQuestionCircle, BsExclamationCircle } from "react-icons/bs";
import type { IconBaseProps } from "react-icons/lib";
export interface ToggleTipProps extends ChakraPopover.RootProps {
showArrow?: boolean
@ -67,10 +68,12 @@ export const ToggleTip = React.forwardRef<HTMLDivElement, ToggleTipProps>(
export interface InfoTipProps extends Partial<ToggleTipProps> {
buttonProps?: IconButtonProps | undefined
iconProps?: IconBaseProps
question?: boolean
}
export const InfoTip = React.forwardRef<HTMLDivElement, InfoTipProps>(
function InfoTip(props, ref) {
(props, ref) => {
const { children, buttonProps, ...rest } = props
return (
<ToggleTip content={children} {...rest} ref={ref}>
@ -81,13 +84,56 @@ export const InfoTip = React.forwardRef<HTMLDivElement, InfoTipProps>(
colorPalette="gray"
{...buttonProps}
>
<HiOutlineInformationCircle />
{props.question ? <BsQuestionCircle {...props.iconProps}/> : <BsExclamationCircle />}
</IconButton>
</ToggleTip>
)
},
)
export interface InfoPopoverProps extends ChakraPopover.RootProps {
buttonProps?: IconButtonProps | undefined
iconProps?: IconBaseProps
question?: boolean
title?: string | React.JSX.Element
}
export const InfoPopover = React.forwardRef<HTMLDivElement, InfoPopoverProps>(
(props, ref) => {
const { children, buttonProps, ...rest } = props
return (
<ChakraPopover.Root {...rest}>
<ChakraPopover.Trigger asChild>
<IconButton
variant="ghost"
aria-label="info"
size="2xs"
colorPalette="gray"
{...buttonProps}
>
{props.question ? <BsQuestionCircle {...props.iconProps}/> : <BsExclamationCircle />}
</IconButton>
</ChakraPopover.Trigger>
<Portal>
<ChakraPopover.Positioner>
<ChakraPopover.Content width="max-content" maxWidth="90vw">
<ChakraPopover.Arrow />
<ChakraPopover.Body>
{props.title ? (
<ChakraPopover.Title fontWeight="medium">
{props.title}
</ChakraPopover.Title>
) : undefined}
{children}
</ChakraPopover.Body>
</ChakraPopover.Content>
</ChakraPopover.Positioner>
</Portal>
</ChakraPopover.Root>
)
},
)
export interface TextTipProps extends Partial<ToggleTipProps> {
text: string | React.ReactNode
}

View file

@ -161,6 +161,7 @@ export const IdleIcon = (props: {animated?: boolean} & ComponentProps<typeof RiZ
};
export const EllipsisIcon = LuEllipsis;
export const EllipsisChakraIcon = makeChakraIcon(LuEllipsis);
export const EllipsisButton = makeIconButton(EllipsisIcon);
export const EllipsisVerticalIcon = LuEllipsisVertical;
export const EllipsisVerticalButton = makeIconButton(EllipsisVerticalIcon);

View file

@ -1,14 +1,14 @@
import { HStack, Box, Field, Stack, Fieldset, Button, IconButton, DateInput, DatePicker, Portal, EmptyState } from "@chakra-ui/react"
import { HStack, Box, Field, Stack, Fieldset, Button, IconButton, DateInput, DatePicker, Portal, Text, Kbd, Span } from "@chakra-ui/react"
import { playEditStrictCreateSchema, type PlayObjectMinimal, type TrackData } from '../../../core/Atomic.js';
import { MSErrorBoundary } from '../ErrorBoundary.js';
import { useForm, formOptions } from '@tanstack/react-form';
import { TrackSearch } from "./TrackSearch.js";
import { ArtistSearch } from "./ArtistSearch.js";
import { CopyToRight, ResetIconRaw, TrashIconButton } from "../icons/ChakraIcons.js";
import { useState } from "react";
import { BracesIcon, CopyToRight, EllipsisChakraIcon, ResetIconRaw, TextIcon, TrashIconButton } from "../icons/ChakraIcons.js";
import { useState, type ReactNode } from "react";
import { AlbumSearch } from "./AlbumSearch.js";
import { DurationSepEditable } from "./DurationEditable.js";
import { Tooltip } from "../ToggleTip.js";
import { InfoPopover, Tooltip } from "../ToggleTip.js";
import { hashObject } from "../../../core/StringUtils.js";
import { dedupAlbumArtists } from "../../../core/MusicMetadata.js";
import { LuCalendar } from "react-icons/lu";
@ -23,6 +23,20 @@ export interface PlayEditProps {
onCancel?: () => void
}
const IconWrapper = (props: {children: ReactNode}) => <Box paddingX="1" marginX="1" display="inline-block" rounded="md" backgroundColor="bg.emphasized">{props.children}</Box>
const editPopoverContent = (
<Stack mt="4" gap="2">
<Span>The fields of your Play data are both <strong>plain text</strong> and <strong>search fields.</strong></Span>
<Span>Apply plain text to a field with <Kbd>Enter</Kbd> or selecting outside the field at any time.</Span>
<Span>The contents of the field are also used to <strong>search</strong> for rich data. Use <Kbd>Down Arrow</Kbd> or the field arrow to access results.</Span>
<Span>Select a result to apply <strong>all</strong> shown parts (album, artists, meta ids, etc...) or use the <IconWrapper><EllipsisChakraIcon size="md"/></IconWrapper> menu to apply only some parts.</Span>
<Text fontWeight="bold" my="2">Search Mode</Text>
<HStack><Span><strong>Context</strong><IconWrapper><BracesIcon size="md"/></IconWrapper>mode will search for results using your text and <i>all</i> filled fields + ids as hints/restrictions.</Span></HStack>
<HStack><Span><strong>Text</strong><IconWrapper><TextIcon size="md"/></IconWrapper>mode will search for results using <i>only</i> the text in the field.</Span></HStack>
</Stack>
);
const logSubmit: PlayEditProps['onSubmit'] = (val) => console.log(val, 'Play Edit Submit');
const cancelNoop = () => console.log('Clicked cancel');
@ -161,7 +175,6 @@ export const PlayEdit = (props: PlayEditProps) => {
return (
<Box position="relative">
<MSErrorBoundary>
<form
onSubmit={(e) => {
@ -169,7 +182,15 @@ export const PlayEdit = (props: PlayEditProps) => {
e.stopPropagation()
form.handleSubmit()
}}>
<HStack justify="flex-end">
<Box marginRight="auto">
<InfoPopover question
iconProps={{size: 'xl'}}
buttonProps={{size: 'md'}}
title={<Text fontWeight="bold" textStyle="lg">Editing and Searching</Text>}
positioning={{ placement: "top-end" }}>{editPopoverContent}</InfoPopover>
</Box>
<Button loading={isSubmitting} type="submit" variant="subtle" colorPalette="blue">{context === 'edit' ? 'Save' : 'Create'}</Button>
<Button disabled={isSubmitting} variant="subtle" colorPalette="red" onClick={() => onCancel()}>Cancel</Button>
<form.Subscribe