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 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 Label from '#/components/dashboard/Label'
import FocusArea from '#/components/styled/FocusArea'
import FocusRing from '#/components/styled/FocusRing'
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 DragModal from '#/modals/DragModal'
import NewLabelModal from '#/modals/NewLabelModal'
import * as modalProvider from '#/providers/ModalProvider'
import * as textProvider from '#/providers/TextProvider'
import type Backend from '#/services/Backend'
import AssetEventType from '#/events/AssetEventType'
import { useDispatchAssetEvent } from '#/layouts/AssetsTable/EventListProvider'
import * as array from '#/utilities/array'
import type AssetQuery from '#/utilities/AssetQuery'
import * as drag from '#/utilities/drag'
// ==============
// === Labels ===
// ==============
/** Props for a {@link Labels}. */
export interface LabelsProps {
readonly backend: Backend
@ -61,96 +51,98 @@ export default function Labels(props: LabelsProps) {
return (
<FocusArea direction="vertical">
{(innerProps) => (
<div
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 className="flex flex-1 flex-col overflow-auto">
<div
data-testid="labels-list"
aria-label={getText('labelsListLabel')}
className="flex flex-1 flex-col items-start gap-labels overflow-auto"
data-testid="labels"
className="flex flex-col items-start gap-4 overflow-auto"
{...innerProps}
>
{labels.map((label) => {
const negated = currentNegativeLabels.some((term) =>
array.shallowEqual(term, [label.value]),
)
return (
<div key={label.id} className="group relative flex items-center gap-label-icons">
<Label
draggable={draggable}
color={label.color}
active={
negated ||
currentLabels.some((term) => array.shallowEqual(term, [label.value]))
}
negated={negated}
onPress={(event) => {
setQuery((oldQuery) =>
oldQuery.withToggled(
'labels',
'negativeLabels',
label.value,
event.shiftKey,
),
)
}}
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)
<ariaComponents.Text variant="subtitle" className="px-2 font-bold">
{getText('labels')}
</ariaComponents.Text>
<div
data-testid="labels-list"
aria-label={getText('labelsListLabel')}
className="flex flex-1 flex-col items-start gap-labels overflow-auto"
>
{labels.map((label) => {
const negated = currentNegativeLabels.some((term) =>
array.shallowEqual(term, [label.value]),
)
return (
<div key={label.id} className="group relative flex items-center gap-label-icons">
<Label
draggable={draggable}
color={label.color}
active={
negated ||
currentLabels.some((term) => array.shallowEqual(term, [label.value]))
}
negated={negated}
onPress={(event) => {
setQuery((oldQuery) =>
oldQuery.withToggled(
'labels',
'negativeLabels',
label.value,
event.shiftKey,
),
)
}}
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={() => {}}>
{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])
}}
/>
</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>
/>
</ariaComponents.DialogTrigger>
</FocusRing>
</div>
)
})}
</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>
)}
</FocusArea>