Skip to content

Commit

Permalink
feat(Reactions): add addButtonPlacement property (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruminat authored Sep 5, 2024
1 parent 70bf9f0 commit 6eccd44
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 45 deletions.
44 changes: 21 additions & 23 deletions src/components/Reactions/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
## Reactions

Component for user reactions (e.g. 👍, 😊, 😎 etc) as new GitHub comments for example.
Component for user reactions (e.g. 👍, 😊, 😎 etc) as in GitHub comments for example.

### Usage example

```typescript
import React from 'react';

import {PaletteOption} from '@gravity-ui/uikit';
import {ReactionState, Reactions} from '@gravity-ui/components';
import {Reactions, ReactionProps, ReactionState} from '@gravity-ui/components';

const user = {
spongeBob: {name: 'Sponge Bob'},
Expand All @@ -20,18 +19,18 @@ const currentUser = user.spongeBob;
const option = {
'thumbs-up': {content: '👍', value: 'thumbs-up'},
cool: {content: '😎', value: 'cool'},
} satisfies Record<string, PaletteOption>;
} satisfies Record<string, ReactionProps>;

const options = Object.values(option);
const options: ReactionProps[] = Object.values(option);

const YourComponent = () => {
export const YourComponent = () => {
// You can set up a mapping: reaction.value -> users reacted
const [usersReacted, setUsersReacted] = React.useState({
[option.cool.value]: [user.spongeBob],
});

// And then convert that mapping into an array of ReactionState
const reactions = React.useMemo(
const reactionsState = React.useMemo(
() =>
Object.entries(usersReacted).map(
([value, users]): ReactionState => ({
Expand All @@ -44,7 +43,7 @@ const YourComponent = () => {
);

// You can then handle clicking on a reaction with changing the inital mapping,
// and the array of ReactionState will change accordingly
// and the reactionsState array will change accordingly
const onToggle = React.useCallback(
(value: string) => {
if (!usersReacted[value]) {
Expand Down Expand Up @@ -74,9 +73,7 @@ const YourComponent = () => {
[usersReacted],
);

return (
<Reactions palette={{options}} reactions={reactions} onToggle={onToggle} />
);
return <Reactions reactions={options} reactionsState={reactionsState} onToggle={onToggle} />;
};
```

Expand All @@ -86,18 +83,19 @@ For more code examples go to [Reactions.stories.tsx](https://github.com/gravity-

**ReactionsProps** (main component props — Reactions' list):

| Property | Type | Required | Default | Description |
| :--------------- | :------------------------------------------ | :------: | :------ | :--------------------------------------------------------------------------------------------- |
| `className` | `string` | | | HTML `class` attribute |
| `onToggle` | `(value: string) => void` | | | Fires when a user clicks on a Reaction (in a Palette or in the Reactions' list) |
| `paletteProps` | `ReactionsPaletteProps` | `true` | | Notifications' palette props — it's a `Palette` component with available reactions to the user |
| `qa` | `string` | | | `qa` attribute for testing |
| `reactions` | `PaletteOption[]` | `true` | | List of all available reactions |
| `reactionsState` | `ReactionState[]` | `true` | | List of reactions that were used |
| `readOnly` | `boolean` | | `false` | readOnly state (usage example: only signed in users can react) |
| `renderTooltip` | `(state: ReactionState) => React.ReactNode` | | | Reaction's tooltip with the list of reacted users for example |
| `size` | `ButtonSize` | | `m` | Buttons's size |
| `style` | `React.CSSProperties` | | | HTML `style` attribute |
| Property | Type | Required | Default | Description |
| :------------------- | :------------------------------------------ | :------: | :------ | :--------------------------------------------------------------------------------------------- |
| `addButtonPlacement` | `'start' or 'end'` | | `'end'` | Position of the "Add reaction" button. |
| `className` | `string` | | | HTML `class` attribute |
| `onToggle` | `(value: string) => void` | | | Fires when a user clicks on a Reaction (in a Palette or in the Reactions' list) |
| `paletteProps` | `ReactionsPaletteProps` | `true` | | Notifications' palette props — it's a `Palette` component with available reactions to the user |
| `qa` | `string` | | | `qa` attribute for testing |
| `reactions` | `PaletteOption[]` | `true` | | List of all available reactions |
| `reactionsState` | `ReactionState[]` | `true` | | List of reactions that were used |
| `readOnly` | `boolean` | | `false` | readOnly state (usage example: only signed in users can react) |
| `renderTooltip` | `(state: ReactionState) => React.ReactNode` | | | Reaction's tooltip with the list of reacted users for example |
| `size` | `ButtonSize` | | `m` | Buttons's size |
| `style` | `React.CSSProperties` | | | HTML `style` attribute |

**ReactionState** (single reaction props):

Expand Down
54 changes: 32 additions & 22 deletions src/components/Reactions/Reactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export interface ReactionsProps extends Pick<PaletteProps, 'size'>, QAProps, DOM
* Reactions' readonly state (when a user is unable to react for some reason).
*/
readOnly?: boolean;
/**
* Position of the "Add reaction" button.
*
* @default 'end'
*/
addButtonPlacement?: 'start' | 'end';
/**
* If present, when a user hovers over the reaction, a popover appears with renderTooltip(state) content.
* Can be used to display users who used this reaction.
Expand Down Expand Up @@ -74,6 +80,7 @@ export function Reactions({
paletteProps,
readOnly,
qa,
addButtonPlacement = 'end',
renderTooltip,
onToggle,
}: ReactionsProps) {
Expand Down Expand Up @@ -122,6 +129,28 @@ export function Reactions({
[paletteProps, reactions, paletteValue, size, onUpdatePalette],
);

const addReactionButton = readOnly ? null : (
<Popover
content={paletteContent}
tooltipContentClassName={b('add-reaction-popover')}
openOnHover={false}
hasArrow={false}
focusTrap
autoFocus
>
<Button
className={b('reaction-button')}
size={size}
extraProps={{'aria-label': i18n('add-reaction')}}
view="flat-secondary"
>
<Button.Icon>
<Icon data={FaceSmile} size={buttonSizeToIconSize[size]} />
</Button.Icon>
</Button>
</Popover>
);

return (
<ReactionsContextProvider
value={{
Expand All @@ -130,6 +159,8 @@ export function Reactions({
}}
>
<Flex className={b(null, className)} style={style} gap={1} wrap={true} qa={qa}>
{addButtonPlacement === 'start' ? addReactionButton : null}

{/* Reactions' list */}
{reactionsState.map((reaction) => {
const content = paletteOptionsMap[reaction.value]?.content ?? '?';
Expand All @@ -146,28 +177,7 @@ export function Reactions({
);
})}

{/* Add reaction button */}
{readOnly ? null : (
<Popover
content={paletteContent}
tooltipContentClassName={b('add-reaction-popover')}
openOnHover={false}
hasArrow={false}
focusTrap
autoFocus
>
<Button
className={b('reaction-button')}
size={size}
extraProps={{'aria-label': i18n('add-reaction')}}
view="flat-secondary"
>
<Button.Icon>
<Icon data={FaceSmile} size={buttonSizeToIconSize[size]} />
</Button.Icon>
</Button>
</Popover>
)}
{addButtonPlacement === 'end' ? addReactionButton : null}
</Flex>
</ReactionsContextProvider>
);
Expand Down
15 changes: 15 additions & 0 deletions src/components/Reactions/__stories__/Reactions.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,18 @@ export const Size: StoryFn = () => {
</Flex>
);
};

export const AddButtonPlacement: StoryFn = () => {
return (
<Flex direction="column" gap={4}>
<Flex direction="column" gap={2}>
<Text variant="subheader-1">Start</Text>
<Reactions {...useMockReactions()} addButtonPlacement="start" />
</Flex>
<Flex direction="column" gap={2}>
<Text variant="subheader-1">End</Text>
<Reactions {...useMockReactions()} addButtonPlacement="end" />
</Flex>
</Flex>
);
};

0 comments on commit 6eccd44

Please sign in to comment.