UNPKG

orcs-design-system

Version:
870 lines (865 loc) 25.7 kB
import React, { useMemo } from "react"; import styled, { ThemeProvider, keyframes } from "styled-components"; import { space, layout, variant, position, compose } from "styled-system"; import PropTypes from "prop-types"; import Header, { Text } from "../Typography"; import Icon from "../Icon"; import Popover from "../Popover"; import StatusDot from "../StatusDot"; import css from "@styled-system/css"; import { themeGet } from "@styled-system/theme-get"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const avatarStyles = compose(space, layout, position); export const COLOR_CHANGE = keyframes(["0%{background-position:-468px 0;}100%{background-position:468px 0;}"]); // Helper function to resolve customSize values const resolveCustomSize = (customSize, props) => { if (!customSize) return null; // Check if customSize is a pixel value or a theme value if (customSize.includes("px") || customSize.includes("rem") || customSize.includes("em") || customSize.includes("%")) { return customSize; } else { // It's a theme value, resolve it using themeGet return themeGet(`avatarScale.${customSize}`)(props) || customSize; } }; const AvatarWrapper = styled("div").withConfig({ displayName: "Avatar__AvatarWrapper", componentId: "sc-1wtet2j-0" })(css({ boxSizing: "border-box", display: "flex", alignItems: "center", position: "relative" }), props => variant({ prop: "sizing", variants: { small: { fontSize: themeGet("fontSizes.1")(props) }, large: { fontSize: themeGet("fontSizes.2")(props) }, default: {} } }), variant({ prop: "type", variants: { inverted: {} } }), avatarStyles); const TextContent = styled("div").withConfig({ displayName: "Avatar__TextContent", componentId: "sc-1wtet2j-1" })(css({ marginLeft: "s", lineHeight: "0.8" }), props => variant({ prop: "sizing", variants: { small: { fontSize: themeGet("fontSizes.1")(props) }, large: { marginLeft: "r", fontSize: themeGet("fontSizes.2")(props) } } }), props => variant({ prop: "type", variants: { inverted: { color: themeGet("colors.white")(props) } } })); const Image = styled("img").withConfig({ displayName: "Avatar__Image", componentId: "sc-1wtet2j-2" })(props => css({ width: resolveCustomSize(props.customSize, props) || themeGet("avatarScale.avatarDefault")(props), height: resolveCustomSize(props.customSize, props) || themeGet("avatarScale.avatarDefault")(props), flex: "0 0 auto", backgroundColor: themeGet("colors.greyLighter")(props), objectFit: "cover", objectPosition: "center", objectRepeat: "no-repeat", border: "0", display: "block", borderRadius: "50%" }), props => variant({ prop: "sizing", variants: { small: { width: themeGet("avatarScale.avatarSmall")(props), height: themeGet("avatarScale.avatarSmall")(props) }, large: { width: themeGet("avatarScale.avatarLarge")(props), height: themeGet("avatarScale.avatarLarge")(props) } } }), props => variant({ prop: "shape", variants: { square: { borderRadius: themeGet("radii.2")(props) }, hexagon: { borderRadius: "0", clipPath: "polygon(50% 0, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%)" }, tag: { borderRadius: "0", clipPath: "polygon(6% 0, 95% 0, 95% 65%, 50% 100%, 6% 65%)" } } })); const Shape = styled("div").withConfig({ displayName: "Avatar__Shape", componentId: "sc-1wtet2j-3" })(props => css({ width: resolveCustomSize(props.customSize, props) || themeGet("avatarScale.avatarDefault")(props), height: resolveCustomSize(props.customSize, props) || themeGet("avatarScale.avatarDefault")(props), fontWeight: themeGet("fontWeights.2")(props), flex: "0 0 auto", background: `linear-gradient(135deg, ${themeGet("colors.primaryLightest")(props)}, ${themeGet("colors.primaryLight")(props)})`, color: themeGet("colors.white")(props), border: "0", display: "flex", borderRadius: "50%", alignItems: "center", justifyContent: "center", textAlign: "center", textTransform: `${props => props.uppercase ? "uppercase" : "none"}`, a: { color: themeGet("colors.greyDarkest")(props) }, "&.inverted a": { color: themeGet("colors.white")(props) } }), props => variant({ prop: "sizing", variants: { small: { width: themeGet("avatarScale.avatarSmall")(props), height: themeGet("avatarScale.avatarSmall")(props) }, large: { width: themeGet("avatarScale.avatarLarge")(props), height: themeGet("avatarScale.avatarLarge")(props) }, default: {} } }), props => variant({ prop: "type", variants: { inverted: { backgroundColor: themeGet("colors.greyDarker")(props), color: themeGet("colors.white")(props) }, default: {} } }), props => variant({ prop: "shape", variants: { square: { borderRadius: themeGet("radii.2")(props) }, hexagon: { borderRadius: "0", clipPath: "polygon(50% 0, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%)" }, tag: { borderRadius: "0", paddingBottom: "8px", clipPath: "polygon(6% 0, 95% 0, 95% 65%, 50% 100%, 6% 65%)" } } })); const Title = styled(_ref => { let { titleLevel, ...props } = _ref; const Heading = Header[titleLevel]; return /*#__PURE__*/React.cloneElement(/*#__PURE__*/_jsx(Heading, {}), props); }).withConfig({ displayName: "Avatar__Title", componentId: "sc-1wtet2j-4" })(props => css({ fontSize: themeGet("fontSizes.2")(props), "> a": { fontSize: themeGet("fontSizes.2")(props), color: props.type == "inverted" ? themeGet("colors.primaryLight")(props) : themeGet("colors.primary")(props) }, "> div > a": { fontSize: themeGet("fontSizes.2")(props), color: props.type == "inverted" ? themeGet("colors.primaryLight")(props) : themeGet("colors.primary")(props) }, "> div > div > a": { fontSize: themeGet("fontSizes.2")(props), color: props.type == "inverted" ? themeGet("colors.primaryLight")(props) : themeGet("colors.primary")(props) } }), props => variant({ prop: "sizing", variants: { small: { fontSize: themeGet("fontSizes.2")(props), "> a": { fontSize: themeGet("fontSizes.2")(props) }, "> div > a": { fontSize: themeGet("fontSizes.2")(props) }, "> div > div > a": { fontSize: themeGet("fontSizes.2")(props) } }, large: { fontSize: themeGet("fontSizes.3")(props), "> a": { fontSize: themeGet("fontSizes.3")(props) }, "> div > a": { fontSize: themeGet("fontSizes.3")(props) }, "> div > div > a": { fontSize: themeGet("fontSizes.3")(props) } } } }), props => variant({ prop: "type", variants: { inverted: { color: themeGet("colors.white")(props) } } })); const Subtitle = styled(Text).withConfig({ displayName: "Avatar__Subtitle", componentId: "sc-1wtet2j-5" })(props => css({ color: themeGet("colors.greyDark")(props), fontSize: themeGet("fontSizes.1")(props) }), props => variant({ prop: "sizing", variants: { small: { fontSize: themeGet("fontSizes.0")(props), fontWeight: themeGet("fontWeights.1")(props) }, large: { fontSize: themeGet("fontSizes.2")(props), fontWeight: themeGet("fontWeights.1")(props) } } }), props => variant({ prop: "type", variants: { inverted: { color: themeGet("colors.white")(props) }, default: {} } })); const LocalTime = styled(Popover).withConfig({ displayName: "Avatar__LocalTime", componentId: "sc-1wtet2j-6" })(["position:absolute;top:0;left:0;"]); // HeaderAvatar specific styles const HeaderAvatarWrapper = styled("div").withConfig({ displayName: "Avatar__HeaderAvatarWrapper", componentId: "sc-1wtet2j-7" })(_ref2 => { let { $size, $shape } = _ref2; return { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", width: $size, height: $size, transition: "width 200ms ease-in-out, height 200ms ease-in-out", boxShadow: "0px 3px 6px rgba(0,0,0,0.15)", borderRadius: $shape === "circle" || !$shape ? "50%" : $shape === "square" ? "6px" : "0", background: "transparent" }; }, avatarStyles); const HeaderAvatarBorder = styled("div").withConfig({ displayName: "Avatar__HeaderAvatarBorder", componentId: "sc-1wtet2j-8" })(_ref3 => { let { $size, $shape, $sizing } = _ref3; return { width: $size, height: $size, transition: "width 200ms ease-in-out, height 200ms ease-in-out, border-width 200ms ease-in-out", border: $sizing === "large" ? "6px solid #fff" : "4px solid #fff", borderRadius: $shape === "circle" || !$shape ? "50%" : $shape === "square" ? "6px" : "0", overflow: "hidden", display: "flex", alignItems: "center", justifyContent: "center", background: "#fff" }; }); const HeaderAvatarLoading = styled("div").withConfig({ displayName: "Avatar__HeaderAvatarLoading", componentId: "sc-1wtet2j-9" })(["width:", ";height:", ";background:linear-gradient( to right,rgba(0,0,0,0.1) 5%,rgba(0,0,0,0.05) 65%,rgba(0,0,0,0.1) 70% );background-size:200px;animation:", " 20s linear infinite;"], _ref4 => { let { $size } = _ref4; return $size; }, _ref5 => { let { $size } = _ref5; return $size; }, COLOR_CHANGE); const HeaderAvatarImage = styled("img").withConfig({ displayName: "Avatar__HeaderAvatarImage", componentId: "sc-1wtet2j-10" })(_ref6 => { let { $size, $shape } = _ref6; return { width: $size, height: $size, transition: "width 200ms ease-in-out, height 200ms ease-in-out", objectFit: "cover", objectPosition: "center", objectRepeat: "no-repeat", borderRadius: $shape === "circle" || !$shape ? "50%" : $shape === "square" ? "6px" : "0", display: "block" }; }); const HeaderAvatarShape = styled("div").withConfig({ displayName: "Avatar__HeaderAvatarShape", componentId: "sc-1wtet2j-11" })(_ref7 => { let { $size, $shape } = _ref7; return { width: $size, height: $size, transition: "width 200ms ease-in-out, height 200ms ease-in-out", display: "flex", alignItems: "center", justifyContent: "center", background: "#e6e6e6", color: "#222", fontWeight: 600, fontSize: `calc(${$size} / 2.5)`, borderRadius: $shape === "circle" || !$shape ? "50%" : $shape === "square" ? "6px" : "0", ...($shape === "hexagon" && { clipPath: "polygon(50% 0, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%)" }), ...($shape === "tag" && { clipPath: "polygon(6% 0, 95% 0, 95% 65%, 50% 100%, 6% 65%)" }) }; }); const HeaderAvatarAction = styled("button").withConfig({ displayName: "Avatar__HeaderAvatarAction", componentId: "sc-1wtet2j-12" })(_ref8 => { let { $shape } = _ref8; return { cursor: "pointer", padding: "0", background: "none", border: "none", outline: "none", appearance: "none", position: "relative", zIndex: "5", borderRadius: $shape === "circle" || !$shape ? "50%" : "6px", ...($shape === "hexagon" && { clipPath: "polygon(50% 0, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%)" }), ...($shape === "tag" && { clipPath: "polygon(6% 0, 95% 0, 95% 65%, 50% 100%, 6% 65%)" }), "&:hover, &:focus": { "& > .icon-container": { opacity: "1" } } }; }); const IconContainer = styled("div").withConfig({ displayName: "Avatar__IconContainer", componentId: "sc-1wtet2j-13" })(_ref9 => { let { $shape, $sizing, $hasImage } = _ref9; // Simplified width calculation (no change based on image presence) const width = $sizing === "large" ? "calc(100% - 12px)" : "calc(100% - 8px)"; // Height calculation with reduction when no image const heightReduction = $sizing === "large" ? $hasImage ? 17 : 12 : $hasImage ? 12 : 8; const height = `calc(100% - ${heightReduction}px)`; return { opacity: "0", transition: "opacity 0.2s ease-in-out", position: "absolute", top: $sizing === "large" ? "6px" : "4px", left: $sizing === "large" ? "6px" : "4px", width, height, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0, 0, 0, 0.5)", zIndex: "2", borderRadius: $shape === "circle" || !$shape ? "50%" : "0", ...($shape === "hexagon" && { clipPath: "polygon(50% 0, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%)" }), ...($shape === "tag" && { clipPath: "polygon(6% 0, 95% 0, 95% 65%, 50% 100%, 6% 65%)" }) }; }); const Avatar = _ref0 => { let { sizing, image, imageAlt, initials, title, titleLevel = "H3", subtitle, subtitleContent, type, theme, shape = "circle", localTime, uppercase = true, customSize, headerAvatar = false, onClick, loading = false, ...props } = _ref0; const hasTitle = !!title; const hasSubtitle = !!subtitle; const hasSubtitleContent = !!subtitleContent; const alt = useMemo(() => { if (imageAlt) { return imageAlt; } else if (typeof title === "string") { return `Avatar for ${title}`; } }, [imageAlt, title]); // HeaderAvatar logic if (headerAvatar) { // Sizing logic let size = theme?.avatarScale?.headerAvatarDefault || "65px"; if (sizing === "large") { size = theme?.avatarScale?.headerAvatarLarge || "150px"; } if (customSize) { size = resolveCustomSize(customSize, { theme }) || customSize; } const imgContent = image ? /*#__PURE__*/_jsx(HeaderAvatarImage, { src: image, alt: alt, $size: size, $shape: shape }) : /*#__PURE__*/_jsx(HeaderAvatarShape, { $size: size, $shape: shape, children: initials ? initials : shape === "hexagon" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "users"] }) : shape === "tag" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "tag"] }) : shape === "square" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "users"] }) : /*#__PURE__*/_jsx(Icon, { icon: ["fas", "user"] }) }); const avatarContent = /*#__PURE__*/_jsx(HeaderAvatarWrapper, { ...props, $size: size, $shape: shape, children: /*#__PURE__*/_jsx(HeaderAvatarBorder, { $size: size, $shape: shape, $sizing: sizing, children: loading ? /*#__PURE__*/_jsx(HeaderAvatarLoading, { $size: size }) : imgContent }) }); if (onClick && !loading) { return /*#__PURE__*/_jsx(Popover, { direction: "right", width: "fit-content", $shape: shape, text: shape === "circle" ? "Upload profile picture" : "Change image", headerAvatarSizing: sizing, tabIndex: "-1", children: /*#__PURE__*/_jsxs(HeaderAvatarAction, { onClick: onClick, "aria-label": "Edit avatar", $shape: shape, $sizing: sizing, children: [/*#__PURE__*/_jsx(IconContainer, { $shape: shape, $sizing: sizing, $hasImage: !!image, className: "icon-container", children: shape === "circle" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "camera"], size: sizing === "large" ? "lg" : "1x", color: "white" }) : /*#__PURE__*/_jsx(Icon, { icon: ["fas", "image"], size: sizing === "large" ? "lg" : "1x", color: "white" }) }), avatarContent] }) }); } return avatarContent; } const component = /*#__PURE__*/_jsxs(AvatarWrapper, { ...props, type: type, sizing: sizing, children: [image ? /*#__PURE__*/_jsx(Image, { src: image, sizing: sizing, shape: shape, alt: alt, customSize: customSize }) : /*#__PURE__*/_jsx(Shape, { shape: shape, sizing: sizing, customSize: customSize, type: type, className: type == "inverted" ? "inverted" : "", uppercase: uppercase, children: initials ? initials : shape === "hexagon" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "users"] }) : shape === "tag" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "tag"] }) : shape === "square" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "users"] }) : /*#__PURE__*/_jsx(Icon, { icon: ["fas", "user"] }) }), (hasTitle || hasSubtitle) && /*#__PURE__*/_jsxs(TextContent, { type: type, sizing: sizing, theme: theme, children: [hasTitle && /*#__PURE__*/_jsx(Title, { titleLevel: titleLevel, type: type, sizing: sizing, theme: theme, children: title }), hasSubtitle && /*#__PURE__*/_jsx(Subtitle, { type: type, sizing: sizing, children: subtitle }), hasSubtitleContent && subtitleContent] }), localTime && /*#__PURE__*/_jsx(LocalTime, { direction: "topRight", textAlign: "center", width: "fit-content", text: localTime, children: /*#__PURE__*/_jsx(StatusDot, { icon: true, children: /*#__PURE__*/_jsx(Icon, { icon: ["fas", "clock"], color: "white", transform: "grow-2" }) }) })] }); return theme ? /*#__PURE__*/_jsx(ThemeProvider, { theme: theme, children: component }) : component; }; Avatar.propTypes = { /** Changes the sizing of the Avatar component */ sizing: PropTypes.oneOf(["small", "large", "default"]), /* If specifying custom sizing, provide a string with the size including units (e.g. "65px") */ customSize: PropTypes.string, /** Changes the shape of the Avatar component */ shape: PropTypes.oneOf(["circle", "square", "hexagon", "tag"]), // circle is the default so doesn't explicitly need to be specified /** Specifies the inverted type for dark backgrounds */ type: PropTypes.oneOf(["inverted", "default"]), /** Specifies a source path for an image */ image: PropTypes.string, /** Specifies the alt text for an image. This must be specified if image prop is used and title prop is not a string. */ imageAlt: (props, propName) => { if (props.image && props.title && typeof props.title !== "string" && (props[propName] == null || props[propName] === "")) { return new Error(`Missing prop \`${propName}\` not specified for Avatar component. When \`image\` is provided, \`${propName}\` is required.`); } if (props[propName] && typeof props[propName] !== "string") { return new Error(`Invalid propType \`${propName}\` supplied to Avatar component. Expected \`string\`, received \`${typeof props[propName]}\`.`); } return null; }, /** Specifies initials of person if available */ initials: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), /** Specifies custom content for avatar subtitle */ subtitleContent: PropTypes.oneOfType([PropTypes.element, PropTypes.node]), /** Specifies whether to use uppercase for initials */ uppercase: PropTypes.bool, /** Specifies title / name as just plain text, or an element like a hyperlink or react router link */ title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), /** Specifies the component to wrap the heading, defaults to H3 */ titleLevel: PropTypes.oneOf(["H1", "H2", "H3", "H4", "H5", "H6"]), /** Specifies subtitle / role */ subtitle: PropTypes.string, /** Specifies local time */ localTime: PropTypes.string, /** Specifies the colour theme */ theme: PropTypes.object, /** Specifies whether to render the avatar in headerAvatar mode */ headerAvatar: PropTypes.bool, /** Specifies the onClick handler for the action button in headerAvatar mode */ onClick: PropTypes.func, /** Specifies whether to render the avatar in loading state */ loading: PropTypes.bool }; Avatar.__docgenInfo = { "description": "", "methods": [], "displayName": "Avatar", "props": { "titleLevel": { "defaultValue": { "value": "\"H3\"", "computed": false }, "description": "Specifies the component to wrap the heading, defaults to H3", "type": { "name": "enum", "value": [{ "value": "\"H1\"", "computed": false }, { "value": "\"H2\"", "computed": false }, { "value": "\"H3\"", "computed": false }, { "value": "\"H4\"", "computed": false }, { "value": "\"H5\"", "computed": false }, { "value": "\"H6\"", "computed": false }] }, "required": false }, "shape": { "defaultValue": { "value": "\"circle\"", "computed": false }, "description": "Changes the shape of the Avatar component", "type": { "name": "enum", "value": [{ "value": "\"circle\"", "computed": false }, { "value": "\"square\"", "computed": false }, { "value": "\"hexagon\"", "computed": false }, { "value": "\"tag\"", "computed": false }] }, "required": false }, "uppercase": { "defaultValue": { "value": "true", "computed": false }, "description": "Specifies whether to use uppercase for initials", "type": { "name": "bool" }, "required": false }, "headerAvatar": { "defaultValue": { "value": "false", "computed": false }, "description": "Specifies whether to render the avatar in headerAvatar mode", "type": { "name": "bool" }, "required": false }, "loading": { "defaultValue": { "value": "false", "computed": false }, "description": "Specifies whether to render the avatar in loading state", "type": { "name": "bool" }, "required": false }, "sizing": { "description": "Changes the sizing of the Avatar component", "type": { "name": "enum", "value": [{ "value": "\"small\"", "computed": false }, { "value": "\"large\"", "computed": false }, { "value": "\"default\"", "computed": false }] }, "required": false }, "customSize": { "description": "", "type": { "name": "string" }, "required": false }, "type": { "description": "Specifies the inverted type for dark backgrounds", "type": { "name": "enum", "value": [{ "value": "\"inverted\"", "computed": false }, { "value": "\"default\"", "computed": false }] }, "required": false }, "image": { "description": "Specifies a source path for an image", "type": { "name": "string" }, "required": false }, "imageAlt": { "description": "Specifies the alt text for an image. This must be specified if image prop is used and title prop is not a string.", "type": { "name": "custom", "raw": "(props, propName) => {\n if (\n props.image &&\n props.title &&\n typeof props.title !== \"string\" &&\n (props[propName] == null || props[propName] === \"\")\n ) {\n return new Error(\n `Missing prop \\`${propName}\\` not specified for Avatar component. When \\`image\\` is provided, \\`${propName}\\` is required.`\n );\n }\n if (props[propName] && typeof props[propName] !== \"string\") {\n return new Error(\n `Invalid propType \\`${propName}\\` supplied to Avatar component. Expected \\`string\\`, received \\`${typeof props[\n propName\n ]}\\`.`\n );\n }\n return null;\n}" }, "required": false }, "initials": { "description": "Specifies initials of person if available", "type": { "name": "union", "value": [{ "name": "string" }, { "name": "element" }] }, "required": false }, "subtitleContent": { "description": "Specifies custom content for avatar subtitle", "type": { "name": "union", "value": [{ "name": "element" }, { "name": "node" }] }, "required": false }, "title": { "description": "Specifies title / name as just plain text, or an element like a hyperlink or react router link", "type": { "name": "union", "value": [{ "name": "string" }, { "name": "element" }] }, "required": false }, "subtitle": { "description": "Specifies subtitle / role", "type": { "name": "string" }, "required": false }, "localTime": { "description": "Specifies local time", "type": { "name": "string" }, "required": false }, "theme": { "description": "Specifies the colour theme", "type": { "name": "object" }, "required": false }, "onClick": { "description": "Specifies the onClick handler for the action button in headerAvatar mode", "type": { "name": "func" }, "required": false } } }; export default Avatar;