mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
Merge pull request #123 from muerwre/feature/122_new_profile_tooltip
#122 new profile tooltip
This commit is contained in:
commit
af5f060951
17 changed files with 281 additions and 201 deletions
|
@ -3,60 +3,26 @@ import React, { FC, useCallback, useState } from 'react';
|
|||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
|
||||
import { Avatar } from '~/components/common/Avatar';
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
import { MenuButton } from '~/components/menu';
|
||||
import { ProfileQuickInfo } from '~/containers/profile/ProfileQuickInfo';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { path } from '~/utils/ramda';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface Props {
|
||||
user: IUser;
|
||||
withDetails: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const modifiers = [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 10],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const CommentAvatar: FC<Props> = ({ user, withDetails, className }) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const randomPhrase = useRandomPhrase('USER_DESCRIPTION');
|
||||
|
||||
const onMouseOver = useCallback(() => setHovered(true), [setHovered]);
|
||||
const onMouseOut = useCallback(() => setHovered(false), [setHovered]);
|
||||
|
||||
const CommentAvatar: FC<Props> = ({ user, className }) => {
|
||||
return (
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<Avatar
|
||||
url={path(['photo', 'url'], user)}
|
||||
username={user.username}
|
||||
className={className}
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseOut={onMouseOut}
|
||||
ref={ref}
|
||||
/>
|
||||
)}
|
||||
</Reference>
|
||||
|
||||
{hovered && withDetails && (
|
||||
<Popper placement="right" modifiers={modifiers} strategy="fixed">
|
||||
{({ style, ref }) => (
|
||||
<div style={style} ref={ref} className={styles.popper}>
|
||||
<h4 className={styles.username}>{user.fullname || user.username}</h4>
|
||||
<div className={styles.description}>{user.description || randomPhrase}</div>
|
||||
</div>
|
||||
)}
|
||||
</Popper>
|
||||
)}
|
||||
</Manager>
|
||||
<MenuButton
|
||||
position="auto"
|
||||
icon={
|
||||
<Avatar url={path(['photo', 'url'], user)} username={user.username} className={className} />
|
||||
}
|
||||
>
|
||||
<ProfileQuickInfo user={user} />
|
||||
</MenuButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -19,14 +19,11 @@ interface Props extends DivProps {
|
|||
|
||||
const Avatar = forwardRef<HTMLDivElement, Props>(
|
||||
({ url, username, size, className, preset = ImagePresets.avatar, ...rest }, ref) => {
|
||||
const onOpenProfile = useCallback(() => openUserProfile(username), [username]);
|
||||
|
||||
return (
|
||||
<Square
|
||||
{...rest}
|
||||
image={getURLFromString(url, preset)}
|
||||
className={classNames(styles.avatar, className)}
|
||||
onClick={onOpenProfile}
|
||||
size={size}
|
||||
ref={ref}
|
||||
/>
|
||||
|
|
|
@ -36,7 +36,7 @@ const CommentWrapper: FC<IProps> = ({
|
|||
{...props}
|
||||
>
|
||||
<div className={styles.thumb}>
|
||||
<CommentAvatar user={user} className={styles.thumb_image} withDetails={!isForm} />
|
||||
<CommentAvatar user={user} className={styles.thumb_image} />
|
||||
<div className={styles.thumb_user}>~{path(['username'], user)}</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { FC, useCallback } from 'react';
|
|||
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { MenuButton, MenuItemWithIcon } from '~/components/menu';
|
||||
import { ImagePresets } from '~/constants/urls';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { getURL } from '~/utils/dom';
|
||||
|
@ -28,19 +29,23 @@ const UserButton: FC<IProps> = ({ user: { username, photo }, authOpenProfile, on
|
|||
<Group horizontal className={styles.user_button}>
|
||||
<div className={styles.username}>{username}</div>
|
||||
|
||||
<div
|
||||
className={styles.user_avatar}
|
||||
style={{ backgroundImage: `url('${getURL(photo, ImagePresets.avatar)}')` }}
|
||||
<MenuButton
|
||||
position="bottom"
|
||||
translucent={false}
|
||||
icon={
|
||||
<div
|
||||
className={styles.user_avatar}
|
||||
style={{ backgroundImage: `url('${getURL(photo, ImagePresets.avatar)}')` }}
|
||||
>
|
||||
{(!photo || !photo.id) && <Icon icon="profile" />}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{(!photo || !photo.id) && <Icon icon="profile" />}
|
||||
</div>
|
||||
<MenuItemWithIcon onClick={onProfileOpen}>Профиль</MenuItemWithIcon>
|
||||
<MenuItemWithIcon onClick={onSettingsOpen}>Настройки</MenuItemWithIcon>
|
||||
<MenuItemWithIcon onClick={onLogout}>Выдох</MenuItemWithIcon>
|
||||
</MenuButton>
|
||||
</Group>
|
||||
|
||||
<div className={styles.menu}>
|
||||
<div onClick={onProfileOpen}>Профиль</div>
|
||||
<div onClick={onSettingsOpen}>Настройки</div>
|
||||
<div onClick={onLogout}>Выдох</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,63 +9,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: absolute;
|
||||
right: -$gap;
|
||||
top: 100%;
|
||||
padding: $gap;
|
||||
border-radius: $radius;
|
||||
display: none;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
padding: $gap;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 0 16px 16px;
|
||||
border-color: transparent transparent $content_bg transparent;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -4px;
|
||||
transform: translate(-20px, 0);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
& > div {
|
||||
background: $content_bg;
|
||||
padding: $gap $gap * 2;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
transition: opacity 0.25s;
|
||||
width: 100%;
|
||||
padding-right: 40px;
|
||||
transition: background-color 0.25s;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $radius;
|
||||
border-top-right-radius: $radius;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom-left-radius: $radius;
|
||||
border-bottom-right-radius: $radius;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $secondary;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > div {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.user_button {
|
||||
align-items: center;
|
||||
border-radius: $radius;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React, { FC, ReactNode } from 'react';
|
||||
import React, { FC, ReactNode, useState } from 'react';
|
||||
|
||||
import { Placement } from '@popperjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import { usePopper } from 'react-popper';
|
||||
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { useFocusEvent } from '~/hooks/dom/useFocusEvent';
|
||||
|
@ -9,55 +10,73 @@ import { useFocusEvent } from '~/hooks/dom/useFocusEvent';
|
|||
import styles from './styles.module.scss';
|
||||
|
||||
interface MenuButtonProps {
|
||||
position?: Placement;
|
||||
icon?: ReactNode;
|
||||
className?: string;
|
||||
activate?: 'hover' | 'focus';
|
||||
fixed?: boolean;
|
||||
translucent?: boolean;
|
||||
}
|
||||
|
||||
const modifiers = [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [5, 10],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const MenuButton: FC<MenuButtonProps> = ({
|
||||
position = 'auto',
|
||||
children,
|
||||
className,
|
||||
icon = <Icon icon="dots-vertical" size={24} />,
|
||||
translucent = true,
|
||||
activate = 'focus',
|
||||
fixed,
|
||||
}) => {
|
||||
const { focused, onFocus, onBlur } = useFocusEvent(false, 150);
|
||||
const focus = useFocusEvent(false, 150);
|
||||
const hover = useFocusEvent(false, 150);
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const popper = usePopper(referenceElement, popperElement, {
|
||||
placement: position,
|
||||
strategy: fixed ? 'fixed' : 'absolute',
|
||||
modifiers: [
|
||||
{ name: 'arrow', options: { element: arrowElement } },
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [-10, 10],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const visible = activate === 'focus' ? focus.focused : hover.focused;
|
||||
|
||||
return (
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<button
|
||||
className={classNames(styles.menu, className)}
|
||||
ref={ref}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
)}
|
||||
</Reference>
|
||||
<>
|
||||
<button
|
||||
className={classNames(styles.menu, className)}
|
||||
ref={setReferenceElement}
|
||||
onFocus={focus.onFocus}
|
||||
onBlur={focus.onBlur}
|
||||
onMouseOver={hover.onFocus}
|
||||
onMouseOut={hover.onBlur}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
|
||||
{focused && (
|
||||
<Popper placement="bottom-end" modifiers={modifiers}>
|
||||
{({ style, ref, placement }) => (
|
||||
<div
|
||||
style={style}
|
||||
ref={ref}
|
||||
className={classNames(styles.popper, { [styles.top]: placement === 'top-end' })}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Popper>
|
||||
)}
|
||||
</Manager>
|
||||
<div
|
||||
style={popper.styles.popper}
|
||||
ref={setPopperElement}
|
||||
{...popper.attributes.popper}
|
||||
className={classNames(styles.popper, {
|
||||
[styles.fixed]: fixed,
|
||||
[styles.translucent]: translucent,
|
||||
[styles.visible]: visible,
|
||||
})}
|
||||
>
|
||||
<div style={popper.styles.arrow} ref={setArrowElement} className={styles.arrow} />
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,36 +5,59 @@
|
|||
|
||||
@import "src/styles/variables.scss";
|
||||
|
||||
@keyframes appear {
|
||||
0% { opacity: 0 }
|
||||
100% { opacity: 1 }
|
||||
}
|
||||
|
||||
.popper {
|
||||
@include outer_shadow;
|
||||
|
||||
background-color: $menu_bg;
|
||||
box-sizing: border-box;
|
||||
z-index: 12;
|
||||
border-radius: $radius;
|
||||
animation: appear forwards 250ms;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
touch-action: none;
|
||||
background-color: transparentize($content_bg, 0.05);
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 10px 10px 10px;
|
||||
border-color: transparent transparent lighten($menu_bg, 6%) transparent;
|
||||
position: absolute;
|
||||
top: -11px;
|
||||
right: 10px;
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
pointer-events: all;
|
||||
touch-action: initial;
|
||||
}
|
||||
|
||||
&.top::after {
|
||||
border-width: 10px 10px 0 10px;
|
||||
border-color: darken($menu_bg, 8%) transparent transparent transparent;
|
||||
top: auto;
|
||||
bottom: -11px;
|
||||
&.fixed {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
&.translucent {
|
||||
@include blur($content_bg, 15px, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
|
||||
[data-popper-placement*='bottom'] & {
|
||||
border-width: 0 10px 10px 10px;
|
||||
border-color: transparent transparent lighten($menu_bg, 6%) transparent;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
[data-popper-placement*='top'] & {
|
||||
border-width: 10px 10px 0 10px;
|
||||
border-color: lighten($menu_bg, 6%) transparent transparent transparent;
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
[data-popper-placement*='right'] & {
|
||||
border-width: 10px 10px 10px 0;
|
||||
border-color: transparent lighten($menu_bg, 6%) transparent transparent;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
[data-popper-placement*='left'] & {
|
||||
border-width: 10px 0 10px 10px;
|
||||
border-color: transparent transparent transparent lighten($menu_bg, 6%);
|
||||
right: -10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,17 @@ import styles from './styles.module.scss';
|
|||
|
||||
interface MenuItemWithIconProps {
|
||||
children: string;
|
||||
icon: string;
|
||||
icon?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const MenuItemWithIcon: FC<MenuItemWithIconProps> = ({ children, icon, onClick }) => (
|
||||
<button className={styles.item} onClick={onClick}>
|
||||
<div className={styles.icon}>
|
||||
<Icon icon={icon} size={20} />
|
||||
</div>
|
||||
{icon && (
|
||||
<div className={styles.icon}>
|
||||
<Icon icon={icon} size={20} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.text}>{children}</div>
|
||||
</button>
|
||||
|
|
|
@ -18,12 +18,11 @@
|
|||
|
||||
.icon {
|
||||
flex: 0 0 20px;
|
||||
margin-right: $gap;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
padding-right: $gap;
|
||||
padding: 0 $gap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
@keyframes appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(0, -$header_height);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
}
|
||||
|
|
45
src/containers/profile/ProfileQuickInfo/index.tsx
Normal file
45
src/containers/profile/ProfileQuickInfo/index.tsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Avatar } from '~/components/common/Avatar';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
import { useUserActiveStatus } from '~/hooks/auth/useUserActiveStatus';
|
||||
import { useUserDescription } from '~/hooks/auth/useUserDescription';
|
||||
import { useColorGradientFromString } from '~/hooks/color/useColorGradientFromString';
|
||||
import { IUser } from '~/types/auth';
|
||||
import { generateGradientFromColor } from '~/utils/color';
|
||||
import { path } from '~/utils/ramda';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface ProfileQuickInfoProps {
|
||||
user: IUser;
|
||||
}
|
||||
|
||||
const ProfileQuickInfo: FC<ProfileQuickInfoProps> = ({ user }) => {
|
||||
const isActive = useUserActiveStatus(user.last_seen);
|
||||
|
||||
return (
|
||||
<Group className={styles.wrapper}>
|
||||
<Group className={styles.top} horizontal>
|
||||
<div>
|
||||
<Avatar url={path(['photo', 'url'], user)} username={user.username} />
|
||||
</div>
|
||||
|
||||
<Filler className={styles.names}>
|
||||
<h5 className={styles.fullname}>{user.fullname || user.username}</h5>
|
||||
<div className={styles.username}>~{user.username}</div>
|
||||
|
||||
<div className={classNames(styles.status, { [styles.active]: isActive })}>
|
||||
{isActive ? 'в сознании' : 'деактивирован'}
|
||||
</div>
|
||||
</Filler>
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export { ProfileQuickInfo };
|
47
src/containers/profile/ProfileQuickInfo/styles.module.scss
Normal file
47
src/containers/profile/ProfileQuickInfo/styles.module.scss
Normal file
|
@ -0,0 +1,47 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrapper {
|
||||
@include outer_shadow;
|
||||
|
||||
padding: $gap $gap * 2 $gap $gap;
|
||||
border-radius: $radius;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
div.info {
|
||||
font: $font_12_regular;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
div.top.top {
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.names {
|
||||
}
|
||||
|
||||
.username {
|
||||
font: $font_12_regular;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.fullname {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.status {
|
||||
font: $font_12_regular;
|
||||
margin-top: $gap;
|
||||
|
||||
&.active {
|
||||
color: $olive;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '●';
|
||||
margin-right: 5px;
|
||||
padding-top: 1px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
14
src/hooks/auth/useUserActiveStatus.ts
Normal file
14
src/hooks/auth/useUserActiveStatus.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { differenceInDays, parseISO } from 'date-fns';
|
||||
|
||||
import { INACTIVE_ACCOUNT_DAYS } from '~/constants/user';
|
||||
|
||||
const today = new Date();
|
||||
|
||||
export const useUserActiveStatus = (lastSeen?: string) => {
|
||||
try {
|
||||
const lastSeenDate = lastSeen ? parseISO(lastSeen) : undefined;
|
||||
return lastSeenDate && differenceInDays(today, lastSeenDate) < INACTIVE_ACCOUNT_DAYS;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
|
@ -1,11 +1,7 @@
|
|||
import { differenceInDays, parseISO } from 'date-fns';
|
||||
|
||||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
import { INACTIVE_ACCOUNT_DAYS } from '~/constants/user';
|
||||
import { useUserActiveStatus } from '~/hooks/auth/useUserActiveStatus';
|
||||
import { IUser } from '~/types/auth';
|
||||
|
||||
const today = new Date();
|
||||
|
||||
export const useUserDescription = (user?: Partial<IUser>) => {
|
||||
const randomPhrase = useRandomPhrase('USER_DESCRIPTION');
|
||||
|
||||
|
@ -13,8 +9,9 @@ export const useUserDescription = (user?: Partial<IUser>) => {
|
|||
return '';
|
||||
}
|
||||
|
||||
const lastSeen = user.last_seen ? parseISO(user.last_seen) : undefined;
|
||||
if (!lastSeen || differenceInDays(today, lastSeen) > INACTIVE_ACCOUNT_DAYS) {
|
||||
const isActive = useUserActiveStatus(user.last_seen);
|
||||
|
||||
if (!isActive) {
|
||||
return 'Юнит деактивирован';
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||
|
||||
import { adjustHue } from 'color2k';
|
||||
|
||||
import { normalizeBrightColor } from '~/utils/color';
|
||||
import { generateGradientFromColor, normalizeBrightColor } from '~/utils/color';
|
||||
import { stringToColour } from '~/utils/dom';
|
||||
|
||||
export const useColorGradientFromString = (
|
||||
|
@ -16,9 +16,5 @@ export const useColorGradientFromString = (
|
|||
return '';
|
||||
}
|
||||
|
||||
const color = normalizeBrightColor(stringToColour(val), saturation, lightness);
|
||||
const second = normalizeBrightColor(adjustHue(color, 45), saturation, lightness);
|
||||
const third = normalizeBrightColor(adjustHue(color, 90), saturation, lightness);
|
||||
|
||||
return `linear-gradient(${angle}deg, ${color}, ${second}, ${third})`;
|
||||
return generateGradientFromColor(val, saturation, lightness, angle);
|
||||
}, [angle, lightness, saturation, val]);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { darken, desaturate, parseToHsla } from 'color2k';
|
||||
import { adjustHue, darken, desaturate, parseToHsla, transparentize } from 'color2k';
|
||||
|
||||
import { DEFAULT_DOMINANT_COLOR } from '~/constants/node';
|
||||
import { stringToColour } from '~/utils/dom';
|
||||
|
||||
export const normalizeBrightColor = (color?: string, saturationExp = 3, lightnessExp = 3) => {
|
||||
if (!color) {
|
||||
|
@ -14,3 +15,31 @@ export const normalizeBrightColor = (color?: string, saturationExp = 3, lightnes
|
|||
const desaturated = saturationExp > 1 ? desaturate(color, saturation ** saturationExp) : color;
|
||||
return lightnessExp > 1 ? darken(desaturated, lightness ** lightnessExp) : desaturated;
|
||||
};
|
||||
|
||||
export const generateColorTriplet = (val: string, saturation: number, lightness: number) => {
|
||||
const color = normalizeBrightColor(stringToColour(val), saturation, lightness);
|
||||
|
||||
return [
|
||||
color,
|
||||
normalizeBrightColor(adjustHue(color, 45), saturation, lightness),
|
||||
normalizeBrightColor(adjustHue(color, 90), saturation, lightness),
|
||||
];
|
||||
};
|
||||
|
||||
export const generateGradientFromColor = (
|
||||
val: string,
|
||||
saturation = 3,
|
||||
lightness = 3,
|
||||
angle = 155,
|
||||
opacity = 1
|
||||
) => {
|
||||
const [first, second, third] = generateColorTriplet(val, saturation, lightness).map(it => {
|
||||
if (opacity > 1 || opacity < 0) {
|
||||
return it;
|
||||
}
|
||||
|
||||
return transparentize(it, 1 - opacity);
|
||||
});
|
||||
|
||||
return `linear-gradient(${angle}deg, ${first}, ${second}, ${third})`;
|
||||
};
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue