Browse Source

ref: move input to components/core (#85839)

This PR builds on top of https://github.com/getsentry/sentry/pull/85835
and moves the `Input` component to `core/components`. It also changes
the default export in favour of a named export.
Dominik Dorfmeister 2 weeks ago
parent
commit
b1aefd647a

+ 1 - 1
static/app/components/arithmeticBuilder/index.tsx

@@ -10,7 +10,7 @@ import type {
   AggregateFunction,
   FunctionArgument,
 } from 'sentry/components/arithmeticBuilder/types';
-import {inputStyles} from 'sentry/components/input';
+import {inputStyles} from 'sentry/components/core/input';
 import PanelProvider from 'sentry/utils/panelProvider';
 
 export interface ArithmeticBuilderProps {

+ 1 - 1
static/app/components/comboBox/index.tsx

@@ -16,8 +16,8 @@ import {
   getHiddenOptions,
   getItemsWithKeys,
 } from 'sentry/components/compactSelect/utils';
+import {Input} from 'sentry/components/core/input';
 import {GrowingInput} from 'sentry/components/growingInput';
-import Input from 'sentry/components/input';
 import InteractionStateLayer from 'sentry/components/interactionStateLayer';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {Overlay, PositionWrapper} from 'sentry/components/overlay';

+ 1 - 1
static/app/components/confirmDelete.tsx

@@ -2,8 +2,8 @@ import {Fragment} from 'react';
 
 import Confirm from 'sentry/components/confirm';
 import {Alert} from 'sentry/components/core/alert';
+import {Input} from 'sentry/components/core/input';
 import FieldGroup from 'sentry/components/forms/fieldGroup';
-import Input from 'sentry/components/input';
 import {t} from 'sentry/locale';
 
 interface Props

+ 67 - 0
static/app/components/core/input.stories.tsx

@@ -0,0 +1,67 @@
+import {Fragment} from 'react';
+import styled from '@emotion/styled';
+
+import JSXNode from 'sentry/components/stories/jsxNode';
+import storyBook from 'sentry/stories/storyBook';
+import {space} from 'sentry/styles/space';
+
+import {Input} from './input';
+
+// eslint-disable-next-line import/no-webpack-loader-syntax
+import types from '!!type-loader!sentry/components/input';
+
+export default storyBook('Input', (story, APIReference) => {
+  APIReference(types.Input);
+
+  story('Sizes', () => {
+    return (
+      <Fragment>
+        <p>
+          The <JSXNode name="Input" /> component comes in different sizes:
+        </p>
+        <Grid>
+          <label>
+            <code>md (default):</code> <Input size="md" />
+          </label>
+          <label>
+            <code>sm:</code> <Input size="sm" value="value" />
+          </label>
+          <label>
+            <code>xs:</code> <Input size="xs" placeholder="placeholder" />
+          </label>
+        </Grid>
+      </Fragment>
+    );
+  });
+
+  story('Locked', () => {
+    return (
+      <Fragment>
+        <p>
+          <JSXNode name="Input" /> can either be <code>disabled</code> or{' '}
+          <code>readonly</code> to make them non-editable. <code>aria-disabled</code>{' '}
+          fields are styled like a <code>disabled</code> field, but they remain
+          interactive like a <code>readonly</code> field:
+        </p>
+        <Grid>
+          <label>
+            <code>disabled:</code> <Input disabled value="this is disabled" />
+          </label>
+          <label>
+            <code>aria-disabled:</code>{' '}
+            <Input aria-disabled value="this is aria-disabled" />
+          </label>
+          <label>
+            <code>readonly:</code> <Input readOnly value="this is readonly" />
+          </label>
+        </Grid>
+      </Fragment>
+    );
+  });
+});
+
+const Grid = styled('div')`
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: ${space(2)};
+`;

+ 103 - 0
static/app/components/core/input.tsx

@@ -0,0 +1,103 @@
+import {forwardRef} from 'react';
+import isPropValid from '@emotion/is-prop-valid';
+import type {Theme} from '@emotion/react';
+import {css} from '@emotion/react';
+import styled from '@emotion/styled';
+
+import type {FormSize} from 'sentry/utils/theme';
+
+export interface InputStylesProps {
+  monospace?: boolean;
+  nativeSize?: React.InputHTMLAttributes<HTMLInputElement>['size'];
+  readOnly?: React.InputHTMLAttributes<HTMLInputElement>['readOnly'];
+  size?: FormSize;
+  type?: React.HTMLInputTypeAttribute;
+}
+
+export const inputStyles = (p: InputStylesProps & {theme: Theme}) => css`
+  display: block;
+  width: 100%;
+  color: ${p.theme.formText};
+  background: ${p.theme.background};
+  border: 1px solid ${p.theme.border};
+  border-radius: ${p.theme.borderRadius};
+  box-shadow: inset ${p.theme.dropShadowMedium};
+  resize: vertical;
+  transition:
+    border 0.1s,
+    box-shadow 0.1s;
+
+  ${p.monospace ? `font-family: ${p.theme.text.familyMono};` : ''}
+  ${p.readOnly ? 'cursor: default;' : ''}
+
+  ${p.theme.form[p.size ?? 'md']}
+  ${p.theme.formPadding[p.size ?? 'md']}
+
+  &::placeholder {
+    color: ${p.theme.formPlaceholder};
+    opacity: 1;
+  }
+
+  &[disabled],
+  &[aria-disabled='true'] {
+    background: ${p.theme.backgroundSecondary};
+    color: ${p.theme.disabled};
+    cursor: not-allowed;
+
+    &::placeholder {
+      color: ${p.theme.disabled};
+    }
+  }
+
+  &:focus,
+  &:focus-visible {
+    outline: none;
+    border-color: ${p.theme.focusBorder};
+    box-shadow: ${p.theme.focusBorder} 0 0 0 1px;
+  }
+  &[type='number'] {
+    appearance: textfield;
+    -moz-appearance: textfield;
+    font-variant-numeric: tabular-nums;
+  }
+  &::-webkit-outer-spin-button,
+  &::-webkit-inner-spin-button {
+    -webkit-appearance: none;
+    margin: 0;
+  }
+`;
+
+export interface InputProps
+  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'readOnly'>,
+    InputStylesProps {}
+
+/**
+ * Basic input component.
+ *
+ * Use the `size` prop ('md', 'sm', 'xs') to control the input's height &
+ * padding. To use the native size attribute (which controls the number of
+ * characters the input should fit), use the `nativeSize` prop instead.
+ *
+ * To add leading/trailing items (e.g. a search icon on the left side), use
+ * InputControl (components/inputControl) instead.
+ */
+export const Input = styled(
+  forwardRef<HTMLInputElement, InputProps>(
+    (
+      {
+        // Do not forward `required` to avoid default browser behavior
+        required: _required,
+        // Do not forward `size` since it's used for custom styling, not as the
+        // native `size` attribute (for that, use `nativeSize` instead)
+        size: _size,
+        // Use `nativeSize` as the native `size` attribute
+        nativeSize,
+        ...props
+      },
+      ref
+    ) => <input {...props} ref={ref} size={nativeSize} />
+  ),
+  {shouldForwardProp: prop => typeof prop === 'string' && isPropValid(prop)}
+)`
+  ${inputStyles}
+`;

+ 1 - 1
static/app/components/devtoolbar/components/featureFlags/customOverride.tsx

@@ -1,8 +1,8 @@
 import {useContext, useState} from 'react';
 import {css} from '@emotion/react';
 
+import {Input} from 'sentry/components/core/input';
 import {resetButtonCss} from 'sentry/components/devtoolbar/styles/reset';
-import Input from 'sentry/components/input';
 import Switch from 'sentry/components/switchButton';
 import {IconAdd} from 'sentry/icons';
 

+ 1 - 1
static/app/components/devtoolbar/components/featureFlags/featureFlagsPanel.tsx

@@ -1,8 +1,8 @@
 import {type Dispatch, Fragment, type SetStateAction, useState} from 'react';
 import {css} from '@emotion/react';
 
+import {Input} from 'sentry/components/core/input';
 import {resetButtonCss, resetFlexRowCss} from 'sentry/components/devtoolbar/styles/reset';
-import Input from 'sentry/components/input';
 import {PanelTable} from 'sentry/components/panels/panelTable';
 import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {IconChevron, IconClose} from 'sentry/icons';

+ 1 - 1
static/app/components/dropdownAutoComplete/menu.tsx

@@ -3,8 +3,8 @@ import styled from '@emotion/styled';
 import memoize from 'lodash/memoize';
 
 import AutoComplete from 'sentry/components/autoComplete';
+import {Input} from 'sentry/components/core/input';
 import DropdownBubble from 'sentry/components/dropdownBubble';
-import Input from 'sentry/components/input';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';

+ 1 - 1
static/app/components/editableText.tsx

@@ -2,7 +2,7 @@ import {useCallback, useEffect, useRef, useState} from 'react';
 import styled from '@emotion/styled';
 
 import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
-import Input from 'sentry/components/input';
+import {Input} from 'sentry/components/core/input';
 import TextOverflow from 'sentry/components/textOverflow';
 import {IconEdit} from 'sentry/icons/iconEdit';
 import {space} from 'sentry/styles/space';

+ 1 - 1
static/app/components/events/autofix/autofixHighlightPopup.tsx

@@ -15,11 +15,11 @@ import {addErrorMessage} from 'sentry/actionCreators/indicator';
 import {SeerIcon} from 'sentry/components/ai/SeerIcon';
 import UserAvatar from 'sentry/components/avatar/userAvatar';
 import {Button} from 'sentry/components/button';
+import {Input} from 'sentry/components/core/input';
 import {
   makeAutofixQueryKey,
   useAutofixData,
 } from 'sentry/components/events/autofix/useAutofix';
-import Input from 'sentry/components/input';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import {IconChevron} from 'sentry/icons';
 import {t} from 'sentry/locale';

Some files were not shown because too many files changed in this diff