Skip to content

Commit 2fd29a5

Browse files
authored
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
1 parent 96b21f2 commit 2fd29a5

File tree

1 file changed

+93
-101
lines changed

1 file changed

+93
-101
lines changed

app/gui/src/dashboard/layouts/Labels.tsx

Lines changed: 93 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,24 @@ import { useMutation } from '@tanstack/react-query'
55

66
import PlusIcon from '#/assets/plus.svg'
77
import Trash2Icon from '#/assets/trash2.svg'
8-
9-
import { backendMutationOptions, useBackendQuery } from '#/hooks/backendHooks'
10-
11-
import * as modalProvider from '#/providers/ModalProvider'
12-
import * as textProvider from '#/providers/TextProvider'
13-
148
import * as ariaComponents from '#/components/AriaComponents'
159
import Label from '#/components/dashboard/Label'
1610
import FocusArea from '#/components/styled/FocusArea'
1711
import FocusRing from '#/components/styled/FocusRing'
1812
import SvgMask from '#/components/SvgMask'
19-
13+
import AssetEventType from '#/events/AssetEventType'
14+
import { backendMutationOptions, useBackendQuery } from '#/hooks/backendHooks'
15+
import { useDispatchAssetEvent } from '#/layouts/AssetsTable/EventListProvider'
2016
import ConfirmDeleteModal from '#/modals/ConfirmDeleteModal'
2117
import DragModal from '#/modals/DragModal'
2218
import NewLabelModal from '#/modals/NewLabelModal'
23-
19+
import * as modalProvider from '#/providers/ModalProvider'
20+
import * as textProvider from '#/providers/TextProvider'
2421
import type Backend from '#/services/Backend'
25-
26-
import AssetEventType from '#/events/AssetEventType'
27-
import { useDispatchAssetEvent } from '#/layouts/AssetsTable/EventListProvider'
2822
import * as array from '#/utilities/array'
2923
import type AssetQuery from '#/utilities/AssetQuery'
3024
import * as drag from '#/utilities/drag'
3125

