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

feat(ui): Allow buttons to provide icons default sizes (#61791)

This works by adding a IconDefaultsContext that is consumed in the base
SVGIcon component.

This means instead of writing

```tsx
<Button size="sm" icon={<IconDelete size="xs" />} />
```

you can now just write

```tsx
<Button size="sm" icon={<IconDelete />} />
```
Evan Purkhiser 1 год назад
Родитель
Сommit
51b29d150e
3 измененных файлов с 47 добавлено и 10 удалено
  1. 14 1
      static/app/components/button.tsx
  2. 10 9
      static/app/icons/svgIcon.tsx
  3. 23 0
      static/app/icons/useIconDefaults.tsx

+ 14 - 1
static/app/components/button.tsx

@@ -7,6 +7,8 @@ import InteractionStateLayer from 'sentry/components/interactionStateLayer';
 import ExternalLink from 'sentry/components/links/externalLink';
 import Link from 'sentry/components/links/link';
 import {Tooltip, TooltipProps} from 'sentry/components/tooltip';
+import {SVGIconProps} from 'sentry/icons/svgIcon';
+import {IconDefaultsProvider} from 'sentry/icons/useIconDefaults';
 import HookStore from 'sentry/stores/hookStore';
 import {space} from 'sentry/styles/space';
 import mergeRefs from 'sentry/utils/mergeRefs';
@@ -193,6 +195,17 @@ type LinkButtonProps =
   | HrefLinkButtonPropsWithChildren
   | HrefLinkButtonPropsWithAriaLabel;
 
+/**
+ * Default sizes to use for SVGIcon
+ */
+const ICON_SIZES: Partial<
+  Record<NonNullable<BaseButtonProps['size']>, SVGIconProps['size']>
+> = {
+  xs: 'xs',
+  sm: 'xs',
+  md: 'sm',
+};
+
 function BaseButton({
   size = 'md',
   to,
@@ -297,7 +310,7 @@ function BaseButton({
       <ButtonLabel size={size} borderless={borderless}>
         {icon && (
           <Icon size={size} hasChildren={hasChildren}>
-            {icon}
+            <IconDefaultsProvider size={ICON_SIZES[size]}>{icon}</IconDefaultsProvider>
           </Icon>
         )}
         {children}

+ 10 - 9
static/app/icons/svgIcon.tsx

@@ -3,9 +3,11 @@ import {useTheme} from '@emotion/react';
 
 import {Aliases, Color, IconSize} from 'sentry/utils/theme';
 
+import {useIconDefaults} from './useIconDefaults';
+
 export interface SVGIconProps extends React.SVGAttributes<SVGSVGElement> {
   className?: string;
-  color?: Color | keyof Aliases;
+  color?: Color | keyof Aliases | 'currentColor';
   /**
    * DO NOT USE THIS! Please use the `size` prop
    *
@@ -15,21 +17,20 @@ export interface SVGIconProps extends React.SVGAttributes<SVGSVGElement> {
   size?: IconSize;
 }
 
-export const SvgIcon = forwardRef<SVGSVGElement, SVGIconProps>(function SvgIcon(
-  {
+export const SvgIcon = forwardRef<SVGSVGElement, SVGIconProps>((props, ref) => {
+  const {
     color: providedColor = 'currentColor',
     size: providedSize = 'sm',
-    legacySize,
     viewBox = '0 0 16 16',
-    ...props
-  },
-  ref
-) {
+    legacySize,
+    ...rest
+  } = useIconDefaults(props);
+
   const theme = useTheme();
   const color = theme[providedColor] ?? providedColor;
   const size = legacySize ?? theme.iconSizes[providedSize];
 
   return (
-    <svg {...props} viewBox={viewBox} fill={color} height={size} width={size} ref={ref} />
+    <svg {...rest} viewBox={viewBox} fill={color} height={size} width={size} ref={ref} />
   );
 });

+ 23 - 0
static/app/icons/useIconDefaults.tsx

@@ -0,0 +1,23 @@
+import {createContext, useContext} from 'react';
+
+import {SVGIconProps} from './svgIcon';
+
+const IconDefaultsContext = createContext<SVGIconProps>({});
+
+/**
+ * Use this context provider to set default values for icons.
+ */
+function IconDefaultsProvider({children, ...props}: SVGIconProps) {
+  return (
+    <IconDefaultsContext.Provider value={props}>{children}</IconDefaultsContext.Provider>
+  );
+}
+
+/**
+ * Provides default props for SVGIconProps via
+ */
+function useIconDefaults(props: SVGIconProps) {
+  return {...useContext(IconDefaultsContext), ...props};
+}
+
+export {IconDefaultsProvider, useIconDefaults};