mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
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:
parent
7dee34d667
commit
41003abe52
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user