Add 80+ additional icons from FontAwesome (#6803)
* Add 80+ additional icons from FontAwesome * fix: color switch transition, add 6 more icons to fill out grid * Add strict validation for collection icon * fix: Avoid import from app in server
This commit is contained in:
@@ -5,13 +5,13 @@ import { Trans, useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { randomElement } from "@shared/random";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import { IconLibrary } from "@shared/utils/IconLibrary";
|
||||
import { colorPalette } from "@shared/utils/collections";
|
||||
import { CollectionValidation } from "@shared/validations";
|
||||
import Collection from "~/models/Collection";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import IconPicker from "~/components/IconPicker";
|
||||
import { IconLibrary } from "~/components/Icons/IconLibrary";
|
||||
import Input from "~/components/Input";
|
||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
||||
import Switch from "~/components/Switch";
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import Squircle from "@shared/components/Squircle";
|
||||
import { s, ellipsis } from "@shared/styles";
|
||||
import Document from "~/models/Document";
|
||||
import Pin from "~/models/Pin";
|
||||
@@ -17,7 +18,6 @@ import useStores from "~/hooks/useStores";
|
||||
import { hover } from "~/styles";
|
||||
import CollectionIcon from "./Icons/CollectionIcon";
|
||||
import EmojiIcon from "./Icons/EmojiIcon";
|
||||
import Squircle from "./Squircle";
|
||||
import Text from "./Text";
|
||||
import Tooltip from "./Tooltip";
|
||||
|
||||
|
||||
@@ -1,38 +1,3 @@
|
||||
import { CSSProperties } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type JustifyValues = CSSProperties["justifyContent"];
|
||||
|
||||
type AlignValues = CSSProperties["alignItems"];
|
||||
|
||||
const Flex = styled.div<{
|
||||
auto?: boolean;
|
||||
column?: boolean;
|
||||
align?: AlignValues;
|
||||
justify?: JustifyValues;
|
||||
wrap?: boolean;
|
||||
shrink?: boolean;
|
||||
reverse?: boolean;
|
||||
gap?: number;
|
||||
}>`
|
||||
display: flex;
|
||||
flex: ${({ auto }) => (auto ? "1 1 auto" : "initial")};
|
||||
flex-direction: ${({ column, reverse }) =>
|
||||
reverse
|
||||
? column
|
||||
? "column-reverse"
|
||||
: "row-reverse"
|
||||
: column
|
||||
? "column"
|
||||
: "row"};
|
||||
align-items: ${({ align }) => align};
|
||||
justify-content: ${({ justify }) => justify};
|
||||
flex-wrap: ${({ wrap }) => (wrap ? "wrap" : "initial")};
|
||||
flex-shrink: ${({ shrink }) =>
|
||||
shrink === true ? 1 : shrink === false ? 0 : "initial"};
|
||||
gap: ${({ gap }) => (gap ? `${gap}px` : "initial")};
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
`;
|
||||
import Flex from "@shared/components/Flex";
|
||||
|
||||
export default Flex;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { PopoverDisclosure, usePopoverState } from "reakit";
|
||||
import { MenuItem } from "reakit/Menu";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import { IconLibrary } from "@shared/utils/IconLibrary";
|
||||
import { colorPalette } from "@shared/utils/collections";
|
||||
import Flex from "~/components/Flex";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
@@ -10,7 +11,7 @@ import Text from "~/components/Text";
|
||||
import useOnClickOutside from "~/hooks/useOnClickOutside";
|
||||
import lazyWithRetry from "~/utils/lazyWithRetry";
|
||||
import DelayedMount from "./DelayedMount";
|
||||
import { IconLibrary } from "./Icons/IconLibrary";
|
||||
import InputSearch from "./InputSearch";
|
||||
import Popover from "./Popover";
|
||||
|
||||
const icons = IconLibrary.mapping;
|
||||
@@ -38,11 +39,12 @@ function IconPicker({
|
||||
onChange,
|
||||
className,
|
||||
}: Props) {
|
||||
const [query, setQuery] = React.useState("");
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const popover = usePopoverState({
|
||||
gutter: 0,
|
||||
placement: "bottom",
|
||||
placement: "right",
|
||||
modal: true,
|
||||
});
|
||||
|
||||
@@ -51,9 +53,15 @@ function IconPicker({
|
||||
onOpen?.();
|
||||
} else {
|
||||
onClose?.();
|
||||
setQuery("");
|
||||
}
|
||||
}, [onOpen, onClose, popover.visible]);
|
||||
|
||||
const filteredIcons = IconLibrary.findIcons(query);
|
||||
const handleFilter = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(event.target.value.toLowerCase());
|
||||
};
|
||||
|
||||
const styles = React.useMemo(
|
||||
() => ({
|
||||
default: {
|
||||
@@ -92,6 +100,9 @@ function IconPicker({
|
||||
{ capture: true }
|
||||
);
|
||||
|
||||
const iconNames = Object.keys(icons);
|
||||
const delayPerIcon = 250 / iconNames.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PopoverDisclosure {...popover}>
|
||||
@@ -112,69 +123,77 @@ function IconPicker({
|
||||
</PopoverDisclosure>
|
||||
<Popover
|
||||
{...popover}
|
||||
width={388}
|
||||
aria-label={t("Choose icon")}
|
||||
width={552}
|
||||
aria-label={t("Choose an icon")}
|
||||
hideOnClickOutside={false}
|
||||
>
|
||||
<Icons>
|
||||
{Object.keys(icons).map((name, index) => (
|
||||
<MenuItem key={name} onClick={() => onChange(color, name)}>
|
||||
{(props) => (
|
||||
<IconButton
|
||||
style={
|
||||
{
|
||||
"--delay": `${index * 8}ms`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<Icon
|
||||
as={IconLibrary.getComponent(name)}
|
||||
color={color}
|
||||
size={30}
|
||||
<Flex column gap={12}>
|
||||
<Text size="large" weight="xbold">
|
||||
{t("Choose an icon")}
|
||||
</Text>
|
||||
<InputSearch
|
||||
value={query}
|
||||
placeholder={`${t("Filter")}…`}
|
||||
onChange={handleFilter}
|
||||
autoFocus
|
||||
/>
|
||||
<div>
|
||||
{iconNames.map((name, index) => (
|
||||
<MenuItem key={name} onClick={() => onChange(color, name)}>
|
||||
{(props) => (
|
||||
<IconButton
|
||||
style={
|
||||
{
|
||||
opacity: query
|
||||
? filteredIcons.includes(name)
|
||||
? 1
|
||||
: 0.3
|
||||
: undefined,
|
||||
"--delay": `${Math.round(index * delayPerIcon)}ms`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{initial}
|
||||
</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Icons>
|
||||
<Colors>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<DelayedMount>
|
||||
<Text>{t("Loading")}…</Text>
|
||||
</DelayedMount>
|
||||
}
|
||||
>
|
||||
<ColorPicker
|
||||
color={color}
|
||||
onChange={(color) => onChange(color.hex, icon)}
|
||||
colors={colorPalette}
|
||||
triangle="hide"
|
||||
styles={styles}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</Colors>
|
||||
<Icon
|
||||
as={IconLibrary.getComponent(name)}
|
||||
color={color}
|
||||
size={30}
|
||||
>
|
||||
{initial}
|
||||
</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</div>
|
||||
<Flex>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<DelayedMount>
|
||||
<Text>{t("Loading")}…</Text>
|
||||
</DelayedMount>
|
||||
}
|
||||
>
|
||||
<ColorPicker
|
||||
color={color}
|
||||
onChange={(color) => onChange(color.hex, icon)}
|
||||
colors={colorPalette}
|
||||
triangle="hide"
|
||||
styles={styles}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Icon = styled.svg`
|
||||
transition: fill 150ms ease-in-out;
|
||||
transition: color 150ms ease-in-out, fill 150ms ease-in-out;
|
||||
transition-delay: var(--delay);
|
||||
`;
|
||||
|
||||
const Colors = styled(Flex)`
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
const Icons = styled.div`
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
const IconButton = styled(NudeButton)`
|
||||
vertical-align: top;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -2,10 +2,10 @@ import { observer } from "mobx-react";
|
||||
import { CollectionIcon } from "outline-icons";
|
||||
import { getLuminance } from "polished";
|
||||
import * as React from "react";
|
||||
import { IconLibrary } from "@shared/utils/IconLibrary";
|
||||
import Collection from "~/models/Collection";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import Logger from "~/utils/Logger";
|
||||
import { IconLibrary } from "./IconLibrary";
|
||||
|
||||
type Props = {
|
||||
/** The collection to show an icon for */
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
import intersection from "lodash/intersection";
|
||||
import {
|
||||
BookmarkedIcon,
|
||||
BicycleIcon,
|
||||
AcademicCapIcon,
|
||||
BeakerIcon,
|
||||
BuildingBlocksIcon,
|
||||
BrowserIcon,
|
||||
CollectionIcon,
|
||||
CoinsIcon,
|
||||
CameraIcon,
|
||||
CarrotIcon,
|
||||
FlameIcon,
|
||||
HashtagIcon,
|
||||
GraphIcon,
|
||||
InternetIcon,
|
||||
LibraryIcon,
|
||||
PlaneIcon,
|
||||
RamenIcon,
|
||||
CloudIcon,
|
||||
CodeIcon,
|
||||
EditIcon,
|
||||
EmailIcon,
|
||||
EyeIcon,
|
||||
GlobeIcon,
|
||||
InfoIcon,
|
||||
IceCreamIcon,
|
||||
ImageIcon,
|
||||
LeafIcon,
|
||||
LightBulbIcon,
|
||||
MathIcon,
|
||||
MoonIcon,
|
||||
NotepadIcon,
|
||||
TeamIcon,
|
||||
PadlockIcon,
|
||||
PaletteIcon,
|
||||
PromoteIcon,
|
||||
QuestionMarkIcon,
|
||||
SportIcon,
|
||||
SunIcon,
|
||||
ShapesIcon,
|
||||
TargetIcon,
|
||||
TerminalIcon,
|
||||
ToolsIcon,
|
||||
VehicleIcon,
|
||||
WarningIcon,
|
||||
DatabaseIcon,
|
||||
SmileyIcon,
|
||||
LightningIcon,
|
||||
ClockIcon,
|
||||
DoneIcon,
|
||||
FeedbackIcon,
|
||||
ServerRackIcon,
|
||||
ThumbsUpIcon,
|
||||
TruckIcon,
|
||||
} from "outline-icons";
|
||||
import LetterIcon from "./LetterIcon";
|
||||
|
||||
export class IconLibrary {
|
||||
/**
|
||||
* Get the component for a given icon name
|
||||
*
|
||||
* @param icon The name of the icon
|
||||
* @returns The component for the icon
|
||||
*/
|
||||
public static getComponent(icon: string) {
|
||||
return this.mapping[icon].component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an icon by keyword. This is useful for searching for an icon based on a user's input.
|
||||
*
|
||||
* @param keyword The keyword to search for
|
||||
* @returns The name of the icon that matches the keyword, or undefined if no match is found
|
||||
*/
|
||||
public static findIconByKeyword(keyword: string) {
|
||||
const keys = Object.keys(this.mapping);
|
||||
|
||||
for (const key of keys) {
|
||||
const icon = this.mapping[key];
|
||||
const keywords = icon.keywords.split(" ");
|
||||
const namewords = keyword.toLocaleLowerCase().split(" ");
|
||||
const matches = intersection(namewords, keywords);
|
||||
|
||||
if (matches.length > 0) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of all icons available to end users in the app. This does not include icons that are used
|
||||
* internally only, which can be imported from `outline-icons` directly.
|
||||
*/
|
||||
public static mapping = {
|
||||
academicCap: {
|
||||
component: AcademicCapIcon,
|
||||
keywords: "learn teach lesson guide tutorial onboarding training",
|
||||
},
|
||||
bicycle: {
|
||||
component: BicycleIcon,
|
||||
keywords: "bicycle bike cycle",
|
||||
},
|
||||
beaker: {
|
||||
component: BeakerIcon,
|
||||
keywords: "lab research experiment test",
|
||||
},
|
||||
buildingBlocks: {
|
||||
component: BuildingBlocksIcon,
|
||||
keywords: "app blocks product prototype",
|
||||
},
|
||||
bookmark: {
|
||||
component: BookmarkedIcon,
|
||||
keywords: "bookmark",
|
||||
},
|
||||
browser: {
|
||||
component: BrowserIcon,
|
||||
keywords: "browser web app",
|
||||
},
|
||||
collection: {
|
||||
component: CollectionIcon,
|
||||
keywords: "collection",
|
||||
},
|
||||
coins: {
|
||||
component: CoinsIcon,
|
||||
keywords: "coins money finance sales income revenue cash",
|
||||
},
|
||||
camera: {
|
||||
component: CameraIcon,
|
||||
keywords: "photo picture",
|
||||
},
|
||||
carrot: {
|
||||
component: CarrotIcon,
|
||||
keywords: "food vegetable produce",
|
||||
},
|
||||
clock: {
|
||||
component: ClockIcon,
|
||||
keywords: "time",
|
||||
},
|
||||
cloud: {
|
||||
component: CloudIcon,
|
||||
keywords: "cloud service aws infrastructure",
|
||||
},
|
||||
code: {
|
||||
component: CodeIcon,
|
||||
keywords: "developer api code development engineering programming",
|
||||
},
|
||||
database: {
|
||||
component: DatabaseIcon,
|
||||
keywords: "server ops database",
|
||||
},
|
||||
done: {
|
||||
component: DoneIcon,
|
||||
keywords: "checkmark success complete finished",
|
||||
},
|
||||
email: {
|
||||
component: EmailIcon,
|
||||
keywords: "email at",
|
||||
},
|
||||
eye: {
|
||||
component: EyeIcon,
|
||||
keywords: "eye view",
|
||||
},
|
||||
feedback: {
|
||||
component: FeedbackIcon,
|
||||
keywords: "faq help support",
|
||||
},
|
||||
flame: {
|
||||
component: FlameIcon,
|
||||
keywords: "fire flame hot",
|
||||
},
|
||||
graph: {
|
||||
component: GraphIcon,
|
||||
keywords: "chart analytics data",
|
||||
},
|
||||
globe: {
|
||||
component: GlobeIcon,
|
||||
keywords: "world translate",
|
||||
},
|
||||
hashtag: {
|
||||
component: HashtagIcon,
|
||||
keywords: "social media tag",
|
||||
},
|
||||
info: {
|
||||
component: InfoIcon,
|
||||
keywords: "info information",
|
||||
},
|
||||
icecream: {
|
||||
component: IceCreamIcon,
|
||||
keywords: "food dessert cone scoop",
|
||||
},
|
||||
image: {
|
||||
component: ImageIcon,
|
||||
keywords: "image photo picture",
|
||||
},
|
||||
internet: {
|
||||
component: InternetIcon,
|
||||
keywords: "network global globe world",
|
||||
},
|
||||
leaf: {
|
||||
component: LeafIcon,
|
||||
keywords: "leaf plant outdoors nature ecosystem climate",
|
||||
},
|
||||
library: {
|
||||
component: LibraryIcon,
|
||||
keywords: "library collection archive",
|
||||
},
|
||||
lightbulb: {
|
||||
component: LightBulbIcon,
|
||||
keywords: "lightbulb idea",
|
||||
},
|
||||
lightning: {
|
||||
component: LightningIcon,
|
||||
keywords: "lightning fast zap",
|
||||
},
|
||||
letter: {
|
||||
component: LetterIcon,
|
||||
keywords: "letter",
|
||||
},
|
||||
math: {
|
||||
component: MathIcon,
|
||||
keywords: "math formula",
|
||||
},
|
||||
moon: {
|
||||
component: MoonIcon,
|
||||
keywords: "night moon dark",
|
||||
},
|
||||
notepad: {
|
||||
component: NotepadIcon,
|
||||
keywords: "journal notepad write notes",
|
||||
},
|
||||
padlock: {
|
||||
component: PadlockIcon,
|
||||
keywords: "padlock private security authentication authorization auth",
|
||||
},
|
||||
palette: {
|
||||
component: PaletteIcon,
|
||||
keywords: "design palette art brand",
|
||||
},
|
||||
pencil: {
|
||||
component: EditIcon,
|
||||
keywords: "copy writing post blog",
|
||||
},
|
||||
plane: {
|
||||
component: PlaneIcon,
|
||||
keywords: "airplane travel flight trip vacation",
|
||||
},
|
||||
promote: {
|
||||
component: PromoteIcon,
|
||||
keywords: "marketing promotion",
|
||||
},
|
||||
ramen: {
|
||||
component: RamenIcon,
|
||||
keywords: "soup food noodle bowl meal",
|
||||
},
|
||||
question: {
|
||||
component: QuestionMarkIcon,
|
||||
keywords: "question help support faq",
|
||||
},
|
||||
server: {
|
||||
component: ServerRackIcon,
|
||||
keywords: "ops infra server",
|
||||
},
|
||||
sun: {
|
||||
component: SunIcon,
|
||||
keywords: "day sun weather",
|
||||
},
|
||||
shapes: {
|
||||
component: ShapesIcon,
|
||||
keywords: "blocks toy",
|
||||
},
|
||||
sport: {
|
||||
component: SportIcon,
|
||||
keywords: "sport outdoor racket game",
|
||||
},
|
||||
smiley: {
|
||||
component: SmileyIcon,
|
||||
keywords: "emoji smiley happy",
|
||||
},
|
||||
target: {
|
||||
component: TargetIcon,
|
||||
keywords: "target goal sales",
|
||||
},
|
||||
team: {
|
||||
component: TeamIcon,
|
||||
keywords: "team building organization office",
|
||||
},
|
||||
terminal: {
|
||||
component: TerminalIcon,
|
||||
keywords: "terminal code",
|
||||
},
|
||||
thumbsup: {
|
||||
component: ThumbsUpIcon,
|
||||
keywords: "like social favorite upvote",
|
||||
},
|
||||
truck: {
|
||||
component: TruckIcon,
|
||||
keywords: "truck transport vehicle",
|
||||
},
|
||||
tools: {
|
||||
component: ToolsIcon,
|
||||
keywords: "tool settings",
|
||||
},
|
||||
vehicle: {
|
||||
component: VehicleIcon,
|
||||
keywords: "truck car travel transport",
|
||||
},
|
||||
warning: {
|
||||
component: WarningIcon,
|
||||
keywords: "warning alert error",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import Squircle from "../Squircle";
|
||||
|
||||
type Props = {
|
||||
/** The width and height of the icon, including standard padding. */
|
||||
size?: number;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* A squircle shaped icon with a letter inside, used for collections.
|
||||
*/
|
||||
const LetterIcon = ({ children, size = 24, ...rest }: Props) => (
|
||||
<LetterIconWrapper $size={size}>
|
||||
<Squircle size={Math.round(size * 0.66)} {...rest}>
|
||||
{children}
|
||||
</Squircle>
|
||||
</LetterIconWrapper>
|
||||
);
|
||||
|
||||
const LetterIconWrapper = styled.div<{ $size: number }>`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: ${({ $size }) => $size}px;
|
||||
height: ${({ $size }) => $size}px;
|
||||
|
||||
font-weight: 700;
|
||||
font-size: ${({ $size }) => $size / 2}px;
|
||||
color: ${s("background")};
|
||||
`;
|
||||
|
||||
export default LetterIcon;
|
||||
@@ -3,6 +3,7 @@ import { MoreIcon, QuestionMarkIcon, UserIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "styled-components";
|
||||
import Squircle from "@shared/components/Squircle";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import type Collection from "~/models/Collection";
|
||||
import type Document from "~/models/Document";
|
||||
@@ -14,7 +15,6 @@ import useStores from "~/hooks/useStores";
|
||||
import Avatar from "../Avatar";
|
||||
import { AvatarSize } from "../Avatar/Avatar";
|
||||
import CollectionIcon from "../Icons/CollectionIcon";
|
||||
import Squircle from "../Squircle";
|
||||
import Tooltip from "../Tooltip";
|
||||
import { StyledListItem } from "./MemberListItem";
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Trans, useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import Squircle from "@shared/components/Squircle";
|
||||
import { s } from "@shared/styles";
|
||||
import { UrlHelper } from "@shared/utils/UrlHelper";
|
||||
import Document from "~/models/Document";
|
||||
@@ -21,7 +22,6 @@ import { AvatarSize } from "../Avatar/Avatar";
|
||||
import CopyToClipboard from "../CopyToClipboard";
|
||||
import NudeButton from "../NudeButton";
|
||||
import { ResizingHeightContainer } from "../ResizingHeightContainer";
|
||||
import Squircle from "../Squircle";
|
||||
import Text from "../Text";
|
||||
import Tooltip from "../Tooltip";
|
||||
import { StyledListItem } from "./MemberListItem";
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Flex from "./Flex";
|
||||
|
||||
type Props = {
|
||||
/** The width and height of the squircle */
|
||||
size?: number;
|
||||
/** The color of the squircle */
|
||||
color?: string;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Squircle: React.FC<Props> = ({
|
||||
color,
|
||||
size = 28,
|
||||
children,
|
||||
className,
|
||||
}: Props) => (
|
||||
<Wrapper size={size} align="center" justify="center" className={className}>
|
||||
<svg width={size} height={size} fill={color} viewBox="0 0 28 28">
|
||||
<path d="M0 11.1776C0 1.97285 1.97285 0 11.1776 0H16.8224C26.0272 0 28 1.97285 28 11.1776V16.8224C28 26.0272 26.0272 28 16.8224 28H11.1776C1.97285 28 0 26.0272 0 16.8224V11.1776Z" />
|
||||
</svg>
|
||||
<Content>{children}</Content>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
const Wrapper = styled(Flex)<{ size: number }>`
|
||||
position: relative;
|
||||
width: ${(props) => props.size}px;
|
||||
height: ${(props) => props.size}px;
|
||||
|
||||
svg {
|
||||
transition: fill 150ms ease-in-out;
|
||||
transition-delay: var(--delay);
|
||||
}
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
transform: translate(-50%, -50%);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
`;
|
||||
|
||||
export default Squircle;
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
/** Whether the text should be selectable (defaults to false) */
|
||||
selectable?: boolean;
|
||||
/** The font weight of the text */
|
||||
weight?: "bold" | "normal";
|
||||
weight?: "xbold" | "bold" | "normal";
|
||||
/** Whether the text should be truncated with an ellipsis */
|
||||
ellipsis?: boolean;
|
||||
};
|
||||
@@ -47,7 +47,9 @@ const Text = styled.span<Props>`
|
||||
${(props) =>
|
||||
props.weight &&
|
||||
css`
|
||||
font-weight: ${props.weight === "bold"
|
||||
font-weight: ${props.weight === "xbold"
|
||||
? 600
|
||||
: props.weight === "bold"
|
||||
? 500
|
||||
: props.weight === "normal"
|
||||
? 400
|
||||
|
||||
Reference in New Issue
Block a user