Skip to content

Commit

Permalink
feat(Persona): add component (#599)
Browse files Browse the repository at this point in the history
  • Loading branch information
ogonkov authored Apr 4, 2023
1 parent 8fe2e40 commit 5190c9d
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 1 deletion.
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
/src/components/List @korvin89
/src/components/Loader @SeqviriouM
/src/components/Modal @amje
/src/components/Persona @DaffPunks
/src/components/Popover @kseniya57
/src/components/Popup @amje
/src/components/Portal @amje
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"devDependencies": {
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0",
"@faker-js/faker": "^7.6.0",
"@gravity-ui/eslint-config": "^1.0.0",
"@gravity-ui/prettier-config": "^1.0.0",
"@gravity-ui/stylelint-config": "^1.0.0",
Expand Down Expand Up @@ -154,4 +155,4 @@
"prettier --write"
]
}
}
}
65 changes: 65 additions & 0 deletions src/components/Persona/Persona.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, {FC, ReactNode} from 'react';
import {Icon} from '../Icon';
import {Mail} from '../icons';
import {PersonaWrap} from '../PersonaWrap';
import {getTwoLetters} from './getTwoLetters';

export interface PersonaProps {
/** Visible text */
text: string;
/** Image source */
image?: string;
/** Visual appearance (with or without border) */
theme?: 'default' | 'clear';
/** Avatar appearance */
type?: 'person' | 'email' | 'empty';
/** Text size */
size?: 's' | 'n';
/** Handle click on button with cross */
onClose?: (text: string) => void;
/** Handle click on component itself */
onClick?: (text: string) => void;
/** Custom CSS class for root element */
className?: string;
}

export const Persona: FC<PersonaProps> = ({
size = 's',
theme = 'default',
type = 'person',
onClick,
onClose,
text,
image,
className,
}) => {
let avatar: ReactNode | null;

switch (type) {
case 'person':
avatar = image ? <img alt={''} src={image} /> : <span>{getTwoLetters(text)}</span>;
break;
case 'email':
avatar = <Icon data={Mail} size={14} />;
break;
case 'empty':
avatar = null;
break;
}

return (
<PersonaWrap
size={size}
theme={theme}
isEmpty={type === 'empty'}
onClick={onClick && onClick.bind(null, text)}
onClose={onClose && onClose.bind(null, text)}
avatar={avatar}
className={className}
>
{text}
</PersonaWrap>
);
};

Persona.displayName = 'Persona';
22 changes: 22 additions & 0 deletions src/components/Persona/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Persona

Component to display user or email.

## PropTypes

| Property | Type | Required | Default | Description |
| :-------- | :--------------------------- | :------: | :-------- | :----------------------------------------- |
| text | `String` | `+` | | Visible text |
| image | `String` | | | Image source |
| theme | `'default', 'clear'` | | `default` | Visual appearance (with or without border) |
| type | `'person', 'email', 'empty'` | | `person` | Avatar appearance |
| size | `'s', 'n'` | | `s` | Text size |
| onClose | `(text: string) => void` | | | Handle click on button with cross |
| onClick | `(text: string) => void` | | | Handle click on component itself |
| className | `String` | | | Custom CSS class for root element |

## Examples

```jsx
const persona = <Persona text={defaultPerson} type={'person'} image={personImg} />;
```
50 changes: 50 additions & 0 deletions src/components/Persona/__stories__/Persona.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import {ComponentMeta, ComponentStory} from '@storybook/react';
import {faker} from '@faker-js/faker/locale/en';
import {Persona} from '../Persona';

export default {
title: 'Components/Persona',
component: Persona,
} as ComponentMeta<typeof Persona>;

const person = 'Charles Darwin';
const email = faker.internet.email(...person.split(' '));
const personImg = faker.internet.avatar();

const Template: ComponentStory<typeof Persona> = (args) => <Persona {...args} />;

export const Default = Template.bind({});
Default.args = {
text: person,
};

export const Image = Template.bind({});
Image.args = {
text: person,
image: personImg,
};

export const Email = Template.bind({});
Email.args = {
text: email,
type: 'email',
};

export const Empty = Template.bind({});
Empty.args = {
text: person,
type: 'empty',
};

export const Clickable = Template.bind({});
Clickable.args = {
text: person,
onClick: (text) => console.log('clicked', text),
};

export const Closable = Template.bind({});
Closable.args = {
text: person,
onClose: (text) => console.log('closed', text),
};
6 changes: 6 additions & 0 deletions src/components/Persona/getTwoLetters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import _ from 'lodash';

export function getTwoLetters(text: string) {
const words = text.split(' ');
return [_.get(words, '[0][0]'), _.get(words, '[1][0]')].filter(Boolean).join('');
}
1 change: 1 addition & 0 deletions src/components/Persona/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Persona';
140 changes: 140 additions & 0 deletions src/components/PersonaWrap/PersonaWrap.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
@use '../../../styles/mixins';
@use '../variables';

