Make sure "new label" button is always visible even when labels panel has scrollbar (#11586)

- Move "new label" button outside of scroll container so that it is always visible

# Important Notes
None
This commit is contained in:
somebody1234 2024-11-21 07:59:13 +10:00
parent 7dee34d667
commit 41003abe52

View File

@ -5,34 +5,24 @@ import { useMutation } from '@tanstack/react-query'
import PlusIcon from '#/assets/plus.svg' import PlusIcon from '#/assets/plus.svg'
import Trash2Icon from '#/assets/trash2.svg' import Trash2Icon from '#/assets/trash2.svg'
import { backendMutationOptions, useBackendQuery } from '#/hooks/backendHooks'
import * as modalProvider from '#/providers/ModalProvider'
import * as textProvider from '#/providers/TextProvider'
import * as ariaComponents from '#/components/AriaComponents' import * as ariaComponents from '#/components/AriaComponents'
import Label from '#/components/dashboard/Label' import Label from '#/components/dashboard/Label'
import FocusArea from '#/components/styled/FocusArea' import FocusArea from '#/components/styled/FocusArea'
import FocusRing from '#/components/styled/FocusRing' import FocusRing from '#/components/styled/FocusRing'
import SvgMask from '#/components/SvgMask' import SvgMask from '#/components/SvgMask'
import AssetEventType from '#/events/AssetEventType'
import { backendMutationOptions, useBackendQuery } from '#/hooks/backendHooks'
import { useDispatchAssetEvent } from '#/layouts/AssetsTable/EventListProvider'
import ConfirmDeleteModal from '#/modals/ConfirmDeleteModal' import ConfirmDeleteModal from '#/modals/ConfirmDeleteModal'
import DragModal from '#/modals/DragModal' import DragModal from '#/modals/DragModal'
import NewLabelModal from '#/modals/NewLabelModal' import NewLabelModal from '#/modals/NewLabelModal'
import * as modalProvider from '#/providers/ModalProvider'
import * as textProvider from '#/providers/TextProvider'
import type Backend from '#/services/Backend' import type Backend from '#/services/Backend'
import AssetEventType from '#/events/AssetEventType'
import { useDispatchAssetEvent } from '#/layouts/AssetsTable/EventListProvider'
import * as array from '#/utilities/array' import * as array from '#/utilities/array'
import type AssetQuery from '#/utilities/AssetQuery' import type AssetQuery from '#/utilities/AssetQuery'
import * as drag from '#/utilities/drag' import * as drag from '#/utilities/drag'
// ==============
// === Labels ===
// ==============
/** Props for a {@link Labels}. */ /** Props for a {@link Labels}. */
export interface LabelsProps { export interface LabelsProps {
readonly backend: Backend readonly backend: Backend
@ -61,96 +51,98 @@ export default function Labels(props: LabelsProps) {
return ( return (
<FocusArea direction="vertical"> <FocusArea direction="vertical">
{(innerProps) => ( {(innerProps) => (
<div <div className="flex flex-1 flex-col overflow-auto">
data-testid="labels"
className="flex flex-1 flex-col items-start gap-4 overflow-auto"
{...innerProps}
>
<ariaComponents.Text variant="subtitle" className="px-2 font-bold">
{getText('labels')}
</ariaComponents.Text>
<div <div
data-testid="labels-list" data-testid="labels"
aria-label={getText('labelsListLabel')} className="flex flex-col items-start gap-4 overflow-auto"
className="flex flex-1 flex-col items-start gap-labels overflow-auto" {...innerProps}
> >
{labels.map((label) => { <ariaComponents.Text variant="subtitle" className="px-2 font-bold">
const negated = currentNegativeLabels.some((term) => {getText('labels')}
array.shallowEqual(term, [label.value]), </ariaComponents.Text>
) <div
return ( data-testid="labels-list"
<div key={label.id} className="group relative flex items-center gap-label-icons"> aria-label={getText('labelsListLabel')}
<Label className="flex flex-1 flex-col items-start gap-labels overflow-auto"
draggable={draggable} >
color={label.color} {labels.map((label) => {
active={ const negated = currentNegativeLabels.some((term) =>
negated || array.shallowEqual(term, [label.value]),
currentLabels.some((term) => array.shallowEqual(term, [label.value])) )
} return (
negated={negated} <div key={label.id} className="group relative flex items-center gap-label-icons">
onPress={(event) => { <Label
setQuery((oldQuery) => draggable={draggable}
oldQuery.withToggled( color={label.color}
'labels', active={
'negativeLabels', negated ||
label.value, currentLabels.some((term) => array.shallowEqual(term, [label.value]))
event.shiftKey, }
), negated={negated}
) onPress={(event) => {
}} setQuery((oldQuery) =>
onDragStart={(event) => { oldQuery.withToggled(
drag.setDragImageToBlank(event) 'labels',
const payload: drag.LabelsDragPayload = new Set([label.value]) 'negativeLabels',
drag.LABELS.bind(event, payload) label.value,
setModal( event.shiftKey,
<DragModal ),
event={event} )
onDragEnd={() => { }}
drag.LABELS.unbind(payload) onDragStart={(event) => {
drag.setDragImageToBlank(event)
const payload: drag.LabelsDragPayload = new Set([label.value])
drag.LABELS.bind(event, payload)
setModal(
<DragModal
event={event}
onDragEnd={() => {
drag.LABELS.unbind(payload)
}}
>
<Label active color={label.color} onPress={() => {}}>
{label.value}
</Label>
</DragModal>,
)
}}
>
{label.value}
</Label>
<FocusRing placement="after">
<ariaComponents.DialogTrigger>
<ariaComponents.Button
variant="icon"
icon={Trash2Icon}
aria-label={getText('delete')}
tooltipPlacement="right"
className="relative flex size-4 text-delete opacity-0 transition-all after:absolute after:-inset-1 after:rounded-button-focus-ring group-has-[[data-focus-visible]]:active group-hover:active"
/>
<ConfirmDeleteModal
actionText={getText('deleteLabelActionText', label.value)}
doDelete={() => {
deleteTag([label.id, label.value])
}} }}
> />
<Label active color={label.color} onPress={() => {}}> </ariaComponents.DialogTrigger>
{label.value} </FocusRing>
</Label> </div>
</DragModal>, )
) })}
}} </div>
>
{label.value}
</Label>
<FocusRing placement="after">
<ariaComponents.DialogTrigger>
<ariaComponents.Button
variant="icon"
icon={Trash2Icon}
aria-label={getText('delete')}
tooltipPlacement="right"
className="relative flex size-4 text-delete opacity-0 transition-all after:absolute after:-inset-1 after:rounded-button-focus-ring group-has-[[data-focus-visible]]:active group-hover:active"
/>
<ConfirmDeleteModal
actionText={getText('deleteLabelActionText', label.value)}
doDelete={() => {
deleteTag([label.id, label.value])
}}
/>
</ariaComponents.DialogTrigger>
</FocusRing>
</div>
)
})}
<ariaComponents.DialogTrigger>
<ariaComponents.Button
size="xsmall"
variant="outline"
className="pl-1 pr-2"
/* eslint-disable-next-line no-restricted-syntax */
icon={<SvgMask src={PlusIcon} alt="" className="ml-auto size-[8px]" />}
>
{getText('newLabelButtonLabel')}
</ariaComponents.Button>
<NewLabelModal backend={backend} />
</ariaComponents.DialogTrigger>
</div> </div>
<ariaComponents.DialogTrigger>
<ariaComponents.Button
size="xsmall"
variant="outline"
className="mt-1 self-start pl-1 pr-2"
/* eslint-disable-next-line no-restricted-syntax */
icon={<SvgMask src={PlusIcon} alt="" className="ml-auto size-[8px]" />}
>
{getText('newLabelButtonLabel')}
</ariaComponents.Button>
<NewLabelModal backend={backend} />
</ariaComponents.DialogTrigger>
</div> </div>
)} )}
</FocusArea> </FocusArea>