@bigbinary/neeto-editor 0.2.0 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/index.js +28 -7
  2. package/package.json +7 -5
  3. package/tailwind.config.js +13 -9
  4. package/webpack.config.js +9 -1
  5. package/webpack.dev.config.js +9 -1
  6. package/public/favicon.ico +0 -0
  7. package/public/index.html +0 -28
  8. package/public/robots.txt +0 -3
  9. package/src/App.js +0 -8
  10. package/src/Common/Avatar.js +0 -168
  11. package/src/Common/CodeBlock.js +0 -11
  12. package/src/Common/Description.js +0 -12
  13. package/src/Common/Dropdown/index.js +0 -118
  14. package/src/Common/Heading.js +0 -13
  15. package/src/Common/HighlightText.js +0 -7
  16. package/src/Common/Icons/HashtagFilled.js +0 -59
  17. package/src/Common/Icons/TextColor.js +0 -35
  18. package/src/Common/Icons/index.js +0 -2
  19. package/src/Common/ListItems.js +0 -17
  20. package/src/Editor/CustomExtensions/BubbleMenu/index.js +0 -92
  21. package/src/Editor/CustomExtensions/CodeBlock/CodeBlockComponent.js +0 -12
  22. package/src/Editor/CustomExtensions/CodeBlock/ExtensionConfig.js +0 -10
  23. package/src/Editor/CustomExtensions/Embeds.js +0 -72
  24. package/src/Editor/CustomExtensions/FixedMenu/FontSizeOption.js +0 -32
  25. package/src/Editor/CustomExtensions/FixedMenu/TextColorOption.js +0 -29
  26. package/src/Editor/CustomExtensions/FixedMenu/constants.js +0 -3
  27. package/src/Editor/CustomExtensions/FixedMenu/index.js +0 -186
  28. package/src/Editor/CustomExtensions/Image/ExtensionConfig.js +0 -47
  29. package/src/Editor/CustomExtensions/Image/Uploader.js +0 -41
  30. package/src/Editor/CustomExtensions/Mention/ExtensionConfig.js +0 -67
  31. package/src/Editor/CustomExtensions/Mention/MentionList.js +0 -96
  32. package/src/Editor/CustomExtensions/Mention/helpers.js +0 -23
  33. package/src/Editor/CustomExtensions/Placeholder/ExtensionConfig.js +0 -81
  34. package/src/Editor/CustomExtensions/Placeholder/helpers.js +0 -18
  35. package/src/Editor/CustomExtensions/SlashCommands/Commands.js +0 -20
  36. package/src/Editor/CustomExtensions/SlashCommands/CommandsList.js +0 -109
  37. package/src/Editor/CustomExtensions/SlashCommands/ExtensionConfig.js +0 -205
  38. package/src/Editor/CustomExtensions/Variable/ExtensionConfig.js +0 -208
  39. package/src/Editor/CustomExtensions/Variable/VariableList.js +0 -45
  40. package/src/Editor/CustomExtensions/Variable/VariableSuggestion.js +0 -20
  41. package/src/Editor/CustomExtensions/Variable/helpers.js +0 -31
  42. package/src/Editor/CustomExtensions/Variable/index.js +0 -35
  43. package/src/Editor/CustomExtensions/useCustomExtensions.js +0 -87
  44. package/src/Editor/index.js +0 -92
  45. package/src/Editor/sharedState.js +0 -8
  46. package/src/examples/constants.js +0 -95
  47. package/src/examples/index.js +0 -186
  48. package/src/hooks/useOutsideClick.js +0 -19
  49. package/src/index.js +0 -10
  50. package/src/index.scss +0 -41
  51. package/src/styles/abstracts/_mixins.scss +0 -20
  52. package/src/styles/abstracts/_neeto-ui-variables.scss +0 -36
  53. package/src/styles/abstracts/_variables.scss +0 -10
  54. package/src/styles/components/_avatar.scss +0 -105
  55. package/src/styles/components/_codeblock.scss +0 -16
  56. package/src/styles/components/_command-list.scss +0 -19
  57. package/src/styles/components/_dropdown.scss +0 -69
  58. package/src/styles/components/_editor-variables.scss +0 -12
  59. package/src/styles/components/_editor.scss +0 -98
  60. package/src/styles/components/_fixed-menu.scss +0 -13
  61. package/src/utils/common.js +0 -13
