SaveButton.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import React, { Component } from "react";
  2. import styled from "styled-components";
  3. import loading from "assets/loading.gif";
  4. type Props = {
  5. text?: string;
  6. onClick: () => void;
  7. disabled?: boolean;
  8. status?: string | null;
  9. color?: string;
  10. rounded?: boolean;
  11. helper?: string | null;
  12. saveText?: string | null;
  13. // Makes flush with corner if not within a modal
  14. makeFlush?: boolean;
  15. clearPosition?: boolean;
  16. statusPosition?: "right" | "left";
  17. // Provide the classname to modify styles from other components
  18. className?: string;
  19. successText?: string;
  20. absoluteSave?: boolean;
  21. };
  22. const SaveButton: React.FC<Props> = (props) => {
  23. const renderStatus = () => {
  24. if (props.status) {
  25. if (props.status === "successful") {
  26. return (
  27. <StatusWrapper position={props.statusPosition} successful={true}>
  28. <i className="material-icons">done</i>
  29. <StatusTextWrapper>
  30. {props?.successText || "Successfully updated"}
  31. </StatusTextWrapper>
  32. </StatusWrapper>
  33. );
  34. } else if (props.status === "loading") {
  35. return (
  36. <StatusWrapper position={props.statusPosition} successful={false}>
  37. <LoadingGif src={loading} />
  38. <StatusTextWrapper>
  39. {props.saveText || "Updating . . ."}
  40. </StatusTextWrapper>
  41. </StatusWrapper>
  42. );
  43. } else if (props.status === "error") {
  44. return (
  45. <StatusWrapper position={props.statusPosition} successful={false}>
  46. <i className="material-icons">error_outline</i>
  47. <StatusTextWrapper>Could not update</StatusTextWrapper>
  48. </StatusWrapper>
  49. );
  50. } else {
  51. return (
  52. <StatusWrapper position={props.statusPosition} successful={false}>
  53. <i className="material-icons">error_outline</i>
  54. <StatusTextWrapper>{props.status}</StatusTextWrapper>
  55. </StatusWrapper>
  56. );
  57. }
  58. } else if (props.helper) {
  59. return (
  60. <StatusWrapper position={props.statusPosition} successful={true}>
  61. {props.helper}
  62. </StatusWrapper>
  63. );
  64. }
  65. };
  66. return (
  67. <ButtonWrapper
  68. absoluteSave={props.absoluteSave}
  69. makeFlush={props.makeFlush}
  70. clearPosition={props.clearPosition}
  71. className={props.className}
  72. >
  73. {props.statusPosition !== "right" && <div>{renderStatus()}</div>}
  74. <Button
  75. rounded={props.rounded}
  76. disabled={props.disabled}
  77. onClick={props.onClick}
  78. color={props.color}
  79. >
  80. {props.children || props.text}
  81. </Button>
  82. {props.statusPosition === "right" && <div>{renderStatus()}</div>}
  83. </ButtonWrapper>
  84. );
  85. };
  86. export default SaveButton;
  87. const LoadingGif = styled.img`
  88. width: 15px;
  89. height: 15px;
  90. margin-right: 9px;
  91. margin-bottom: 0px;
  92. `;
  93. const StatusTextWrapper = styled.p`
  94. display: -webkit-box;
  95. line-clamp: 2;
  96. -webkit-line-clamp: 2;
  97. -webkit-box-orient: vertical;
  98. line-height: 19px;
  99. margin: 0;
  100. `;
  101. // TODO: prevent status re-render on form refresh to allow animation
  102. // animation: statusFloatIn 0.5s;
  103. const StatusWrapper = styled.div<{
  104. successful: boolean;
  105. position: "right" | "left";
  106. }>`
  107. display: flex;
  108. align-items: center;
  109. font-family: "Work Sans", sans-serif;
  110. font-size: 13px;
  111. color: #ffffff55;
  112. ${(props) => {
  113. if (props.position !== "right") {
  114. return "margin-right: 25px;";
  115. }
  116. return "margin-left: 25px;";
  117. }}
  118. max-width: 500px;
  119. overflow: hidden;
  120. text-overflow: ellipsis;
  121. > i {
  122. font-size: 18px;
  123. margin-right: 10px;
  124. float: left;
  125. color: ${(props) => (props.successful ? "#4797ff" : "#fcba03")};
  126. }
  127. animation-fill-mode: forwards;
  128. @keyframes statusFloatIn {
  129. from {
  130. opacity: 0;
  131. transform: translateY(10px);
  132. }
  133. to {
  134. opacity: 1;
  135. transform: translateY(0px);
  136. }
  137. }
  138. `;
  139. const ButtonWrapper = styled.div`
  140. ${(props: { makeFlush: boolean; clearPosition?: boolean; absoluteSave: boolean }) => {
  141. const baseStyles = `
  142. display: flex;
  143. position: ${props.absoluteSave ? "absolute" : ""};
  144. bottom: ${props.absoluteSave ? "5px" : ""};
  145. align-items: center;
  146. z-index: 99;
  147. `;
  148. if (props.clearPosition) {
  149. return baseStyles;
  150. }
  151. if (!props.makeFlush) {
  152. return `
  153. ${baseStyles}
  154. position: absolute;
  155. justify-content: flex-end;
  156. bottom: 25px;
  157. right: 27px;
  158. left: 27px;
  159. `;
  160. }
  161. return `
  162. ${baseStyles}
  163. position: absolute;
  164. justify-content: flex-end;
  165. bottom: 5px;
  166. right: 0;
  167. `;
  168. }}
  169. `;
  170. const Button = styled.button<{
  171. disabled: boolean;
  172. color: string;
  173. rounded: boolean;
  174. }>`
  175. height: 35px;
  176. font-size: 13px;
  177. font-weight: 500;
  178. font-family: "Work Sans", sans-serif;
  179. color: white;
  180. display: flex;
  181. align-items: center;
  182. padding: 6px 20px 7px 20px;
  183. text-align: left;
  184. border: 0;
  185. border-radius: ${(props) => (props.rounded ? "100px" : "5px")};
  186. background: ${(props) => (!props.disabled ? (props.color || props.theme.button) : "#aaaabb")};
  187. cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
  188. user-select: none;
  189. :focus {
  190. outline: 0;
  191. }
  192. :hover {
  193. filter: ${(props) => (!props.disabled ? "brightness(120%)" : "")};
  194. }
  195. > i {
  196. color: white;
  197. width: 18px;
  198. height: 18px;
  199. font-weight: 600;
  200. font-size: 14px;
  201. border-radius: 20px;
  202. display: flex;
  203. align-items: center;
  204. margin-right: 10px;
  205. margin-left: -5px;
  206. justify-content: center;
  207. }
  208. `;