UNPKG

@grafana/ui

Version:
257 lines (254 loc) • 8.81 kB
import { jsx, jsxs } from 'react/jsx-runtime'; import { cx, css } from '@emotion/css'; import { forwardRef, useContext } from 'react'; import { useMeasure } from 'react-use'; import { useTheme2 } from '../../themes/ThemeContext.mjs'; import { stylesFactory } from '../../themes/stylesFactory.mjs'; import { useFieldContext } from '../Forms/FieldContext.mjs'; import { getFocusStyle, sharedInputStyle } from '../Forms/commonStyles.mjs'; import { Spinner } from '../Spinner/Spinner.mjs'; import { AutoSizeInputContext } from './AutoSizeInputContext.mjs'; "use strict"; const Input = forwardRef((props, ref) => { const { className, addonAfter, addonBefore, prefix, suffix: suffixProp, invalid: invalidProp, loading: loadingProp, width = 0, id: idProp, disabled: disabledProp, "aria-describedby": ariaDescribedByProp, "aria-labelledby": ariaLabelledByProp, ...restProps } = props; const fieldContext = useFieldContext(); const invalid = invalidProp != null ? invalidProp : fieldContext.invalid; const loading = loadingProp != null ? loadingProp : fieldContext.loading; const id = idProp != null ? idProp : fieldContext.id; const disabled = disabledProp != null ? disabledProp : fieldContext.disabled; const ariaDescribedBy = ariaDescribedByProp != null ? ariaDescribedByProp : fieldContext["aria-describedby"]; const ariaLabelledBy = ariaLabelledByProp != null ? ariaLabelledByProp : fieldContext["aria-labelledby"]; const [prefixRef, prefixRect] = useMeasure(); const [suffixRef, suffixRect] = useMeasure(); const isInAutoSizeInput = useContext(AutoSizeInputContext); const accessoriesWidth = (prefixRect.width || 0) + (suffixRect.width || 0); const autoSizeWidth = isInAutoSizeInput && width ? width + accessoriesWidth / 8 : void 0; const theme = useTheme2(); const styles = getInputStyles({ theme, invalid: !!invalid, width: autoSizeWidth ? void 0 : width }); const suffix = suffixProp || loading && /* @__PURE__ */ jsx(Spinner, { inline: true }); return /* @__PURE__ */ jsxs( "div", { className: cx(styles.wrapper, className), style: autoSizeWidth ? { width: theme.spacing(autoSizeWidth) } : void 0, "data-testid": "input-wrapper", children: [ !!addonBefore && /* @__PURE__ */ jsx("div", { className: styles.addon, children: addonBefore }), /* @__PURE__ */ jsxs("div", { className: styles.inputWrapper, children: [ prefix && /* @__PURE__ */ jsx("div", { className: styles.prefix, ref: prefixRef, children: prefix }), /* @__PURE__ */ jsx( "input", { ref, className: styles.input, "aria-invalid": !!invalid, id, disabled, "aria-describedby": ariaDescribedBy, "aria-labelledby": ariaLabelledBy, ...restProps, onWheel: restProps.type === "number" ? (e) => { var _a; e.currentTarget.blur(); (_a = restProps.onWheel) == null ? void 0 : _a.call(restProps, e); } : restProps.onWheel, style: { paddingLeft: prefix ? prefixRect.width + 12 : void 0, paddingRight: suffix || loading ? suffixRect.width + 12 : void 0 } } ), suffix && /* @__PURE__ */ jsx("div", { className: styles.suffix, ref: suffixRef, children: suffix }) ] }), !!addonAfter && /* @__PURE__ */ jsx("div", { className: styles.addon, children: addonAfter }) ] } ); }); Input.displayName = "Input"; const getInputStyles = stylesFactory(({ theme, invalid = false, width }) => { const prefixSuffixStaticWidth = "28px"; const prefixSuffix = css({ position: "absolute", top: 0, zIndex: 1, display: "flex", alignItems: "center", justifyContent: "center", flexGrow: 0, flexShrink: 0, fontSize: theme.typography.size.md, height: "100%", /* Min width specified for prefix/suffix classes used outside React component*/ minWidth: prefixSuffixStaticWidth, color: theme.colors.text.secondary }); return { // Wraps inputWrapper and addons wrapper: cx( css({ label: "input-wrapper", display: "flex", width: width ? theme.spacing(width) : "100%", height: theme.spacing(theme.components.height.md), borderRadius: theme.shape.radius.default, "&:hover": { "> .prefix, .suffix, .input": { borderColor: invalid ? theme.colors.error.border : theme.colors.primary.border }, // only show number buttons on hover "input[type='number']": { appearance: "textfield" }, "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button": { // Need type assertion here due to the use of !important // see https://github.com/frenic/csstype/issues/114#issuecomment-697201978 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions WebkitAppearance: "inner-spin-button !important", opacity: 1 } } }) ), // Wraps input and prefix/suffix inputWrapper: css({ label: "input-inputWrapper", position: "relative", flexGrow: 1, /* we want input to be above addons, especially for focused state */ zIndex: 1, /* when input rendered with addon before only*/ "&:not(:first-child):last-child": { "> input": { borderLeft: "none", borderTopLeftRadius: "unset", borderBottomLeftRadius: "unset" } }, /* when input rendered with addon after only*/ "&:first-child:not(:last-child)": { "> input": { borderRight: "none", borderTopRightRadius: "unset", borderBottomRightRadius: "unset" } }, /* when rendered with addon before and after */ "&:not(:first-child):not(:last-child)": { "> input": { borderRight: "none", borderTopRightRadius: "unset", borderBottomRightRadius: "unset", borderTopLeftRadius: "unset", borderBottomLeftRadius: "unset" } }, input: { /* paddings specified for classes used outside React component */ "&:not(:first-child)": { paddingLeft: prefixSuffixStaticWidth }, "&:not(:last-child)": { paddingRight: prefixSuffixStaticWidth }, "&[readonly]": { cursor: "default" } } }), input: cx( getFocusStyle(theme), sharedInputStyle(theme, invalid), css({ label: "input-input", position: "relative", zIndex: 0, flexGrow: 1, borderRadius: theme.shape.radius.default, height: "100%", width: "100%" }) ), inputDisabled: css({ backgroundColor: theme.colors.action.disabledBackground, color: theme.colors.action.disabledText, border: `1px solid ${theme.colors.action.disabledBackground}`, "&:focus": { boxShadow: "none" } }), addon: css({ label: "input-addon", display: "flex", justifyContent: "center", alignItems: "center", flexGrow: 0, flexShrink: 0, position: "relative", "&:first-child": { borderTopRightRadius: "unset", borderBottomRightRadius: "unset", "> :last-child": { borderTopRightRadius: "unset", borderBottomRightRadius: "unset" } }, "&:last-child": { borderTopLeftRadius: "unset", borderBottomLeftRadius: "unset", "> :first-child": { borderTopLeftRadius: "unset", borderBottomLeftRadius: "unset" } }, "> *:focus": { /* we want anything that has focus and is an addon to be above input */ zIndex: 2 } }), prefix: cx( prefixSuffix, css({ label: "input-prefix", paddingLeft: theme.spacing(1), paddingRight: theme.spacing(0.5), borderRight: "none", borderTopRightRadius: "unset", borderBottomRightRadius: "unset" }) ), suffix: cx( prefixSuffix, css({ label: "input-suffix", paddingLeft: theme.spacing(1), paddingRight: theme.spacing(1), borderLeft: "none", borderTopLeftRadius: "unset", borderBottomLeftRadius: "unset", right: 0 }) ), loadingIndicator: css({ "& + *": { marginLeft: theme.spacing(0.5) } }) }; }); export { Input, getInputStyles }; //# sourceMappingURL=Input.mjs.map