@@ -1,92 +0,0 @@
1
- import React from "react";
2
- import classnames from "classnames";
3
- import { BubbleMenu } from "@tiptap/react";
4
- import {
5
- TextBold,
6
- TextItalic,
7
- TextCross,
8
- Link,
9
- Code,
10
- Highlight,
11
- } from "@bigbinary/neeto-icons";
12
- import { roundArrow } from "tippy.js";
13
- import "tippy.js/dist/svg-arrow.css";
14
-
15
- export default function index({ editor, formatterOptions }) {
16
- if (!editor) {
17
- return null;
18
- }
19
- const options = [
20
- {
21
- Icon: TextBold,
22
- command: () => editor.chain().focus().toggleBold().run(),
23
- active: editor.isActive("bold"),
24
- optionName: "bold",
25
- },
26
- {
27
- Icon: TextItalic,
28
- command: () => editor.chain().focus().toggleItalic().run(),
29
- active: editor.isActive("italic"),
30
- optionName: "italic",
31
- },
32
- {
33
- Icon: TextCross,
34
- command: () => editor.chain().focus().toggleStrike().run(),
35
- active: editor.isActive("strike"),
36
- optionName: "strike",
37
- },
38
- {
39
- Icon: Link,
40
- command: () => {
41
- if (editor.isActive("link")) {
42
- editor.chain().focus().unsetLink().run();
43
- } else {
44
- const url = window.prompt("Please enter your URL");
45
- editor.chain().focus().setLink({ href: url }).run();
46
- }
47
- },
48
- active: editor.isActive("link"),
49
- optionName: "link",
50
- },
51
- {
52
- Icon: Code,
53
- command: () => editor.chain().focus().toggleCode().run(),
54
- active: editor.isActive("code"),
55
- optionName: "code",
56
- },
57
- {
58
- Icon: Highlight,
59
- command: () => editor.chain().focus().toggleHighlight().run(),
60
- active: editor.isActive("highlight"),
61
- optionName: "highlight",
62
- },
63
- ];
64
- return (
65
- <BubbleMenu
66
- editor={editor}
67
- tippyOptions={{ arrow: roundArrow }}
68
- className="relative flex rounded shadow editor-command-list--root"
69
- >
70
- {options
71
- .filter(({ optionName }) => formatterOptions.includes(optionName))
72
- .map((option) => (
73
- <Option {...option} key={option.optionName} />
74
- ))}
75
- </BubbleMenu>
76
- );
77
- }
78
-
79
- const Option = ({ Icon, command, active, iconSize }) => (
80
- <div
81
- className={classnames(
82
- "p-3 cursor-pointer editor-command-list--item transition-colors rounded",
83
- {
84
- "text-gray-400": !active,
85
- "text-white": active,
86
- }
87
- )}
88
- onClick={command}
89
- >
90
- <Icon size={iconSize || 24} />
91
- </div>
92
- );
@@ -1,12 +0,0 @@
1
- import React from "react";
2
- import { NodeViewWrapper, NodeViewContent } from "@tiptap/react";
3
-
4
- export default function index() {
5
- return (
6
- <NodeViewWrapper>
7
- <pre>
8
- <NodeViewContent as="code" />
9
- </pre>
10
- </NodeViewWrapper>
11
- );
12
- }
@@ -1,10 +0,0 @@
1
- import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
2
- import { ReactNodeViewRenderer } from "@tiptap/react";
3
- import CodeBlockComponent from "./CodeBlockComponent";
4
- import lowlight from "lowlight";
5
-
6
- export default CodeBlockLowlight.extend({
7
- addNodeView() {
8
- return new ReactNodeViewRenderer(CodeBlockComponent);
9
- },
10
- }).configure({ lowlight });
@@ -1,72 +0,0 @@
1
- import { Node, mergeAttributes } from "@tiptap/core";
2
-
3
- export default Node.create({
4
- name: "external-video",
5
-
6
- addOptions() {
7
- return {
8
- inline: false,
9
- HTMLAttributes: {},
10
- };
11
- },
12
-
13
- inline() {
14
- return this.options.inline;
15
- },
16
-
17
- group() {
18
- return this.options.inline ? "inline" : "block";
19
- },
20
-
21
- draggable: true,
22
-
23
- addAttributes() {
24
- return {
25
- src: {
26
- default: null,
27
- },
28
- title: {
29
- default: null,
30
- },
31
- frameborder: {
32
- default: "0",
33
- },
34
- allow: {
35
- default:
36
- "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
37
- },
38
- allowfullscreen: {
39
- default: "allowfullscreen",
40
- },
41
- };
42
- },
43
-
44
- parseHTML() {
45
- return [
46
- {
47
- tag: "iframe[src]",
48
- },
49
- ];
50
- },
51
-
52
- renderHTML({ HTMLAttributes }) {
53
- return [
54
- "div",
55
- { class: "video-wrapper" },
56
- ["iframe", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)],
57
- ];
58
- },
59
-
60
- addCommands() {
61
- return {
62
- setExternalVideo:
63
- (options) =>
64
- ({ commands }) => {
65
- return commands.insertContent({
66
- type: this.name,
67
- attrs: options,
68
- });
69
- },
70
- };
71
- },
72
- });
@@ -1,32 +0,0 @@
1
- import React from "react";
2
-
3
- import { TextSize } from "@bigbinary/neeto-icons";
4
-
5
- import { ICON_COLOR_ACTIVE, MENU_ICON_SIZE } from "./constants";
6
- import Dropdown from "../../../Common/Dropdown";
7
-
8
- const FontSizeOption = ({ onChange }) => {
9
- const options = [
10
- { label: <h2 className="text-xl font-medium">Large</h2>, value: "large" },
11
- { label: <h3 className="text-lg">Medium</h3>, value: "medium" },
12
- { label: <span className="text-sm">Normal</span>, value: "normal" },
13
- ];
14
-
15
- return (
16
- <Dropdown
17
- customTarget={() => (
18
- <button className="p-3 transition-colors editor-fixed-menu--item">
19
- <TextSize size={MENU_ICON_SIZE} color={ICON_COLOR_ACTIVE} />
20
- </button>
21
- )}
22
- >
23
- {options.map(({ label, className, value }) => (
24
- <li className={className} onClick={() => onChange(value)}>
25
- {label}
26
- </li>
27
- ))}
28
- </Dropdown>
29
- );
30
- };
31
-
32
- export default FontSizeOption;
@@ -1,29 +0,0 @@
1
- import React, { useRef } from "react";
2
-
3
- import { TextColor } from "../../../Common/Icons";
4
- import { ICON_COLOR_ACTIVE, MENU_ICON_SIZE } from "./constants";
5
-
6
- const TextColorOption = ({ color = "#000", onChange }) => {
7
- const colorInputRef = useRef();
8
-
9
- return (
10
- <div
11
- onClick={() => colorInputRef.current?.click()}
12
- className="flex items-center justify-center p-3 transition-colors cursor-pointer editor-fixed-menu--item"
13
- >
14
- <TextColor
15
- size={MENU_ICON_SIZE}
16
- underlineColor={color}
17
- color={ICON_COLOR_ACTIVE}
18
- />
19
- <input
20
- ref={colorInputRef}
21
- type="color"
22
- className="invisible w-0 h-0"
23
- onChange={(event) => onChange && onChange(event.target.value)}
24
- />
25
- </div>
26
- );
27
- };
28
-
29
- export default TextColorOption;
@@ -1,3 +0,0 @@
1
- export const MENU_ICON_SIZE = 24;
2
- export const ICON_COLOR_ACTIVE = "#2F3941";
3
- export const ICON_COLOR_INACTIVE = "#87929D";
@@ -1,186 +0,0 @@
1
- import React from "react";
2
- import {
3
- TextBold,
4
- TextItalic,
5
- Underline,
6
- TextCross,
7
- Link,
8
- Code,
9
- ListDot,
10
- ListNumber,
11
- Image,
12
- Quote,
13
- Undo,
14
- Redo,
15
- } from "@bigbinary/neeto-icons";
16
-
17
- import TextColorOption from "./TextColorOption";
18
- import FontSizeOption from "./FontSizeOption";
19
-
20
- import { ICON_COLOR_ACTIVE, ICON_COLOR_INACTIVE } from "./constants";
21
- import sharedState from "../../sharedState";
22
- import Variables from "../Variable";
23
-
24
- const FixedMenu = ({ editor, variables }) => {
25
- if (!editor) {
26
- return null;
27
- }
28
-
29
- const fontStyleOptions = [
30
- {
31
- Icon: TextBold,
32
- command: () => editor.chain().focus().toggleBold().run(),
33
- active: editor.isActive("bold"),
34
- optionName: "bold",
35
- },
36
- {
37
- Icon: TextItalic,
38
- command: () => editor.chain().focus().toggleItalic().run(),
39
- active: editor.isActive("italic"),
40
- optionName: "italic",
41
- },
42
- {
43
- Icon: Underline,
44
- command: () => editor.chain().focus().toggleUnderline().run(),
45
- active: editor.isActive("underline"),
46
- optionName: "underline",
47
- size: 27,
48
- },
49
- {
50
- Icon: TextCross,
51
- command: () => editor.chain().focus().toggleStrike().run(),
52
- active: editor.isActive("strike"),
53
- optionName: "strike",
54
- },
55
- ];
56
-
57
- const blockStyleOptions = [
58
- {
59
- Icon: Link,
60
- command: () => {
61
- if (editor.isActive("link")) {
62
- editor.chain().focus().unsetLink().run();
63
- } else {
64
- const url = window.prompt("Please enter your URL");
65
- editor.chain().focus().setLink({ href: url }).run();
66
- }
67
- },
68
- active: editor.isActive("link"),
69
- optionName: "link",
70
- },
71
- {
72
- Icon: Quote,
73
- command: () => editor.chain().focus().toggleBlockquote().run(),
74
- active: editor.isActive("blockquote"),
75
- optionName: "block-quote",
76
- },
77
- {
78
- Icon: Code,
79
- command: () => editor.chain().focus().toggleCode().run(),
80
- active: editor.isActive("code"),
81
- optionName: "code",
82
- },
83
- {
84
- Icon: Image,
85
- command: ({ editor, range }) => {
86
- sharedState.showImageUpload = true;
87
- sharedState.range = range;
88
- editor.chain().focus().deleteRange(range).run();
89
- },
90
- optionName: "image-upload",
91
- },
92
- ];
93
-
94
- const listStyleOptions = [
95
- {
96
- Icon: ListDot,
97
- command: () => editor.chain().focus().toggleBulletList().run(),
98
- active: editor.isActive("bulletList"),
99
- optionName: "bullet-list",
100
- },
101
- {
102
- Icon: ListNumber,
103
- command: () => editor.chain().focus().toggleOrderedList().run(),
104
- active: editor.isActive("orderedList"),
105
- optionName: "ordered-list",
106
- },
107
- ];
108
-
109
- const editorOptions = [
110
- {
111
- Icon: Undo,
112
- command: () => editor.chain().focus().undo().run(),
113
- optionName: "undo",
114
- disabled: !editor.can().undo(),
115
- active: editor.can().undo(),
116
- },
117
- {
118
- Icon: Redo,
119
- command: () => editor.chain().focus().redo().run(),
120
- optionName: "redo",
121
- disabled: !editor.can().redo(),
122
- active: editor.can().redo(),
123
- },
124
- ];
125
-
126
- const handleTextSizeChange = (value) => {
127
- switch (value) {
128
- case "large": {
129
- editor.chain().focus().setHeading({ level: 2 }).run();
130
- break;
131
- }
132
- case "medium": {
133
- editor.chain().focus().setHeading({ level: 3 }).run();
134
- break;
135
- }
136
- case "normal": {
137
- editor.chain().focus().setParagraph().run();
138
- break;
139
- }
140
- }
141
- };
142
-
143
- const renderOptionButton = ({
144
- Icon,
145
- command,
146
- active,
147
- optionName,
148
- disabled,
149
- ...rest
150
- }) => (
151
- <button
152
- disabled={disabled}
153
- onClick={command}
154
- key={optionName}
155
- className="p-3 transition-colors cursor-pointer editor-fixed-menu--item"
156
- >
157
- <Icon
158
- color={active ? ICON_COLOR_ACTIVE : ICON_COLOR_INACTIVE}
159
- {...rest}
160
- />
161
- </button>
162
- );
163
-
164
- return (
165
- <div className="flex items-center space-x-6 border-t border-l border-r editor-fixed-menu--root">
166
- <div className="flex">
167
- <TextColorOption
168
- color={editor.getAttributes("textStyle").color}
169
- onChange={(color) => editor.chain().focus().setColor(color).run()}
170
- />
171
- <FontSizeOption onChange={handleTextSizeChange} />
172
- {fontStyleOptions.map(renderOptionButton)}
173
- </div>
174
- {[blockStyleOptions, listStyleOptions, editorOptions].map(
175
- (optionGroup) => (
176
- <div className="flex">{optionGroup.map(renderOptionButton)}</div>
177
- )
178
- )}
179
- <div className="flex justify-end flex-1">
180
- <Variables editor={editor} variables={variables} />
181
- </div>
182
- </div>
183
- );
184
- };
185
-
186
- export default FixedMenu;
@@ -1,47 +0,0 @@
1
- import Image from "@tiptap/extension-image";
2
- import { mergeAttributes } from "@tiptap/core";
3
-
4
- export default Image.extend({
5
- name: "image",
6
-
7
- addAttributes() {
8
- return {
9
- ...Image.config.addAttributes(),
10
- size: {
11
- default: "small",
12
- rendered: false,
13
- },
14
- float: {
15
- default: "none",
16
- rendered: false,
17
- },
18
- };
19
- },
20
-
21
- addCommands() {
22
- return {
23
- setImage:
24
- (options) =>
25
- ({ tr, commands }) => {
26
- if (tr.selection?.node?.type?.name == "image") {
27
- return commands.updateAttributes("image", options);
28
- } else {
29
- return commands.insertContent({
30
- type: this.name,
31
- attrs: options,
32
- });
33
- }
34
- },
35
- };
36
- },
37
-
38
- renderHTML({ node, HTMLAttributes }) {
39
- HTMLAttributes.class = " image-" + node.attrs.size;
40
- HTMLAttributes.class += " image-float-" + node.attrs.float;
41
-
42
- return [
43
- "img",
44
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
45
- ];
46
- },
47
- });
@@ -1,41 +0,0 @@
1
- import React from "react";
2
- import { view } from "@risingstack/react-easy-state";
3
- import Uppy from "@uppy/core";
4
- import { DashboardModal } from "@uppy/react";
5
- import XHRUpload from "@uppy/xhr-upload";
6
- import "@uppy/core/dist/style.css";
7
- import "@uppy/dashboard/dist/style.css";
8
- import sharedState from "../../sharedState";
9
-
10
- const ImageUpload = ({ editor, imageUploadUrl }) => {
11
- const uppy = new Uppy({
12
- allowMultipleUploads: false,
13
- autoProceed: true,
14
- debug: true,
15
- });
16
-
17
- uppy.use(XHRUpload, {
18
- endpoint: imageUploadUrl || "/api/v1/direct_uploads",
19
- formData: true,
20
- fieldName: "blob",
21
- });
22
-
23
- uppy.on("upload-success", (file, response) => {
24
- const url = response.body.imageURL;
25
- editor.chain().focus().setImage({ src: url }).run();
26
- });
27
-
28
- return (
29
- <div>
30
- <DashboardModal
31
- uppy={uppy}
32
- proudlyDisplayPoweredByUppy={false}
33
- closeModalOnClickOutside
34
- open={sharedState.showImageUpload}
35
- onRequestClose={() => (sharedState.showImageUpload = false)}
36
- />
37
- </div>
38
- );
39
- };
40
-
41
- export default view(ImageUpload);
@@ -1,67 +0,0 @@
1
- import Mention from "@tiptap/extension-mention";
2
- import tippy from "tippy.js";
3
- import { ReactRenderer } from "@tiptap/react";
4
-
5
- import { MentionList } from "./MentionList";
6
-
7
- import { createMentionSuggestions } from "./helpers";
8
-
9
- const suggestion = {
10
- render: () => {
11
- let reactRenderer;
12
- let popup;
13
-
14
- return {
15
- onStart: (props) => {
16
- console.log({ props });
17
- reactRenderer = new ReactRenderer(MentionList, {
18
- props,
19
- editor: props.editor,
20
- });
21
-
22
- popup = tippy("body", {
23
- getReferenceClientRect: props.clientRect,
24
- appendTo: () => document.body,
25
- content: reactRenderer.element,
26
- showOnCreate: true,
27
- interactive: true,
28
- trigger: "manual",
29
- placement: "bottom-start",
30
- });
31
- },
32
-
33
- onUpdate(props) {
34
- reactRenderer.updateProps(props);
35
-
36
- popup[0].setProps({
37
- getReferenceClientRect: props.clientRect,
38
- });
39
- },
40
-
41
- onKeyDown(props) {
42
- if (props.event.key === "Escape") {
43
- popup[0].hide();
44
-
45
- return true;
46
- }
47
-
48
- return reactRenderer.ref?.onKeyDown(props);
49
- },
50
-
51
- onExit() {
52
- popup[0].destroy();
53
- reactRenderer.destroy();
54
- },
55
- };
56
- },
57
- };
58
-
59
- export default {
60
- configure: ({ suggestion: suggestionConfig = {}, ...otherConfig }) =>
61
- Mention.configure({
62
- ...otherConfig,
63
- suggestion: { ...suggestion, ...suggestionConfig },
64
- }),
65
- };
66
-
67
- export { createMentionSuggestions };
@@ -1,96 +0,0 @@
1
- import React from "react";
2
- import classNames from "classnames";
3
-
4
- import Avatar from "../../../Common/Avatar";
5
-
6
- export class MentionList extends React.Component {
7
- state = { selectedIndex: 0 };
8
-
9
- componentDidUpdate(prevProps) {
10
- const { items } = this.props;
11
- if (items !== prevProps.items) {
12
- this.setState({ selectedIndex: 0 });
13
- }
14
- }
15
-
16
- selectItem = (index) => {
17
- const { items, command } = this.props;
18
- const item = items[index];
19
-
20
- if (item) {
21
- command({ label: item.label, id: item.key });
22
- }
23
- };
24
-
25
- upHandler = () => {
26
- const { items } = this.props;
27
- this.setState((prevState) => {
28
- const { selectedIndex } = prevState;
29
- const nextSelectedIndex =
30
- (selectedIndex + items.length - 1) % items.length;
31
- return {
32
- selectedIndex: nextSelectedIndex,
33
- };
34
- });
35
- };
36
-
37
- downHandler = () => {
38
- const { items } = this.props;
39
- this.setState((prevState) => {
40
- const { selectedIndex } = prevState;
41
- const nextSelectedIndex = (selectedIndex + 1) % items.length;
42
- return {
43
- selectedIndex: nextSelectedIndex,
44
- };
45
- });
46
- };
47
-
48
- enterHandler = () => {
49
- const { selectedIndex } = this.state;
50
- this.selectItem(selectedIndex);
51
- };
52
-
53
- onKeyDown = ({ event }) => {
54
- const keyDownHandlers = {
55
- ArrowUp: this.upHandler,
56
- ArrowDown: this.downHandler,
57
- Enter: this.enterHandler,
58
- };
59
-
60
- if (keyDownHandlers.hasOwnProperty(event.key)) {
61
- keyDownHandlers[event.key]();
62
- return true;
63
- }
64
-
65
- return false;
66
- };
67
-
68
- render() {
69
- const { selectedIndex } = this.state;
70
- const { items } = this.props;
71
-
72
- const containerClassName =
73
- "relative p-2 space-y-1 overflow-hidden rounded shadow editor-command-list--root";
74
- const itemClassName =
75
- "flex items-center w-full px-4 py-2 transition-all duration-100 ease-in-out cursor-pointer text-xs text-white rounded editor-command-list--item";
76
-
77
- return (
78
- <div className={containerClassName}>
79
- {items.map(({ label, imageUrl, showImage }, index) => (
80
- <button
81
- className={classNames(itemClassName, {
82
- selected_item: index === selectedIndex,
83
- })}
84
- key={label}
85
- onClick={() => this.selectItem(index)}
86
- >
87
- {showImage ? (
88
- <Avatar user={{ name: label, imageUrl }} className="mr-2" />
89
- ) : null}
90
- <span>{label}</span>
91
- </button>
92
- ))}
93
- </div>
94
- );
95
- }
96
- }