$block: '.#{variables.$ns}persona';

#{$block} {
$avatarSize: 28px;
$transitionDuration: 0.1s;
$transitionTimingFunction: ease-in-out;

height: $avatarSize;
display: inline-flex;
align-items: center;
position: relative;
z-index: 0;
border-radius: 20px;

transition-property: background-color;
transition-duration: $transitionDuration;
transition-timing-function: $transitionTimingFunction;

&_theme_default {
&:after {
position: absolute;
z-index: -1;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
border: 1px solid var(--yc-color-line-generic);
border-radius: 20px;

transition-property: border-color;
transition-duration: $transitionDuration;
transition-timing-function: $transitionTimingFunction;
}
}

&_empty {
&#{$block}_size_s {
padding-left: 12px;
}

&#{$block}_size_n {
padding-left: 16px;
}
}

&_clickable:hover {
cursor: pointer;
background-color: var(--yc-color-base-simple-hover);

&:after {
border-color: transparent;
}
}

&__main {
height: $avatarSize;
display: inline-flex;
align-items: center;
padding-right: 6px;

#{$block}_closeable & {
padding-right: 0;
}
}

&__avatar {
width: $avatarSize;
height: $avatarSize;
display: flex;
align-items: center;
border-radius: 50%;
overflow: hidden;
justify-content: center;
background-color: var(--yc-color-base-simple-hover-solid);

transition-property: background-color;
transition-duration: $transitionDuration;
transition-timing-function: $transitionTimingFunction;

#{$block}_size_n & {
margin-right: 12px;
}

#{$block}_size_s & {
margin-right: 6px;
}

#{$block}_clickable:hover & {
background-color: var(--yc-color-base-generic);
}

span {
@include mixins.text-accent;
text-transform: uppercase;
font-size: 11px;
line-height: 11px;
}

img {
width: $avatarSize;
height: $avatarSize;
object-fit: cover;
}
}

&__text {
#{$block}_size_n & {
@include mixins.text-body-2;
margin-right: 10px;
}

#{$block}_size_s & {
@include mixins.text-body-1;
margin-right: 6px;
}
}

&__close {
height: $avatarSize;
width: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding-right: 6px;
color: var(--yc-color-text-secondary);

transition-property: color;
transition-duration: $transitionDuration;
transition-timing-function: $transitionTimingFunction;

&:hover {
color: var(--yc-color-text-primary);
}
}
}
48 changes: 48 additions & 0 deletions src/components/PersonaWrap/PersonaWrap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, {ReactNode, MouseEvent, FC} from 'react';
import {Icon} from '../Icon';
import {CrossIcon} from '../icons';
import {block} from '../utils/cn';

import './PersonaWrap.scss';

const b = block('persona');

export interface PersonaWrapProps {
avatar: ReactNode;
children?: ReactNode;
isEmpty?: boolean;
theme?: 'default' | 'clear';
size?: 's' | 'n';
onClose?: (event: MouseEvent) => void;
onClick?: (event: MouseEvent) => void;
className?: string;
}

export const PersonaWrap: FC<PersonaWrapProps> = ({
size = 's',
theme = 'default',
isEmpty,
onClick,
onClose,
className,
avatar,
children,
}) => {
const clickable = Boolean(onClick);
const closeable = Boolean(onClose);
return (
<div className={b({size, theme, clickable, closeable, empty: isEmpty}, className)}>
<div className={b('main')} onClick={onClick}>
{avatar && <div className={b('avatar')}>{avatar}</div>}
<div className={b('text')}>{children}</div>
</div>
{onClose && (
<div className={b('close')} onClick={onClose}>
<Icon data={CrossIcon} size={8} />
</div>
)}
</div>
);
};

PersonaWrap.displayName = 'PersonaWrap';
20 changes: 20 additions & 0 deletions src/components/PersonaWrap/__stories__/PersonaWrap.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import {faker} from '@faker-js/faker/locale/en';
import {ComponentMeta, ComponentStory} from '@storybook/react';
import {PersonaWrap} from '../PersonaWrap';

export default {
title: 'Components/PersonaWrap',
component: PersonaWrap,
args: {
children: 'Darwin',
},
} as ComponentMeta<typeof PersonaWrap>;

const personImg = faker.internet.avatar();

const Template: ComponentStory<typeof PersonaWrap> = (args) => <PersonaWrap {...args} />;
export const Default = Template.bind({});
Default.args = {
avatar: <img alt={''} src={personImg} />,
};
1 change: 1 addition & 0 deletions src/components/PersonaWrap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PersonaWrap';
Loading

0 comments on commit 5190c9d

Please sign in to comment.