You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputField.tsx 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { css } from '@emotion/react';
  21. import styled from '@emotion/styled';
  22. import { forwardRef } from 'react';
  23. import tw from 'twin.macro';
  24. import { INPUT_SIZES } from '../../helpers/constants';
  25. import { themeBorder, themeColor, themeContrast } from '../../helpers/theme';
  26. import { InputSizeKeys, ThemedProps } from '../../types/theme';
  27. interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
  28. as?: React.ElementType;
  29. className?: string;
  30. isInvalid?: boolean;
  31. isValid?: boolean;
  32. size?: InputSizeKeys;
  33. }
  34. interface InputTextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  35. className?: string;
  36. isInvalid?: boolean;
  37. isValid?: boolean;
  38. size?: InputSizeKeys;
  39. }
  40. export const InputField = forwardRef<HTMLInputElement, InputProps>(
  41. ({ size = 'medium', style, ...props }, ref) => {
  42. return (
  43. <StyledInput ref={ref} style={{ ...style, '--inputSize': INPUT_SIZES[size] }} {...props} />
  44. );
  45. },
  46. );
  47. InputField.displayName = 'InputField';
  48. export const InputTextArea = forwardRef<HTMLTextAreaElement, InputTextAreaProps>(
  49. ({ size = 'medium', style, ...props }, ref) => {
  50. return (
  51. <StyledTextArea ref={ref} style={{ ...style, '--inputSize': INPUT_SIZES[size] }} {...props} />
  52. );
  53. },
  54. );
  55. InputTextArea.displayName = 'InputTextArea';
  56. const defaultStyle = (props: ThemedProps) => css`
  57. --border: ${themeBorder('default', 'inputBorder')(props)};
  58. --focusBorder: ${themeBorder('default', 'inputFocus')(props)};
  59. --focusOutline: ${themeBorder('focus', 'inputFocus')(props)};
  60. `;
  61. const dangerStyle = (props: ThemedProps) => css`
  62. --border: ${themeBorder('default', 'inputDanger')(props)};
  63. --focusBorder: ${themeBorder('default', 'inputDangerFocus')(props)};
  64. --focusOutline: ${themeBorder('focus', 'inputDangerFocus')(props)};
  65. `;
  66. const successStyle = (props: ThemedProps) => css`
  67. --border: ${themeBorder('default', 'inputSuccess')(props)};
  68. --focusBorder: ${themeBorder('default', 'inputSuccessFocus')(props)};
  69. --focusOutline: ${themeBorder('focus', 'inputSuccessFocus')(props)};
  70. `;
  71. const getInputVariant = (props: ThemedProps & { isInvalid?: boolean; isValid?: boolean }) => {
  72. const { isValid, isInvalid } = props;
  73. if (isInvalid) {
  74. return dangerStyle;
  75. } else if (isValid) {
  76. return successStyle;
  77. }
  78. return defaultStyle;
  79. };
  80. const baseStyle = (props: ThemedProps) => css`
  81. color: ${themeContrast('inputBackground')(props)};
  82. background: ${themeColor('inputBackground')(props)};
  83. border: var(--border);
  84. width: var(--inputSize);
  85. transition: border-color 0.2s ease;
  86. ${tw`sw-body-sm`}
  87. ${tw`sw-box-border`}
  88. ${tw`sw-rounded-2`}
  89. ${tw`sw-px-3 sw-py-2`}
  90. &::placeholder {
  91. color: ${themeColor('inputPlaceholder')(props)};
  92. }
  93. &:hover {
  94. border: var(--focusBorder);
  95. }
  96. &:active,
  97. &:focus,
  98. &:focus-within,
  99. &:focus-visible {
  100. border: var(--focusBorder);
  101. outline: var(--focusOutline);
  102. }
  103. &:disabled,
  104. &:disabled:hover {
  105. color: ${themeContrast('inputDisabled')(props)};
  106. background-color: ${themeColor('inputDisabled')(props)};
  107. border: ${themeBorder('default', 'inputDisabledBorder')(props)};
  108. outline: none;
  109. ${tw`sw-cursor-not-allowed`};
  110. &::placeholder {
  111. color: ${themeContrast('inputDisabled')(props)};
  112. }
  113. }
  114. `;
  115. const StyledInput = styled.input`
  116. ${getInputVariant}
  117. ${baseStyle}
  118. ${tw`sw-h-control`}
  119. `;
  120. const StyledTextArea = styled.textarea`
  121. ${getInputVariant};
  122. ${baseStyle};
  123. `;