orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
794 lines (790 loc) • 23 kB
JavaScript
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;