Skip to content

Commit

Permalink
ref(flags): search button leads to focus in drawer (#77839)
Browse files Browse the repository at this point in the history
Add a search icon button to the feature flag section, which opens the
fly-out drawer with the search focused. This is the same behavior as the
breadcrumbs section.




https://github.com/user-attachments/assets/553cfd39-f4b3-480b-aa0d-ce961e2d6361
  • Loading branch information
michellewzhang committed Sep 20, 2024
1 parent 460232f commit d160151
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 49 deletions.
20 changes: 3 additions & 17 deletions static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useMemo, useState} from 'react';
import {useMemo, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';

Expand All @@ -24,13 +24,14 @@ import {
NavigationCrumbs,
SearchInput,
ShortId,
} from 'sentry/components/events/eventReplay/eventDrawer';
} from 'sentry/components/events/eventDrawer';
import {
applyBreadcrumbSearch,
BREADCRUMB_SORT_LOCALSTORAGE_KEY,
BREADCRUMB_SORT_OPTIONS,
BreadcrumbSort,
} from 'sentry/components/events/interfaces/breadcrumbs';
import useFocusControl from 'sentry/components/events/useFocusControl';
import {InputGroup} from 'sentry/components/inputGroup';
import {IconClock, IconFilter, IconSearch, IconSort, IconTimer} from 'sentry/icons';
import {t} from 'sentry/locale';
Expand All @@ -49,21 +50,6 @@ export const enum BreadcrumbControlOptions {
SORT = 'sort',
}

function useFocusControl(initialFocusControl?: BreadcrumbControlOptions) {
const [focusControl, setFocusControl] = useState(initialFocusControl);
// If the focused control element is blurred, unset the state to remove styles
// This will allow us to simulate :focus-visible on the button elements.
const getFocusProps = useCallback(
(option: BreadcrumbControlOptions) => {
return option === focusControl
? {autoFocus: true, onBlur: () => setFocusControl(undefined)}
: {};
},
[focusControl]
);
return {getFocusProps};
}

