@lobehub/ui 1.149.3 → 1.150.0

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.
@@ -0,0 +1,13 @@
1
+ /// <reference types="react" />
2
+ export interface AvatarUploaderProps {
3
+ compressSize?: number;
4
+ onChange: (avatar: string) => void;
5
+ onUpload?: (file: File) => void;
6
+ texts?: {
7
+ draggerDesc?: string;
8
+ fileTypeError?: string;
9
+ uploadBtn?: string;
10
+ };
11
+ }
12
+ declare const AvatarUploader: import("react").NamedExoticComponent<AvatarUploaderProps>;
13
+ export default AvatarUploader;
@@ -0,0 +1,129 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
+ import { Button, Upload, message } from 'antd';
3
+ import { ChevronLeftIcon, ImageUpIcon } from 'lucide-react';
4
+ import { memo, useCallback, useRef, useState } from 'react';
5
+ import AvatarEditor from 'react-avatar-editor';
6
+ import { Center, Flexbox } from 'react-layout-kit';
7
+ import Icon from "../Icon";
8
+ import { useStyles } from "./style";
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ import { jsxs as _jsxs } from "react/jsx-runtime";
11
+ var Dragger = Upload.Dragger;
12
+ var createUploadImageHandler = function createUploadImageHandler(onUploadImage) {
13
+ return function (file) {
14
+ var reader = new FileReader();
15
+ reader.readAsDataURL(file);
16
+ reader.addEventListener('load', function () {
17
+ onUploadImage(String(reader.result));
18
+ });
19
+ };
20
+ };
21
+ var AvatarUploader = /*#__PURE__*/memo(function (_ref) {
22
+ var onChange = _ref.onChange,
23
+ texts = _ref.texts,
24
+ _ref$compressSize = _ref.compressSize,
25
+ compressSize = _ref$compressSize === void 0 ? 256 : _ref$compressSize,
26
+ onUpload = _ref.onUpload;
27
+ var editor = useRef(null);
28
+ var _useState = useState(''),
29
+ _useState2 = _slicedToArray(_useState, 2),
30
+ previewImage = _useState2[0],
31
+ setPreviewImage = _useState2[1];
32
+ var _useStyles = useStyles(),
33
+ styles = _useStyles.styles;
34
+ var beforeUpload = useCallback(function (file) {
35
+ var isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
36
+ if (!isJpgOrPng) {
37
+ message.error((texts === null || texts === void 0 ? void 0 : texts.fileTypeError) || 'You can only upload JPG/PNG file!');
38
+ return;
39
+ }
40
+ return createUploadImageHandler(function (avatar) {
41
+ setPreviewImage(avatar);
42
+ })(file);
43
+ }, []);
44
+ var handleUpload = function handleUpload() {
45
+ if (!editor.current) return;
46
+ var canvasScaled = editor.current.getImageScaledToCanvas();
47
+ var dataUrl = canvasScaled.toDataURL();
48
+ onChange(dataUrl);
49
+ if (!onUpload) return;
50
+ var file = new File([dataUrl], 'avatar.webp', {
51
+ type: 'image/webp'
52
+ });
53
+ onUpload === null || onUpload === void 0 || onUpload(file);
54
+ };
55
+ return /*#__PURE__*/_jsxs(Flexbox, {
56
+ padding: 10,
57
+ style: {
58
+ position: 'relative'
59
+ },
60
+ width: '100%',
61
+ children: [!previewImage && /*#__PURE__*/_jsx(Dragger, {
62
+ accept: 'image',
63
+ beforeUpload: beforeUpload,
64
+ itemRender: function itemRender() {
65
+ return void 0;
66
+ },
67
+ maxCount: 1,
68
+ multiple: false,
69
+ children: /*#__PURE__*/_jsxs(Center, {
70
+ gap: 16,
71
+ height: compressSize,
72
+ width: compressSize,
73
+ children: [/*#__PURE__*/_jsx(Icon, {
74
+ icon: ImageUpIcon,
75
+ size: {
76
+ fontSize: 48,
77
+ strokeWidth: 1.5
78
+ }
79
+ }), /*#__PURE__*/_jsx("div", {
80
+ children: (texts === null || texts === void 0 ? void 0 : texts.draggerDesc) || 'Click or Drag image to this area to upload'
81
+ })]
82
+ })
83
+ }), previewImage && /*#__PURE__*/_jsxs(Center, {
84
+ gap: 8,
85
+ style: {
86
+ position: 'relative'
87
+ },
88
+ width: '100%',
89
+ children: [/*#__PURE__*/_jsx(Flexbox, {
90
+ className: styles.editor,
91
+ children: /*#__PURE__*/_jsx(AvatarEditor, {
92
+ border: 0,
93
+ borderRadius: compressSize / 2,
94
+ height: compressSize,
95
+ image: previewImage,
96
+ ref: editor,
97
+ width: compressSize
98
+ })
99
+ }), /*#__PURE__*/_jsxs(Flexbox, {
100
+ gap: 8,
101
+ horizontal: true,
102
+ style: {
103
+ position: 'relative'
104
+ },
105
+ width: '100%',
106
+ children: [/*#__PURE__*/_jsx(Button, {
107
+ icon: /*#__PURE__*/_jsx(Icon, {
108
+ icon: ChevronLeftIcon
109
+ }),
110
+ onClick: function onClick() {
111
+ return setPreviewImage('');
112
+ },
113
+ style: {
114
+ flex: 'none'
115
+ }
116
+ }), /*#__PURE__*/_jsx(Button, {
117
+ onClick: handleUpload,
118
+ style: {
119
+ flex: 1,
120
+ fontWeight: 500
121
+ },
122
+ type: 'primary',
123
+ children: (texts === null || texts === void 0 ? void 0 : texts.uploadBtn) || 'Crop and Upload'
124
+ })]
125
+ })]
126
+ })]
127
+ });
128
+ });
129
+ export default AvatarUploader;
@@ -1,9 +1,46 @@
1
- /// <reference types="react" />
2
- export interface EmojiPickerProps {
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ import { CenterProps } from 'react-layout-kit';
3
+ import { type AvatarUploaderProps } from './AvatarUploader';
4
+ export interface CustomEmoji {
5
+ emojis: [
6
+ {
7
+ id: string;
8
+ keywords?: string[];
9
+ name: string;
10
+ skins: {
11
+ src: string;
12
+ }[];
13
+ }
14
+ ];
15
+ id: string;
16
+ name: string;
17
+ }
18
+ export interface CustomTab {
19
+ label: ReactNode;
20
+ render: (handleAvatarChange: (avatar: string) => void) => ReactNode;
21
+ value: string;
22
+ }
23
+ export interface EmojiPickerProps extends Omit<CenterProps, 'onChange'> {
24
+ allowDelete?: boolean;
25
+ allowUpload?: boolean;
3
26
  backgroundColor?: string;
27
+ compressSize?: number;
28
+ customEmojis?: CustomEmoji[];
29
+ customTabs?: CustomTab[];
4
30
  defaultAvatar?: string;
31
+ loading?: boolean;
5
32
  locale?: string;
6
33
  onChange?: (emoji: string) => void;
34
+ onDelete?: () => void;
35
+ onUpload?: AvatarUploaderProps['onUpload'];
36
+ popupClassName?: string;
37
+ popupStyle?: CSSProperties;
38
+ size?: number;
39
+ texts?: AvatarUploaderProps['texts'] & {
40
+ delete?: string;
41
+ emoji?: string;
42
+ upload?: string;
43
+ };
7
44
  value?: string;
8
45
  }
9
46
  declare const EmojiPicker: import("react").NamedExoticComponent<EmojiPickerProps>;
@@ -1,32 +1,74 @@
1
1
  'use client';
2
2
 
3
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
4
+ import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
3
5
  import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
4
6
  import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
5
7
  import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
8
+ import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
9
+ var _excluded = ["value", "defaultAvatar", "backgroundColor", "onChange", "locale", "allowUpload", "allowDelete", "texts", "onDelete", "compressSize", "customEmojis", "loading", "size", "onClick", "onUpload", "className", "customTabs", "popupClassName", "popupStyle"];
6
10
  import data from '@emoji-mart/data';
7
11
  import Picker from '@emoji-mart/react';
8
12
  import { Popover } from 'antd';
9
- import { memo, useState } from 'react';
13
+ import { Loader2Icon, SmileIcon, TrashIcon, UploadIcon } from 'lucide-react';
14
+ import { memo, useEffect, useRef, useState } from 'react';
15
+ import { Center, Flexbox } from 'react-layout-kit';
10
16
  import useSWR from 'swr';
11
17
  import useMergeState from 'use-merge-value';
18
+ import ActionIcon from "../ActionIcon";
12
19
  import Avatar from "../Avatar";
20
+ import Icon from "../Icon";
21
+ import TabsNav from "../TabsNav";
22
+ import Tooltip from "../Tooltip";
23
+ import AvatarUploader from "./AvatarUploader";
13
24
  import { useStyles } from "./style";
14
25
  import { jsx as _jsx } from "react/jsx-runtime";
26
+ import { jsxs as _jsxs } from "react/jsx-runtime";
27
+ var DEFAULT_AVATAR = '🤖';
15
28
  var EmojiPicker = /*#__PURE__*/memo(function (_ref) {
16
29
  var value = _ref.value,
17
30
  _ref$defaultAvatar = _ref.defaultAvatar,
18
- defaultAvatar = _ref$defaultAvatar === void 0 ? '🤖' : _ref$defaultAvatar,
31
+ defaultAvatar = _ref$defaultAvatar === void 0 ? DEFAULT_AVATAR : _ref$defaultAvatar,
19
32
  _ref$backgroundColor = _ref.backgroundColor,
20
33
  backgroundColor = _ref$backgroundColor === void 0 ? 'rgba(0,0,0,0)' : _ref$backgroundColor,
21
34
  onChange = _ref.onChange,
22
35
  _ref$locale = _ref.locale,
23
- locale = _ref$locale === void 0 ? 'en-US' : _ref$locale;
36
+ locale = _ref$locale === void 0 ? 'en-US' : _ref$locale,
37
+ allowUpload = _ref.allowUpload,
38
+ allowDelete = _ref.allowDelete,
39
+ texts = _ref.texts,
40
+ onDelete = _ref.onDelete,
41
+ _ref$compressSize = _ref.compressSize,
42
+ compressSize = _ref$compressSize === void 0 ? 256 : _ref$compressSize,
43
+ customEmojis = _ref.customEmojis,
44
+ loading = _ref.loading,
45
+ _ref$size = _ref.size,
46
+ size = _ref$size === void 0 ? 44 : _ref$size,
47
+ _onClick = _ref.onClick,
48
+ onUpload = _ref.onUpload,
49
+ className = _ref.className,
50
+ _ref$customTabs = _ref.customTabs,
51
+ customTabs = _ref$customTabs === void 0 ? [] : _ref$customTabs,
52
+ popupClassName = _ref.popupClassName,
53
+ popupStyle = _ref.popupStyle,
54
+ rest = _objectWithoutProperties(_ref, _excluded);
55
+ var ref = useRef(null);
24
56
  var _useState = useState(false),
25
57
  _useState2 = _slicedToArray(_useState, 2),
26
- open = _useState2[0],
27
- setOpen = _useState2[1];
58
+ active = _useState2[0],
59
+ setActive = _useState2[1];
60
+ var _useState3 = useState('emoji'),
61
+ _useState4 = _slicedToArray(_useState3, 2),
62
+ tab = _useState4[0],
63
+ setTab = _useState4[1];
64
+ var _useState5 = useState(false),
65
+ _useState6 = _slicedToArray(_useState5, 2),
66
+ open = _useState6[0],
67
+ setOpen = _useState6[1];
28
68
  var _useStyles = useStyles(),
29
- styles = _useStyles.styles;
69
+ cx = _useStyles.cx,
70
+ styles = _useStyles.styles,
71
+ theme = _useStyles.theme;
30
72
  var _useSWR = useSWR(locale, /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
31
73
  return _regeneratorRuntime().wrap(function _callee$(_context) {
32
74
  while (1) switch (_context.prev = _context.next) {
@@ -44,7 +86,7 @@ var EmojiPicker = /*#__PURE__*/memo(function (_ref) {
44
86
  revalidateOnFocus: false
45
87
  }),
46
88
  i18n = _useSWR.data;
47
- var _useMergeState = useMergeState('🤖', {
89
+ var _useMergeState = useMergeState(defaultAvatar, {
48
90
  defaultValue: defaultAvatar,
49
91
  onChange: onChange,
50
92
  value: value
@@ -52,43 +94,156 @@ var EmojiPicker = /*#__PURE__*/memo(function (_ref) {
52
94
  _useMergeState2 = _slicedToArray(_useMergeState, 2),
53
95
  ava = _useMergeState2[0],
54
96
  setAva = _useMergeState2[1];
97
+ var handleClickOutside = function handleClickOutside(e) {
98
+ if (!ref.current) return;
99
+ if (open && !active && e.target !== ref.current) {
100
+ setOpen(false);
101
+ }
102
+ };
103
+ var handleAvatarChange = function handleAvatarChange(emoji) {
104
+ setAva(emoji);
105
+ setOpen(false);
106
+ };
107
+ useEffect(function () {
108
+ document.addEventListener('click', handleClickOutside);
109
+ return function () {
110
+ return document.removeEventListener('click', handleClickOutside);
111
+ };
112
+ }, [active, open]);
113
+ var items = [{
114
+ key: 'emoji',
115
+ label: /*#__PURE__*/_jsx(Tooltip, {
116
+ title: (texts === null || texts === void 0 ? void 0 : texts.emoji) || 'Emoji',
117
+ children: /*#__PURE__*/_jsx(Icon, {
118
+ icon: SmileIcon,
119
+ size: {
120
+ fontSize: 20,
121
+ strokeWidth: 2.5
122
+ }
123
+ })
124
+ })
125
+ }, allowUpload && {
126
+ key: 'upload',
127
+ label: /*#__PURE__*/_jsx(Tooltip, {
128
+ title: (texts === null || texts === void 0 ? void 0 : texts.upload) || 'Upload',
129
+ children: /*#__PURE__*/_jsx(Icon, {
130
+ icon: UploadIcon,
131
+ size: {
132
+ fontSize: 20,
133
+ strokeWidth: 2.5
134
+ }
135
+ })
136
+ })
137
+ }].concat(_toConsumableArray(customTabs.map(function (tab) {
138
+ return {
139
+ key: tab.value,
140
+ label: tab.label
141
+ };
142
+ }))).filter(Boolean);
143
+ var showTabs = items && items.length > 1;
55
144
  return /*#__PURE__*/_jsx(Popover, {
56
145
  arrow: false,
57
- content: /*#__PURE__*/_jsx("div", {
58
- className: styles.picker,
59
- onBlur: function onBlur() {
60
- return setOpen(false);
146
+ content: /*#__PURE__*/_jsxs(Flexbox, {
147
+ className: cx(styles.picker, popupClassName),
148
+ onMouseEnter: function onMouseEnter() {
149
+ return setActive(true);
61
150
  },
62
- children: /*#__PURE__*/_jsx(Picker, {
151
+ onMouseLeave: function onMouseLeave() {
152
+ return setActive(false);
153
+ },
154
+ ref: ref,
155
+ style: _objectSpread({
156
+ minWidth: 310,
157
+ paddingTop: showTabs ? 4 : 0
158
+ }, popupStyle),
159
+ children: [showTabs && /*#__PURE__*/_jsxs(Flexbox, {
160
+ align: 'center',
161
+ horizontal: true,
162
+ justify: 'space-between',
163
+ paddingInline: 10,
164
+ children: [/*#__PURE__*/_jsx(TabsNav, {
165
+ activeKey: tab,
166
+ items: items,
167
+ onChange: function onChange(key) {
168
+ return setTab(key);
169
+ },
170
+ variant: 'compact'
171
+ }), allowDelete && /*#__PURE__*/_jsx(ActionIcon, {
172
+ icon: TrashIcon,
173
+ onClick: function onClick() {
174
+ handleAvatarChange(defaultAvatar);
175
+ onDelete === null || onDelete === void 0 || onDelete();
176
+ },
177
+ size: {
178
+ fontSize: 20,
179
+ strokeWidth: 2.5
180
+ },
181
+ title: (texts === null || texts === void 0 ? void 0 : texts.delete) || 'Delete'
182
+ })]
183
+ }), tab === 'emoji' && /*#__PURE__*/_jsx(Picker, {
184
+ custom: customEmojis,
63
185
  data: data,
64
186
  i18n: i18n,
187
+ icons: 'outline',
65
188
  locale: locale.split('-')[0],
189
+ navPosition: showTabs ? 'bottom' : 'top',
66
190
  onEmojiSelect: function onEmojiSelect(e) {
67
- setAva(e.native);
68
- setOpen(false);
191
+ return handleAvatarChange(e.src || e.native);
69
192
  },
193
+ previewPosition: 'none',
70
194
  skinTonePosition: 'none',
71
- theme: 'auto'
72
- })
195
+ theme: theme.isDarkMode ? 'dark' : 'light'
196
+ }), tab === 'upload' && /*#__PURE__*/_jsx(AvatarUploader, {
197
+ compressSize: compressSize,
198
+ onChange: handleAvatarChange,
199
+ onUpload: onUpload,
200
+ texts: texts
201
+ }), customTabs.map(function (item) {
202
+ return tab === item.value && /*#__PURE__*/_jsx(Flexbox, {
203
+ padding: 10,
204
+ children: item.render(handleAvatarChange)
205
+ }, item.value);
206
+ })]
73
207
  }),
208
+ destroyTooltipOnHide: true,
74
209
  open: open,
75
- placement: 'left',
210
+ placement: 'bottomLeft',
76
211
  rootClassName: styles.popover,
77
212
  trigger: ['click'],
78
- children: /*#__PURE__*/_jsx("div", {
79
- className: styles.avatar,
80
- onClick: function onClick() {
81
- return setOpen(true);
213
+ children: /*#__PURE__*/_jsxs(Center, _objectSpread(_objectSpread({
214
+ className: cx(styles.avatar, className),
215
+ flex: 'none',
216
+ height: size,
217
+ onClick: function onClick(e) {
218
+ if (loading) return;
219
+ setOpen(!open);
220
+ _onClick === null || _onClick === void 0 || _onClick(e);
221
+ },
222
+ onMouseEnter: function onMouseEnter() {
223
+ return setActive(true);
82
224
  },
83
- style: {
84
- width: 'fit-content'
225
+ onMouseLeave: function onMouseLeave() {
226
+ return setActive(false);
85
227
  },
86
- children: /*#__PURE__*/_jsx(Avatar, {
228
+ width: size
229
+ }, rest), {}, {
230
+ children: [loading && /*#__PURE__*/_jsx(Center, {
231
+ className: styles.loading,
232
+ height: '100%',
233
+ width: '100%',
234
+ children: /*#__PURE__*/_jsx(Icon, {
235
+ icon: Loader2Icon,
236
+ size: {
237
+ fontSize: size / 2
238
+ },
239
+ spin: true
240
+ })
241
+ }), /*#__PURE__*/_jsx(Avatar, {
87
242
  avatar: ava,
88
243
  background: backgroundColor,
89
- size: 44
90
- })
91
- })
244
+ size: size
245
+ })]
246
+ }))
92
247
  });
93
248
  });
94
249
  export default EmojiPicker;
@@ -1,5 +1,7 @@
1
1
  export declare const useStyles: (props?: unknown) => import("antd-style").ReturnStyles<{
2
2
  avatar: import("antd-style").SerializedStyles;
3
+ editor: import("antd-style").SerializedStyles;
4
+ loading: import("antd-style").SerializedStyles;
3
5
  picker: import("antd-style").SerializedStyles;
4
6
  popover: import("antd-style").SerializedStyles;
5
7
  }>;
@@ -1,5 +1,5 @@
1
1
  import _taggedTemplateLiteral from "@babel/runtime/helpers/esm/taggedTemplateLiteral";
2
- var _templateObject, _templateObject2, _templateObject3;
2
+ var _templateObject, _templateObject2, _templateObject3, _templateObject4, _templateObject5;
3
3
  import { createStyles } from 'antd-style';
4
4
  import chroma from 'chroma-js';
5
5
  export var useStyles = createStyles(function (_ref) {
@@ -7,8 +7,10 @@ export var useStyles = createStyles(function (_ref) {
7
7
  token = _ref.token,
8
8
  prefixCls = _ref.prefixCls;
9
9
  return {
10
- avatar: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n border-radius: 50%;\n transition:\n scale 400ms ", ",\n box-shadow 100ms ", ";\n\n &:hover {\n box-shadow: 0 0 0 3px ", ";\n }\n\n &:active {\n scale: 0.8;\n }\n "])), token.motionEaseOut, token.motionEaseOut, token.colorText),
11
- picker: css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n em-emoji-picker {\n --rgb-accent: ", ";\n --shadow: none;\n }\n "])), chroma(token.colorPrimary).rgb().join(',')),
12
- popover: css(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["\n .", "-popover-inner {\n padding: 0;\n }\n "])), prefixCls)
10
+ avatar: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n position: relative;\n border-radius: 50%;\n transition: box-shadow 100ms ", ";\n\n &:hover {\n box-shadow: 0 0 0 3px ", ";\n }\n "])), token.motionEaseOut, token.colorText),
11
+ editor: css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n overflow: hidden;\n border: 1px solid ", ";\n border-radius: ", "px;\n "])), token.colorBorder, token.borderRadiusLG),
12
+ loading: css(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["\n position: absolute;\n z-index: 1;\n inset: 0;\n\n background: ", ";\n border-radius: 50%;\n "])), token.colorBgMask),
13
+ picker: css(_templateObject4 || (_templateObject4 = _taggedTemplateLiteral(["\n position: relative;\n\n em-emoji-picker {\n --rgb-accent: ", ";\n --shadow: none;\n --rgb-background: none;\n --border-radius: 0;\n }\n "])), chroma(token.colorPrimary).rgb().join(',')),
14
+ popover: css(_templateObject5 || (_templateObject5 = _taggedTemplateLiteral(["\n .", "-popover-inner {\n padding: 0;\n }\n "])), prefixCls)
13
15
  };
14
16
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/ui",
3
- "version": "1.149.3",
3
+ "version": "1.150.0",
4
4
  "description": "Lobe UI is an open-source UI component library for building AIGC web apps",
5
5
  "keywords": [
6
6
  "lobehub",
@@ -103,6 +103,7 @@
103
103
  "query-string": "^9.1.0",
104
104
  "rc-footer": "^0.6.8",
105
105
  "re-resizable": "^6.9.17",
106
+ "react-avatar-editor": "^13.0.2",
106
107
  "react-error-boundary": "^4.0.13",
107
108
  "react-layout-kit": "^1.9.0",
108
109
  "react-markdown": "^8.0.7",
@@ -135,6 +136,7 @@
135
136
  "@types/pangu": "^4.0.2",
136
137
  "@types/query-string": "^6.3.0",
137
138
  "@types/react": "18.2.40",
139
+ "@types/react-avatar-editor": "^13.0.2",
138
140
  "@types/react-dom": "^18.3.0",
139
141
  "@types/uuid": "^10.0.0",
140
142
  "@vitest/coverage-v8": "~1.2.2",