32-
// ==============
33-
// === Labels ===
34-
// ==============
35-
3626
/** Props for a {@link Labels}. */
3727
export interface LabelsProps {
3828
readonly backend: Backend
@@ -61,96 +51,98 @@ export default function Labels(props: LabelsProps) {
6151
return (
6252
<FocusArea direction="vertical">
6353
{(innerProps) => (
64-
<div
65-
data-testid="labels"
66-
className="flex flex-1 flex-col items-start gap-4 overflow-auto"
67-
{...innerProps}
68-
>
69-
<ariaComponents.Text variant="subtitle" className="px-2 font-bold">
70-
{getText('labels')}
71-
</ariaComponents.Text>
54+
<div className="flex flex-1 flex-col overflow-auto">
7255
<div
73-
data-testid="labels-list"
74-
aria-label={getText('labelsListLabel')}
75-
className="flex flex-1 flex-col items-start gap-labels overflow-auto"
56+
data-testid="labels"
57+
className="flex flex-col items-start gap-4 overflow-auto"
58+
{...innerProps}
7659
>
77-
{labels.map((label) => {
78-
const negated = currentNegativeLabels.some((term) =>
79-
array.shallowEqual(term, [label.value]),
80-
)
81-
return (
82-
<div key={label.id} className="group relative flex items-center gap-label-icons">
83-
<Label
84-
draggable={draggable}
85-
color={label.color}
86-
active={
87-
negated ||
88-
currentLabels.some((term) => array.shallowEqual(term, [label.value]))
89-
}
90-
negated={negated}
91-
onPress={(event) => {
92-
setQuery((oldQuery) =>
93-
oldQuery.withToggled(
94-
'labels',
95-
'negativeLabels',
96-
label.value,
97-
event.shiftKey,
98-
),
99-
)
100-
}}
101-
onDragStart={(event) => {
102-
drag.setDragImageToBlank(event)
103-
const payload: drag.LabelsDragPayload = new Set([label.value])
104-
drag.LABELS.bind(event, payload)
105-
setModal(
106-
<DragModal
107-
event={event}
108-
onDragEnd={() => {
109-
drag.LABELS.unbind(payload)
60+
<ariaComponents.Text variant="subtitle" className="px-2 font-bold">
61+
{getText('labels')}
62+
</ariaComponents.Text>
63+
<div
64+
data-testid="labels-list"
65+
aria-label={getText('labelsListLabel')}
66+
className="flex flex-1 flex-col items-start gap-labels overflow-auto"
67+
>
68+
{labels.map((label) => {
69+
const negated = currentNegativeLabels.some((term) =>
70+
array.shallowEqual(term, [label.value]),
71+
)
72+
return (
73+
<div key={label.id} className="group relative flex items-center gap-label-icons">
74+
<Label
75+
draggable={draggable}
76+
color={label.color}
77+
active={
78+
negated ||
79+
currentLabels.some((term) => array.shallowEqual(term, [label.value]))
80+
}
81+
negated={negated}
82+
onPress={(event) => {
83+
setQuery((oldQuery) =>
84+
oldQuery.withToggled(
85+
'labels',
86+
'negativeLabels',
87+
label.value,
88+
event.shiftKey,
89+
),
90+
)
91+
}}
92+
onDragStart={(event) => {
93+
drag.setDragImageToBlank(event)
94+
const payload: drag.LabelsDragPayload = new Set([label.value])
95+
drag.LABELS.bind(event, payload)
96+
setModal(
97+
<DragModal
98+
event={event}
99+
onDragEnd={() => {
100+
drag.LABELS.unbind(payload)
101+
}}
102+
>
103+
<Label active color={label.color} onPress={() => {}}>
104+
{label.value}
105+
</Label>
106+
</DragModal>,
107+
)
108+
}}
109+
>
110+
{label.value}
111+
</Label>
112+
<FocusRing placement="after">
113+
<ariaComponents.DialogTrigger>
114+
<ariaComponents.Button
115+
variant="icon"
116+
icon={Trash2Icon}
117+
aria-label={getText('delete')}
118+
tooltipPlacement="right"
119+
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"
120+
/>
121+
<ConfirmDeleteModal
122+
actionText={getText('deleteLabelActionText', label.value)}
123+
doDelete={() => {
124+
deleteTag([label.id, label.value])
110125
}}
111-
>
112-
<Label active color={label.color} onPress={() => {}}>
113-
{label.value}
114-
</Label>
115-
</DragModal>,
116-
)
117-
}}
118-
>
119-
{label.value}
120-
</Label>
121-
<FocusRing placement="after">
122-
<ariaComponents.DialogTrigger>
123-
<ariaComponents.Button
124-
variant="icon"
125-
icon={Trash2Icon}
126-
aria-label={getText('delete')}
127-
tooltipPlacement="right"
128-
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"
129-
/>
130-
<ConfirmDeleteModal
131-
actionText={getText('deleteLabelActionText', label.value)}
132-
doDelete={() => {
133-
deleteTag([label.id, label.value])
134-
}}
135-
/>
136-
</ariaComponents.DialogTrigger>
137-
</FocusRing>
138-
</div>
139-
)
140-
})}
141-
<ariaComponents.DialogTrigger>
142-
<ariaComponents.Button
143-
size="xsmall"
144-
variant="outline"
145-
className="pl-1 pr-2"
146-
/* eslint-disable-next-line no-restricted-syntax */
147-
icon={<SvgMask src={PlusIcon} alt="" className="ml-auto size-[8px]" />}
148-
>
149-
{getText('newLabelButtonLabel')}
150-
</ariaComponents.Button>
151-
<NewLabelModal backend={backend} />
152-
</ariaComponents.DialogTrigger>
126+
/>
127+
</ariaComponents.DialogTrigger>
128+
</FocusRing>
129+
</div>
130+
)
131+
})}
132+
</div>
153133
</div>
134+
<ariaComponents.DialogTrigger>
135+
<ariaComponents.Button
136+
size="xsmall"
137+
variant="outline"
138+
className="mt-1 self-start pl-1 pr-2"
139+
/* eslint-disable-next-line no-restricted-syntax */
140+
icon={<SvgMask src={PlusIcon} alt="" className="ml-auto size-[8px]" />}
141+
>
142+
{getText('newLabelButtonLabel')}
143+
</ariaComponents.Button>
144+
<NewLabelModal backend={backend} />
145+
</ariaComponents.DialogTrigger>
154146
</div>
155147
)}
156148
</FocusArea>

0 commit comments

Comments
 (0)