Status.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. * Copyright 2021-2024 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React, { MouseEvent, ReactNode, useEffect, useMemo, useRef} from "react";
  14. import Chip from "@mui/material/Chip";
  15. import Avatar from "@mui/material/Avatar";
  16. import CheckCircleIcon from "@mui/icons-material/CheckCircle";
  17. import WarningIcon from "@mui/icons-material/Warning";
  18. import ErrorIcon from "@mui/icons-material/Error";
  19. import InfoIcon from "@mui/icons-material/Info";
  20. import { getInitials } from "../../utils";
  21. import { TaipyBaseProps } from "./utils";
  22. import { useClassNames } from "../../utils/hooks";
  23. export interface StatusType {
  24. status: string;
  25. message: string;
  26. }
  27. interface StatusProps extends TaipyBaseProps {
  28. value: StatusType;
  29. onClose?: (evt: MouseEvent) => void;
  30. icon?: ReactNode;
  31. withIcons?: boolean;
  32. content?: string;
  33. }
  34. const status2Color = (status: string): "error" | "info" | "success" | "warning" => {
  35. status = (status || "").toLowerCase();
  36. status = status.length == 0 ? " " : status.charAt(0);
  37. switch (status) {
  38. case "s":
  39. return "success";
  40. case "w":
  41. return "warning";
  42. case "e":
  43. return "error";
  44. }
  45. return "info";
  46. };
  47. // Function to get the appropriate icon based on the status
  48. const GetStatusIcon = (status: string, withIcons?: boolean): ReactNode => {
  49. // Use useMemo to memoize the iconProps as well
  50. const color = status2Color(status);
  51. // Memoize the iconProps
  52. const iconProps = {
  53. sx: { fontSize: 20, color: `${color}.main` }}
  54. if (withIcons) {
  55. switch (color) {
  56. case "success":
  57. return <CheckCircleIcon {...iconProps} />;
  58. case "warning":
  59. return <WarningIcon {...iconProps} />;
  60. case "error":
  61. return <ErrorIcon {...iconProps} />;
  62. default:
  63. return <InfoIcon {...iconProps} />;
  64. }
  65. } else {
  66. return getInitials(status);
  67. }
  68. };
  69. const chipSx = { alignSelf: "flex-start" };
  70. const defaultAvatarStyle = {
  71. width: '100%',
  72. height: '100%',
  73. display: 'flex',
  74. alignItems: 'center',
  75. justifyContent: 'center',
  76. };
  77. const defaultAvatarSx = {
  78. bgcolor: 'transparent'
  79. };
  80. const baseStyles = {
  81. fontSize: '1rem',
  82. textShadow: '1px 1px 4px black, -1px -1px 4px black',
  83. };
  84. const isSvgUrl = (content?: string) => {
  85. return content?.substring(content?.length - 4).toLowerCase() === ".svg"; // Check if it ends with ".svg"
  86. };
  87. const isInlineSvg = (content?: string) => {
  88. return content?.substring(0, 4).toLowerCase() === "<svg"; // Check if the content starts with "<svg"
  89. };
  90. const Status = (props: StatusProps) => {
  91. const { value, id } = props;
  92. const content = props.content || undefined;
  93. const withIcons = props.withIcons;
  94. const svgRef = useRef<HTMLDivElement>(null);
  95. const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
  96. useEffect(() => {
  97. if (content && svgRef.current) {
  98. svgRef.current.innerHTML = content;
  99. }
  100. }, [content]);
  101. const chipProps = useMemo(() => {
  102. const cp: Record<string, unknown> = {};
  103. const statusColor = status2Color(value.status);
  104. cp.color = statusColor;
  105. if (isSvgUrl(content)) {
  106. cp.avatar = (
  107. <Avatar src={content} data-testid="Avatar" />
  108. );
  109. }
  110. else if(content && isInlineSvg(content)){
  111. cp.avatar = (
  112. <Avatar
  113. sx={defaultAvatarSx}
  114. data-testid="Avatar"
  115. >
  116. <div
  117. ref={svgRef}
  118. style={defaultAvatarStyle}
  119. />
  120. </Avatar>
  121. );
  122. }
  123. else {
  124. cp.avatar = (
  125. <Avatar
  126. sx={{
  127. bgcolor: withIcons
  128. ? 'transparent'
  129. : `${statusColor}.main`,
  130. color: `${statusColor}.contrastText`,
  131. ...baseStyles
  132. }}
  133. data-testid="Avatar"
  134. >
  135. {GetStatusIcon(value.status, withIcons)}
  136. </Avatar>
  137. );
  138. }
  139. if (props.onClose) {
  140. cp.onDelete = props.onClose;
  141. }
  142. if (props.icon) {
  143. cp.deleteIcon = props.icon;
  144. }
  145. return cp;
  146. }, [value.status, props.onClose, props.icon, withIcons, content]);
  147. return <Chip id={id} variant="outlined" {...chipProps} label={value.message} sx={chipSx} className={className} />;
  148. };
  149. export default Status;