UNPKG

orcs-design-system

Version:
794 lines (790 loc) 23 kB
import React, { useMemo } from "react"; import styled, { ThemeProvider } 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); // 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, 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, 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 HeaderAvatarImage = styled("img").withConfig({ displayName: "Avatar__HeaderAvatarImage", componentId: "sc-1wtet2j-9" })(_ref4 => { let { $size, $shape } = _ref4; return { width: $size, height: $size, 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-10" })(_ref5 => { let { $size, $shape } = _ref5; return { width: $size, height: $size, 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 HeaderAvatarActionPopover = styled(Popover).withConfig({ displayName: "Avatar__HeaderAvatarActionPopover", componentId: "sc-1wtet2j-11" })(_ref6 => { let { $shape } = _ref6; return { position: "absolute", top: 0, right: 0, transform: $shape === "circle" ? "translate(-5%, 5%)" : "translate(20%, -20%)" }; }); const HeaderAvatarAction = styled("button").withConfig({ displayName: "Avatar__HeaderAvatarAction", componentId: "sc-1wtet2j-12" })(_ref7 => { let { ...props } = _ref7; return { width: "36px", height: "36px", borderRadius: "50%", background: "#e6e6e6", border: "solid 2px #fff", boxShadow: "0 2px 8px 0 rgba(0,0,0,0.10)", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", zIndex: 2, padding: 0, transition: "background 0.2s ease-in-out", "&:hover, &:focus": { background: themeGet("colors.primaryLighter")(props) } }; }); const Avatar = _ref8 => { let { sizing, image, imageAlt, initials, title, titleLevel = "H3", subtitle, subtitleContent, type, theme, shape = "circle", localTime, uppercase = true, customSize, headerAvatar = false, onClick, ...props } = _ref8; 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; } return /*#__PURE__*/_jsxs(HeaderAvatarWrapper, { ...props, $size: size, $shape: shape, children: [/*#__PURE__*/_jsx(HeaderAvatarBorder, { $size: size, $shape: shape, $sizing: sizing, children: 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"] }) }) }), onClick && sizing === "large" && /*#__PURE__*/_jsx(HeaderAvatarActionPopover, { direction: "right", width: "fit-content", $shape: shape, text: shape === "circle" ? "Upload profile picture" : "Change image", children: /*#__PURE__*/_jsx(HeaderAvatarAction, { onClick: onClick, "aria-label": "Edit avatar", children: shape === "circle" ? /*#__PURE__*/_jsx(Icon, { icon: ["fas", "camera"], size: "lg", color: "greyDarker" }) : /*#__PURE__*/_jsx(Icon, { icon: ["fas", "edit"], size: "lg", color: "greyDarker", transform: "right-1" }) }) })] }); } 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 }; 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 }, "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;