cozy-ui 130.8.1 → 130.10.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.
- package/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/react/CozyDialogs/SpecificDialogs/Readme.md +26 -0
- package/react/CozyDialogs/SpecificDialogs/ShortcutDialog.jsx +120 -0
- package/react/CozyDialogs/SpecificDialogs/helpers/shortcuts.js +61 -0
- package/react/CozyDialogs/SpecificDialogs/index.jsx +1 -0
- package/react/CozyDialogs/SpecificDialogs/locales/en.json +14 -0
- package/react/CozyDialogs/SpecificDialogs/locales/fr.json +14 -0
- package/react/CozyDialogs/SpecificDialogs/locales/ru.json +15 -1
- package/react/CozyDialogs/SpecificDialogs/locales/vi.json +15 -1
- package/react/CozyDialogs/index.jsx +2 -1
- package/react/EditBadge/EditMenu.jsx +102 -0
- package/react/EditBadge/Readme.md +22 -0
- package/react/EditBadge/StatusWrapper.jsx +32 -0
- package/react/EditBadge/helpers.js +102 -0
- package/react/EditBadge/index.jsx +81 -0
- package/react/EditBadge/locales/en.json +18 -0
- package/react/EditBadge/locales/fr.json +18 -0
- package/react/EditBadge/locales/index.js +11 -0
- package/react/EditBadge/locales/ru.json +18 -0
- package/react/EditBadge/locales/vi.json +18 -0
- package/react/providers/DemoProvider.jsx +6 -3
- package/react/utils/react.js +14 -1
- package/transpiled/react/CozyDialogs/SpecificDialogs/ShortcutDialog.d.ts +2 -0
- package/transpiled/react/CozyDialogs/SpecificDialogs/ShortcutDialog.js +114 -0
- package/transpiled/react/CozyDialogs/SpecificDialogs/helpers/shortcuts.d.ts +13 -0
- package/transpiled/react/CozyDialogs/SpecificDialogs/helpers/shortcuts.js +83 -0
- package/transpiled/react/CozyDialogs/SpecificDialogs/index.d.ts +1 -0
- package/transpiled/react/CozyDialogs/SpecificDialogs/index.js +2 -1
- package/transpiled/react/CozyDialogs/SpecificDialogs/withSpecificDialogsLocales.js +56 -0
- package/transpiled/react/CozyDialogs/index.d.ts +1 -1
- package/transpiled/react/CozyDialogs/index.js +1 -1
- package/transpiled/react/EditBadge/EditMenu.d.ts +11 -0
- package/transpiled/react/EditBadge/EditMenu.js +93 -0
- package/transpiled/react/EditBadge/StatusWrapper.d.ts +9 -0
- package/transpiled/react/EditBadge/StatusWrapper.js +44 -0
- package/transpiled/react/EditBadge/helpers.d.ts +20 -0
- package/transpiled/react/EditBadge/helpers.js +153 -0
- package/transpiled/react/EditBadge/index.d.ts +23 -0
- package/transpiled/react/EditBadge/index.js +89 -0
- package/transpiled/react/EditBadge/locales/index.d.ts +10 -0
- package/transpiled/react/EditBadge/locales/index.js +78 -0
- package/transpiled/react/providers/DemoProvider.js +3 -2
- package/transpiled/react/utils/react.d.ts +1 -0
- package/transpiled/react/utils/react.js +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# [130.10.0](https://github.com/cozy/cozy-ui/compare/v130.9.0...v130.10.0) (2025-10-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Add EditBadge component ([47e98d2](https://github.com/cozy/cozy-ui/commit/47e98d2))
|
|
7
|
+
* **EditBadge:** We can now pass props to the main Badge component ([98eb169](https://github.com/cozy/cozy-ui/commit/98eb169))
|
|
8
|
+
* **Utils/React:** Add `AddPropsToChildren` function ([1724619](https://github.com/cozy/cozy-ui/commit/1724619))
|
|
9
|
+
|
|
10
|
+
# [130.9.0](https://github.com/cozy/cozy-ui/compare/v130.8.1...v130.9.0) (2025-10-16)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* Add AlertProvider to DemoProvider ([1fea901](https://github.com/cozy/cozy-ui/commit/1fea901))
|
|
16
|
+
* Add ShortcutDialog ([8c501eb](https://github.com/cozy/cozy-ui/commit/8c501eb))
|
|
17
|
+
* Remove useless forwardRef ([7b5cf9b](https://github.com/cozy/cozy-ui/commit/7b5cf9b))
|
|
18
|
+
|
|
1
19
|
## [130.8.1](https://github.com/cozy/cozy-ui/compare/v130.8.0...v130.8.1) (2025-10-15)
|
|
2
20
|
|
|
3
21
|
|
package/package.json
CHANGED
|
@@ -102,3 +102,29 @@ const onAuthentification = () => {
|
|
|
102
102
|
)}
|
|
103
103
|
</Variants>
|
|
104
104
|
```
|
|
105
|
+
|
|
106
|
+
### ShortcutDialog dialog
|
|
107
|
+
|
|
108
|
+
⚠️ Must be used within AlertProvider.
|
|
109
|
+
|
|
110
|
+
```jsx
|
|
111
|
+
import { useState } from 'react'
|
|
112
|
+
|
|
113
|
+
import DemoProvider from 'cozy-ui/docs/components/DemoProvider'
|
|
114
|
+
import { ShortcutDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
|
|
115
|
+
import Buttons from 'cozy-ui/transpiled/react/Buttons'
|
|
116
|
+
|
|
117
|
+
const [open, setOpen] = useState(isTesting())
|
|
118
|
+
|
|
119
|
+
;
|
|
120
|
+
|
|
121
|
+
<DemoProvider>
|
|
122
|
+
{ open && (
|
|
123
|
+
<ShortcutDialog
|
|
124
|
+
onClose={() => { setOpen(false)}}
|
|
125
|
+
onSave={(...args) => { console.log('onSave called with args', args) }}
|
|
126
|
+
/>
|
|
127
|
+
)}
|
|
128
|
+
<Buttons onClick={() => { setOpen(true) }} label="Open ShortcutDialog" />
|
|
129
|
+
</DemoProvider>
|
|
130
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
checkAndSaveShortcut,
|
|
6
|
+
makeHumanReadableFileName
|
|
7
|
+
} from './helpers/shortcuts'
|
|
8
|
+
import withSpecificDialogsLocales from './withSpecificDialogsLocales'
|
|
9
|
+
import { ConfirmDialog } from '..'
|
|
10
|
+
import Button from '../../Buttons'
|
|
11
|
+
import Stack from '../../Stack'
|
|
12
|
+
import TextField from '../../TextField'
|
|
13
|
+
import { useAlert } from '../../providers/Alert'
|
|
14
|
+
import { useI18n } from '../../providers/I18n'
|
|
15
|
+
|
|
16
|
+
const ENTER_KEY = 13
|
|
17
|
+
|
|
18
|
+
const ShortcutDialog = ({ shortcut, onSave, onClose }) => {
|
|
19
|
+
const { t } = useI18n()
|
|
20
|
+
const { showAlert } = useAlert()
|
|
21
|
+
|
|
22
|
+
const initialName = makeHumanReadableFileName(shortcut?.name || '')
|
|
23
|
+
const initialUrl = shortcut?.url || ''
|
|
24
|
+
const isEditing = !!shortcut
|
|
25
|
+
|
|
26
|
+
const [fileName, setFilename] = useState(initialName)
|
|
27
|
+
const [url, setUrl] = useState(initialUrl)
|
|
28
|
+
|
|
29
|
+
const saveShortcut = () => {
|
|
30
|
+
checkAndSaveShortcut({
|
|
31
|
+
fileName,
|
|
32
|
+
url,
|
|
33
|
+
isEditing,
|
|
34
|
+
onSave,
|
|
35
|
+
onClose,
|
|
36
|
+
showAlert,
|
|
37
|
+
t
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleKeyDown = e => {
|
|
42
|
+
if (e.keyCode === ENTER_KEY) {
|
|
43
|
+
saveShortcut()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<ConfirmDialog
|
|
49
|
+
open={true}
|
|
50
|
+
title={
|
|
51
|
+
isEditing
|
|
52
|
+
? t('shortcut-dialog.edit-title')
|
|
53
|
+
: t('shortcut-dialog.create-title')
|
|
54
|
+
}
|
|
55
|
+
onClose={onClose}
|
|
56
|
+
content={
|
|
57
|
+
<Stack>
|
|
58
|
+
<div>
|
|
59
|
+
<TextField
|
|
60
|
+
label={t('shortcut-dialog.url')}
|
|
61
|
+
value={url}
|
|
62
|
+
id="shortcuturl"
|
|
63
|
+
variant="outlined"
|
|
64
|
+
onChange={e => setUrl(e.target.value)}
|
|
65
|
+
onKeyDown={e => handleKeyDown(e)}
|
|
66
|
+
autoComplete="off"
|
|
67
|
+
fullWidth
|
|
68
|
+
margin="normal"
|
|
69
|
+
autoFocus
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
<TextField
|
|
74
|
+
label={t('shortcut-dialog.filename')}
|
|
75
|
+
value={fileName}
|
|
76
|
+
id="shortcutfilename"
|
|
77
|
+
variant="outlined"
|
|
78
|
+
onChange={e => setFilename(e.target.value)}
|
|
79
|
+
onKeyDown={e => handleKeyDown(e)}
|
|
80
|
+
autoComplete="off"
|
|
81
|
+
fullWidth
|
|
82
|
+
margin="normal"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</Stack>
|
|
86
|
+
}
|
|
87
|
+
actions={
|
|
88
|
+
<>
|
|
89
|
+
<Button
|
|
90
|
+
variant="secondary"
|
|
91
|
+
onClick={onClose}
|
|
92
|
+
label={t('shortcut-dialog.cancel')}
|
|
93
|
+
/>
|
|
94
|
+
<Button
|
|
95
|
+
variant="primary"
|
|
96
|
+
label={
|
|
97
|
+
isEditing
|
|
98
|
+
? t('shortcut-dialog.edit')
|
|
99
|
+
: t('shortcut-dialog.create')
|
|
100
|
+
}
|
|
101
|
+
onClick={saveShortcut}
|
|
102
|
+
/>
|
|
103
|
+
</>
|
|
104
|
+
}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
ShortcutDialog.propTypes = {
|
|
110
|
+
/** An io.cozy.files.shortcut object if we want to prefill fields */
|
|
111
|
+
shortcut: PropTypes.object,
|
|
112
|
+
/** A function called when clicking the save button with filename and url */
|
|
113
|
+
onSave: PropTypes.func,
|
|
114
|
+
/** A function called when clicking the close button */
|
|
115
|
+
onClose: PropTypes.func
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ShortcutDialog.displayName = 'ShortcutDialog'
|
|
119
|
+
|
|
120
|
+
export default withSpecificDialogsLocales(ShortcutDialog)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const isValidURL = url => {
|
|
2
|
+
try {
|
|
3
|
+
new URL(url)
|
|
4
|
+
return true
|
|
5
|
+
} catch (e) {
|
|
6
|
+
return false
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const makeValidUrl = str => {
|
|
11
|
+
if (isValidURL(str)) return str
|
|
12
|
+
else if (isValidURL(`https://${str}`)) return `https://${str}`
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const makeValidFileName = fileName =>
|
|
17
|
+
fileName.endsWith('.url') ? fileName : fileName + '.url'
|
|
18
|
+
|
|
19
|
+
export const makeHumanReadableFileName = fileName =>
|
|
20
|
+
fileName.endsWith('.url') ? fileName.slice(0, -4) : fileName
|
|
21
|
+
|
|
22
|
+
export const checkAndSaveShortcut = async ({
|
|
23
|
+
fileName,
|
|
24
|
+
url,
|
|
25
|
+
isEditing,
|
|
26
|
+
onSave,
|
|
27
|
+
onClose,
|
|
28
|
+
showAlert,
|
|
29
|
+
t
|
|
30
|
+
}) => {
|
|
31
|
+
if (!fileName || !url) {
|
|
32
|
+
showAlert({ message: t('shortcut-dialog.needs-info'), severity: 'error' })
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const validFileName = makeValidFileName(fileName)
|
|
37
|
+
|
|
38
|
+
const validURL = makeValidUrl(url)
|
|
39
|
+
|
|
40
|
+
if (!validURL) {
|
|
41
|
+
showAlert({
|
|
42
|
+
message: t('shortcut-dialog.url-bad-format'),
|
|
43
|
+
severity: 'error'
|
|
44
|
+
})
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
onSave(validFileName, validURL)
|
|
49
|
+
|
|
50
|
+
showAlert({
|
|
51
|
+
message: isEditing
|
|
52
|
+
? t('shortcut-dialog.edited')
|
|
53
|
+
: t('shortcut-dialog.created'),
|
|
54
|
+
severity: 'success'
|
|
55
|
+
})
|
|
56
|
+
} catch {
|
|
57
|
+
showAlert({ message: t('shortcut-dialog.errored'), severity: 'error' })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onClose()
|
|
61
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default as AllowLocationDialog } from './AllowLocationDialog'
|
|
2
2
|
export { default as InstallFlagshipAppDialog } from './InstallFlagshipAppDialog'
|
|
3
3
|
export { default as AuthentificationDialog } from './AuthentificationDialog'
|
|
4
|
+
export { default as ShortcutDialog } from './ShortcutDialog'
|
|
@@ -23,5 +23,19 @@
|
|
|
23
23
|
"invalid_password": "Incorrect password, try again.",
|
|
24
24
|
"server_error": "Something went wrong with the server. Please, reload the page."
|
|
25
25
|
}
|
|
26
|
+
},
|
|
27
|
+
"shortcut-dialog": {
|
|
28
|
+
"create-title": "Create a shortcut",
|
|
29
|
+
"edit-title": "Modify a shortcut",
|
|
30
|
+
"filename": "Shortcut name",
|
|
31
|
+
"url": "URL",
|
|
32
|
+
"cancel": "Cancel",
|
|
33
|
+
"create": "Create",
|
|
34
|
+
"edit": "Edit",
|
|
35
|
+
"created": "The shortcut has been created",
|
|
36
|
+
"edited": "The shortcut has been modified",
|
|
37
|
+
"errored": "An error occurred",
|
|
38
|
+
"needs-info": "A shortcut needs a name and a URL",
|
|
39
|
+
"url-bad-format": "The entered URL is not in the correct format"
|
|
26
40
|
}
|
|
27
41
|
}
|
|
@@ -23,5 +23,19 @@
|
|
|
23
23
|
"invalid_password": "Mot de passe incorrect, essayer à nouveau.",
|
|
24
24
|
"server_error": "Une erreur s'est produite. Merci de recharger la page."
|
|
25
25
|
}
|
|
26
|
+
},
|
|
27
|
+
"shortcut-dialog": {
|
|
28
|
+
"create-title": "Créer un raccourci",
|
|
29
|
+
"edit-title": "Modifier un raccourci",
|
|
30
|
+
"filename": "Nom du raccourci",
|
|
31
|
+
"url": "URL",
|
|
32
|
+
"cancel": "Annuler",
|
|
33
|
+
"create": "Créer",
|
|
34
|
+
"edit": "Modifier",
|
|
35
|
+
"created": "Le raccourci a été créé",
|
|
36
|
+
"edited": "Le raccourci a été modifié",
|
|
37
|
+
"errored": "Une erreur s'est produite",
|
|
38
|
+
"needs-info": "Un raccourci a besoin d'un nom et d'une URL",
|
|
39
|
+
"url-bad-format": "L'URL saisie n'est pas dans le bon format"
|
|
26
40
|
}
|
|
27
41
|
}
|
|
@@ -23,5 +23,19 @@
|
|
|
23
23
|
"invalid_password": "Неверный пароль, попробуйте еще раз.",
|
|
24
24
|
"server_error": "Что-то пошло не так с сервером. Пожалуйста, перезагрузите страницу."
|
|
25
25
|
}
|
|
26
|
+
},
|
|
27
|
+
"shortcut-dialog": {
|
|
28
|
+
"create-title": "Создать ярлык",
|
|
29
|
+
"edit-title": "Изменить ярлык",
|
|
30
|
+
"filename": "Имя ярлыка",
|
|
31
|
+
"url": "URL",
|
|
32
|
+
"cancel": "Отменить",
|
|
33
|
+
"create": "Создать",
|
|
34
|
+
"edit": "Изменить",
|
|
35
|
+
"created": "Ярлык был создан",
|
|
36
|
+
"edited": "Ярлык был изменен",
|
|
37
|
+
"errored": "Произошла ошибка",
|
|
38
|
+
"needs-info": "Для ярлыка нужны имя и URL",
|
|
39
|
+
"url-bad-format": "Введённый URL имеет неверный формат"
|
|
26
40
|
}
|
|
27
|
-
}
|
|
41
|
+
}
|
|
@@ -23,5 +23,19 @@
|
|
|
23
23
|
"invalid_password": "Mật khẩu không chính xác, hãy thử lại.",
|
|
24
24
|
"server_error": "Có lỗi xảy ra với máy chủ. Vui lòng tải lại trang."
|
|
25
25
|
}
|
|
26
|
+
},
|
|
27
|
+
"shortcut-dialog": {
|
|
28
|
+
"create-title": "Tạo lối tắt",
|
|
29
|
+
"edit-title": "Chỉnh sửa lối tắt",
|
|
30
|
+
"filename": "Tên lối tắt",
|
|
31
|
+
"url": "URL",
|
|
32
|
+
"cancel": "Hủy",
|
|
33
|
+
"create": "Tạo",
|
|
34
|
+
"edit": "Chỉnh sửa",
|
|
35
|
+
"created": "Lối tắt đã được tạo",
|
|
36
|
+
"edited": "Lối tắt đã được chỉnh sửa",
|
|
37
|
+
"errored": "Đã xảy ra lỗi",
|
|
38
|
+
"needs-info": "Lối tắt cần có tên và URL",
|
|
39
|
+
"url-bad-format": "URL đã nhập không đúng định dạng"
|
|
26
40
|
}
|
|
27
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, { useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { handleDelete, handleUpload } from './helpers'
|
|
4
|
+
import { locales } from './locales'
|
|
5
|
+
import Icon from '../Icon'
|
|
6
|
+
import CameraIcon from '../Icons/Camera'
|
|
7
|
+
import TrashIcon from '../Icons/Trash'
|
|
8
|
+
import ListItemIcon from '../ListItemIcon'
|
|
9
|
+
import ListItemText from '../ListItemText'
|
|
10
|
+
import Menu from '../Menu'
|
|
11
|
+
import MenuItem from '../MenuItem'
|
|
12
|
+
import { useAlert } from '../providers/Alert'
|
|
13
|
+
import { useI18n, useExtendI18n } from '../providers/I18n'
|
|
14
|
+
|
|
15
|
+
const EditMenu = ({
|
|
16
|
+
anchorRef,
|
|
17
|
+
status,
|
|
18
|
+
showMenu,
|
|
19
|
+
setShowMenu,
|
|
20
|
+
setStatus,
|
|
21
|
+
setTimestamp,
|
|
22
|
+
onUpload,
|
|
23
|
+
onDelete
|
|
24
|
+
}) => {
|
|
25
|
+
useExtendI18n(locales)
|
|
26
|
+
const { t } = useI18n()
|
|
27
|
+
const { showAlert } = useAlert()
|
|
28
|
+
|
|
29
|
+
const fileInputRef = useRef(null)
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<input
|
|
34
|
+
className="u-dn"
|
|
35
|
+
type="file"
|
|
36
|
+
ref={fileInputRef}
|
|
37
|
+
accept="image/*"
|
|
38
|
+
onChange={event =>
|
|
39
|
+
handleUpload({
|
|
40
|
+
event,
|
|
41
|
+
t,
|
|
42
|
+
fileInputRef,
|
|
43
|
+
status,
|
|
44
|
+
onUpload,
|
|
45
|
+
setStatus,
|
|
46
|
+
setTimestamp,
|
|
47
|
+
setShowMenu,
|
|
48
|
+
showAlert
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
/>
|
|
52
|
+
{showMenu && (
|
|
53
|
+
<Menu
|
|
54
|
+
open
|
|
55
|
+
anchorEl={anchorRef}
|
|
56
|
+
getContentAnchorEl={null}
|
|
57
|
+
anchorOrigin={{
|
|
58
|
+
vertical: 'bottom',
|
|
59
|
+
horizontal: 'left'
|
|
60
|
+
}}
|
|
61
|
+
transformOrigin={{
|
|
62
|
+
vertical: 'top',
|
|
63
|
+
horizontal: 'left'
|
|
64
|
+
}}
|
|
65
|
+
onClose={() => setShowMenu(false)}
|
|
66
|
+
>
|
|
67
|
+
<MenuItem
|
|
68
|
+
onClick={() => {
|
|
69
|
+
setShowMenu(false)
|
|
70
|
+
fileInputRef.current.click() // triggers onChange of the input
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<ListItemIcon>
|
|
74
|
+
<Icon icon={CameraIcon} />
|
|
75
|
+
</ListItemIcon>
|
|
76
|
+
<ListItemText primary={t('EditBadge.menu.update')} />
|
|
77
|
+
</MenuItem>
|
|
78
|
+
<MenuItem
|
|
79
|
+
onClick={() =>
|
|
80
|
+
handleDelete({
|
|
81
|
+
t,
|
|
82
|
+
status,
|
|
83
|
+
onDelete,
|
|
84
|
+
setShowMenu,
|
|
85
|
+
setStatus,
|
|
86
|
+
setTimestamp,
|
|
87
|
+
showAlert
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
>
|
|
91
|
+
<ListItemIcon>
|
|
92
|
+
<Icon icon={TrashIcon} />
|
|
93
|
+
</ListItemIcon>
|
|
94
|
+
<ListItemText primary={t('EditBadge.menu.delete')} />
|
|
95
|
+
</MenuItem>
|
|
96
|
+
</Menu>
|
|
97
|
+
)}
|
|
98
|
+
</>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default EditMenu
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
⚠️ Must be used within `AlertProvider`.
|
|
2
|
+
|
|
3
|
+
```jsx
|
|
4
|
+
import DemoProvider from 'cozy-ui/docs/components/DemoProvider'
|
|
5
|
+
import AlertProvider from 'cozy-ui/transpiled/react/providers/Alert'
|
|
6
|
+
import EditBadge from 'cozy-ui/transpiled/react/EditBadge'
|
|
7
|
+
import Avatar from 'cozy-ui/transpiled/react/Avatar'
|
|
8
|
+
|
|
9
|
+
;
|
|
10
|
+
|
|
11
|
+
<DemoProvider>
|
|
12
|
+
<AlertProvider>
|
|
13
|
+
<EditBadge
|
|
14
|
+
src={timestamp => ""}
|
|
15
|
+
onUpload={file => {}}
|
|
16
|
+
onDelete={() => {}}
|
|
17
|
+
>
|
|
18
|
+
<Avatar size={94} alt="Avatar" />
|
|
19
|
+
</EditBadge>
|
|
20
|
+
</AlertProvider>
|
|
21
|
+
</DemoProvider>
|
|
22
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import cx from 'classnames'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import Spinner from '../Spinner'
|
|
5
|
+
import { AddPropsToChildren } from '../utils/react'
|
|
6
|
+
|
|
7
|
+
const StatusWrapper = ({ src, status, setStatus, timestamp, children }) => {
|
|
8
|
+
if (status === 'LOADING') {
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
{AddPropsToChildren(children, childProps => ({
|
|
12
|
+
className: cx(childProps.className, 'u-o-50')
|
|
13
|
+
}))}
|
|
14
|
+
<Spinner className="u-m-0" middle size="large" />
|
|
15
|
+
</>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (status === 'PRESENT') {
|
|
20
|
+
return AddPropsToChildren(children, () => ({
|
|
21
|
+
key: timestamp,
|
|
22
|
+
src,
|
|
23
|
+
onError: () => setStatus('ABSENT')
|
|
24
|
+
}))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return AddPropsToChildren(children, () => ({
|
|
28
|
+
key: timestamp
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default StatusWrapper
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024
|
|
2
|
+
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
|
|
3
|
+
|
|
4
|
+
export const handleUpload = async ({
|
|
5
|
+
event,
|
|
6
|
+
t,
|
|
7
|
+
fileInputRef,
|
|
8
|
+
status,
|
|
9
|
+
onUpload,
|
|
10
|
+
setStatus,
|
|
11
|
+
setTimestamp,
|
|
12
|
+
setShowMenu,
|
|
13
|
+
showAlert
|
|
14
|
+
}) => {
|
|
15
|
+
const file = event.target.files[0]
|
|
16
|
+
if (!file) return
|
|
17
|
+
|
|
18
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
19
|
+
showAlert({
|
|
20
|
+
message: t('EditBadge.upload.file-size'),
|
|
21
|
+
severity: 'error'
|
|
22
|
+
})
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!ALLOWED_TYPES.includes(file.type)) {
|
|
27
|
+
showAlert({
|
|
28
|
+
message: t('EditBadge.upload.file-type'),
|
|
29
|
+
severity: 'error'
|
|
30
|
+
})
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const controller = new AbortController()
|
|
35
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000)
|
|
36
|
+
|
|
37
|
+
const previouStatus = status
|
|
38
|
+
setStatus('LOADING')
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await onUpload(file)
|
|
42
|
+
clearTimeout(timeoutId)
|
|
43
|
+
|
|
44
|
+
const newTimestamp = Date.now()
|
|
45
|
+
setStatus('PRESENT')
|
|
46
|
+
setTimestamp(newTimestamp)
|
|
47
|
+
showAlert({
|
|
48
|
+
message: t('EditBadge.upload.success'),
|
|
49
|
+
severity: 'success'
|
|
50
|
+
})
|
|
51
|
+
} catch (error) {
|
|
52
|
+
clearTimeout(timeoutId)
|
|
53
|
+
setStatus(previouStatus)
|
|
54
|
+
showAlert({
|
|
55
|
+
message: t('EditBadge.upload.error'),
|
|
56
|
+
severity: 'error'
|
|
57
|
+
})
|
|
58
|
+
} finally {
|
|
59
|
+
setShowMenu(false)
|
|
60
|
+
if (fileInputRef.current) {
|
|
61
|
+
fileInputRef.current.value = ''
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const handleDelete = async ({
|
|
67
|
+
t,
|
|
68
|
+
status,
|
|
69
|
+
onDelete,
|
|
70
|
+
setShowMenu,
|
|
71
|
+
setStatus,
|
|
72
|
+
setTimestamp,
|
|
73
|
+
showAlert
|
|
74
|
+
}) => {
|
|
75
|
+
setShowMenu(false)
|
|
76
|
+
|
|
77
|
+
const controller = new AbortController()
|
|
78
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000)
|
|
79
|
+
const previousStatus = status
|
|
80
|
+
setStatus('LOADING')
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await onDelete()
|
|
84
|
+
clearTimeout(timeoutId)
|
|
85
|
+
|
|
86
|
+
const checkTimestamp = Date.now()
|
|
87
|
+
|
|
88
|
+
setStatus('ABSENT')
|
|
89
|
+
setTimestamp(checkTimestamp)
|
|
90
|
+
showAlert({
|
|
91
|
+
message: t('EditBadge.delete.success'),
|
|
92
|
+
severity: 'success'
|
|
93
|
+
})
|
|
94
|
+
} catch (error) {
|
|
95
|
+
clearTimeout(timeoutId)
|
|
96
|
+
setStatus(previousStatus)
|
|
97
|
+
showAlert({
|
|
98
|
+
message: t('EditBadge.delete.error'),
|
|
99
|
+
severity: 'error'
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React, { useState, useRef } from 'react'
|
|
3
|
+
|
|
4
|
+
import EditMenu from './EditMenu'
|
|
5
|
+
import StatusWrapper from './StatusWrapper'
|
|
6
|
+
import Badge from '../Badge'
|
|
7
|
+
import Button from '../Buttons'
|
|
8
|
+
import Icon from '../Icon'
|
|
9
|
+
import PenIcon from '../Icons/Pen'
|
|
10
|
+
|
|
11
|
+
const EditBadge = ({
|
|
12
|
+
src,
|
|
13
|
+
onUpload,
|
|
14
|
+
onDelete,
|
|
15
|
+
anchorOrigin,
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}) => {
|
|
19
|
+
const [showMenu, setShowMenu] = useState(false)
|
|
20
|
+
const [status, setStatus] = useState('PRESENT') // PRESENT || ABSENT || LOADING
|
|
21
|
+
const [timestamp, setTimestamp] = useState(Date.now())
|
|
22
|
+
|
|
23
|
+
const menuAnchorRef = useRef(null)
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Badge
|
|
27
|
+
{...props}
|
|
28
|
+
anchorOrigin={anchorOrigin}
|
|
29
|
+
badgeContent={
|
|
30
|
+
<>
|
|
31
|
+
<Button
|
|
32
|
+
ref={menuAnchorRef}
|
|
33
|
+
component="div"
|
|
34
|
+
className="u-miw-auto u-w-2-half u-h-2-half u-bdrs-circle"
|
|
35
|
+
classes={{ label: 'u-flex u-w-auto' }}
|
|
36
|
+
style={{
|
|
37
|
+
outline: '4px solid var(--paperBackgroundColor)'
|
|
38
|
+
}}
|
|
39
|
+
label={<Icon icon={PenIcon} />}
|
|
40
|
+
size="small"
|
|
41
|
+
onClick={() => setShowMenu(v => !v)}
|
|
42
|
+
/>
|
|
43
|
+
<EditMenu
|
|
44
|
+
anchorRef={menuAnchorRef.current}
|
|
45
|
+
status={status}
|
|
46
|
+
showMenu={showMenu}
|
|
47
|
+
setShowMenu={setShowMenu}
|
|
48
|
+
setStatus={setStatus}
|
|
49
|
+
setTimestamp={setTimestamp}
|
|
50
|
+
onUpload={onUpload}
|
|
51
|
+
onDelete={onDelete}
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
54
|
+
}
|
|
55
|
+
>
|
|
56
|
+
<StatusWrapper
|
|
57
|
+
src={src(timestamp)}
|
|
58
|
+
status={status}
|
|
59
|
+
setStatus={setStatus}
|
|
60
|
+
timestamp={timestamp}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</StatusWrapper>
|
|
64
|
+
</Badge>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
EditBadge.defaultProps = {
|
|
69
|
+
anchorOrigin: {
|
|
70
|
+
vertical: 'bottom',
|
|
71
|
+
horizontal: 'right'
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
EditBadge.propTypes = {
|
|
76
|
+
src: PropTypes.func.isRequired,
|
|
77
|
+
onUpload: PropTypes.func.isRequired,
|
|
78
|
+
onDelete: PropTypes.func.isRequired
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default EditBadge
|