Просмотр исходного кода

ref(flags): search button leads to focus in drawer (#77839)

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
Michelle Zhang 5 месяцев назад
Родитель
Сommit
d1601517f0

+ 3 - 17
static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx

@@ -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';
 
@@ -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';
@@ -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;

+ 0 - 0
static/app/components/events/eventReplay/eventDrawer.tsx → static/app/components/events/eventDrawer.tsx


+ 43 - 30
static/app/components/events/featureFlags/eventFeatureFlagList.tsx

@@ -9,6 +9,7 @@ import {
   CardContainer,
   FeatureFlagDrawer,
   FLAG_SORT_OPTIONS,
+  FlagControlOptions,
   FlagSort,
   getLabel,
 } from 'sentry/components/events/featureFlags/featureFlagDrawer';
@@ -16,7 +17,7 @@ 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';
@@ -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;
@@ -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();
         }}

+ 6 - 1
static/app/components/events/featureFlags/featureFlagDrawer.tsx

@@ -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,
@@ -74,6 +75,7 @@ interface FlagDrawerProps {
   hydratedFlags: KeyValueDataContentProps[];
   initialSort: FlagSort;
   project: Project;
+  focusControl?: FlagControlOptions;
 }
 
 export function FeatureFlagDrawer({
@@ -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) => {
@@ -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" />

+ 21 - 0
static/app/components/events/useFocusControl.tsx

@@ -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};
+}

+ 1 - 1
static/app/views/issueDetails/groupEventAttachments/groupEventAttachmentsDrawer.tsx

@@ -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';