interface BreadcrumbsDrawerProps {
breadcrumbs: EnhancedCrumb[];
event: Event;
Expand Down
73 changes: 43 additions & 30 deletions static/app/components/events/featureFlags/eventFeatureFlagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
CardContainer,
FeatureFlagDrawer,
FLAG_SORT_OPTIONS,
FlagControlOptions,
FlagSort,
getLabel,
} from 'sentry/components/events/featureFlags/featureFlagDrawer';
import useDrawer from 'sentry/components/globalDrawer';
import KeyValueData, {
type KeyValueDataContentProps,
} from 'sentry/components/keyValueData';
import {IconMegaphone, IconSort} from 'sentry/icons';
import {IconMegaphone, IconSearch, IconSort} from 'sentry/icons';
import {t} from 'sentry/locale';
import type {Event, FeatureFlag} from 'sentry/types/event';
import type {Group} from 'sentry/types/group';
Expand Down Expand Up @@ -91,35 +92,39 @@ export function EventFeatureFlagList({
? [...hydratedFlags].reverse()
: hydratedFlags;

const onViewAllFlags = useCallback(() => {
trackAnalytics('flags.view-all-clicked', {
organization,
});
openDrawer(
() => (
<FeatureFlagDrawer
group={group}
event={event}
project={project}
hydratedFlags={hydratedFlags}
initialSort={sortMethod}
/>
),
{
ariaLabel: t('Feature flags drawer'),
// We prevent a click on the 'View All' button from closing the drawer so that
// we don't reopen it immediately, and instead let the button handle this itself.
shouldCloseOnInteractOutside: element => {
const viewAllButton = viewAllButtonRef.current;
if (viewAllButton?.contains(element)) {
return false;
}
return true;
},
transitionProps: {stiffness: 1000},
}
);
}, [openDrawer, event, group, project, sortMethod, hydratedFlags, organization]);
const onViewAllFlags = useCallback(
(focusControl?: FlagControlOptions) => {
trackAnalytics('flags.view-all-clicked', {
organization,
});
openDrawer(
() => (
<FeatureFlagDrawer
group={group}
event={event}
project={project}
hydratedFlags={hydratedFlags}
initialSort={sortMethod}
focusControl={focusControl}
/>
),
{
ariaLabel: t('Feature flags drawer'),
// We prevent a click on the 'View All' button from closing the drawer so that
// we don't reopen it immediately, and instead let the button handle this itself.
shouldCloseOnInteractOutside: element => {
const viewAllButton = viewAllButtonRef.current;
if (viewAllButton?.contains(element)) {
return false;
}
return true;
},
transitionProps: {stiffness: 1000},
}
);
},
[openDrawer, event, group, project, sortMethod, hydratedFlags, organization]
);

if (!hydratedFlags.length) {
return null;
Expand All @@ -128,10 +133,18 @@ export function EventFeatureFlagList({
const actions = (
<ButtonBar gap={1}>
{feedbackButton}
<Button
aria-label={t('Open Feature Flag Search')}
icon={<IconSearch size="xs" />}
size="xs"
title={t('Open Search')}
onClick={() => onViewAllFlags(FlagControlOptions.SEARCH)}
/>
<Button
size="xs"
aria-label={t('View All')}
ref={viewAllButtonRef}
title={t('View All Flags')}
onClick={() => {
isDrawerOpen ? closeDrawer() : onViewAllFlags();
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
NavigationCrumbs,
SearchInput,
ShortId,
} from 'sentry/components/events/eventReplay/eventDrawer';
} from 'sentry/components/events/eventDrawer';
import useFocusControl from 'sentry/components/events/useFocusControl';
import {InputGroup} from 'sentry/components/inputGroup';
import KeyValueData, {
type KeyValueDataContentProps,
Expand Down Expand Up @@ -74,6 +75,7 @@ interface FlagDrawerProps {
hydratedFlags: KeyValueDataContentProps[];
initialSort: FlagSort;
project: Project;
focusControl?: FlagControlOptions;
}

export function FeatureFlagDrawer({
Expand All @@ -82,10 +84,12 @@ export function FeatureFlagDrawer({
project,
initialSort,
hydratedFlags,
focusControl: initialFocusControl,
}: FlagDrawerProps) {
const [sortMethod, setSortMethod] = useState<FlagSort>(initialSort);
const [search, setSearch] = useState('');
const organization = useOrganization();
const {getFocusProps} = useFocusControl(initialFocusControl);

const handleSortAlphabetical = (flags: KeyValueDataContentProps[]) => {
return [...flags].sort((a, b) => {
Expand All @@ -111,6 +115,7 @@ export function FeatureFlagDrawer({
setSearch(e.target.value.toLowerCase());
}}
aria-label={t('Search Flags')}
{...getFocusProps(FlagControlOptions.SEARCH)}
/>
<InputGroup.TrailingItems disablePointerEvents>
<IconSearch size="xs" />
Expand Down
21 changes: 21 additions & 0 deletions static/app/components/events/useFocusControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {useCallback, useState} from 'react';

import type {BreadcrumbControlOptions} from 'sentry/components/events/breadcrumbs/breadcrumbsDrawer';
import type {FlagControlOptions} from 'sentry/components/events/featureFlags/featureFlagDrawer';

type FocusControlOption = BreadcrumbControlOptions | FlagControlOptions;

export default function useFocusControl(initialFocusControl?: FocusControlOption) {
const [focusControl, setFocusControl] = useState(initialFocusControl);
// If the focused control element is blurred, unset the state to remove styles
// This will allow us to simulate :focus-visible on the button elements.
const getFocusProps = useCallback(
(option: FocusControlOption) => {
return option === focusControl
? {autoFocus: true, onBlur: () => setFocusControl(undefined)}
: {};
},
[focusControl]
);
return {getFocusProps};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Header,
NavigationCrumbs,
ShortId,
} from 'sentry/components/events/eventReplay/eventDrawer';
} from 'sentry/components/events/eventDrawer';
import LoadingError from 'sentry/components/loadingError';
import Pagination from 'sentry/components/pagination';
import {t} from 'sentry/locale';
Expand Down

0 comments on commit d160151

Please sign in to comment.