From cb267ce3e1cd964b7b1f648d30453e6e500c8dbb Mon Sep 17 00:00:00 2001
From: Alexandre Chau <accounts@chau.moe>
Date: Fri, 17 Feb 2023 17:33:11 +0100
Subject: [PATCH 1/6] feat: add document style flavors

---
 package.json                              |   7 +-
 src/components/item/ItemContent.tsx       |  86 ++++++++----
 src/components/item/form/DocumentForm.tsx | 132 ++++++++++++++----
 src/utils/itemExtra.ts                    |   4 +-
 yarn.lock                                 | 161 ++++++++++++++++++++--
 5 files changed, 325 insertions(+), 65 deletions(-)

diff --git a/package.json b/package.json
index d38e20fab..ec298deb5 100644
--- a/package.json
+++ b/package.json
@@ -14,9 +14,9 @@
     "@emotion/styled": "11.10.5",
     "@graasp/chatbox": "1.0.1",
     "@graasp/query-client": "0.1.3",
-    "@graasp/sdk": "0.3.0",
+    "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor",
     "@graasp/translations": "1.3.0",
-    "@graasp/ui": "0.11.1",
+    "@graasp/ui": "github:graasp/graasp-ui#240-document-flavor",
     "@mui/icons-material": "5.11.0",
     "@mui/lab": "5.0.0-alpha.117",
     "@mui/material": "5.11.6",
@@ -156,8 +156,7 @@
     "react-error-overlay": "6.0.9",
     "@mui/icons-material": "5.10.14",
     "@mui/material": "5.10.14",
-    "react-query": "3.39.2",
-    "@graasp/sdk": "0.3.0"
+    "react-query": "3.39.2"
   },
   "packageManager": "yarn@3.2.4"
 }
diff --git a/src/components/item/ItemContent.tsx b/src/components/item/ItemContent.tsx
index ddd941e61..dcbdf27b3 100644
--- a/src/components/item/ItemContent.tsx
+++ b/src/components/item/ItemContent.tsx
@@ -1,16 +1,19 @@
 import { RecordOf } from 'immutable';
 
-import { Container, styled } from '@mui/material';
+import { Button, Container, styled } from '@mui/material';
 
-import { FC, useContext } from 'react';
+import { FC, useContext, useState } from 'react';
 
 import { Api, MUTATION_KEYS } from '@graasp/query-client';
 import {
+  DocumentItemExtra,
+  DocumentItemExtraProperties,
   Item,
   ItemType,
   PermissionLevel,
   buildPdfViewerLink,
 } from '@graasp/sdk';
+import { COMMON } from '@graasp/translations';
 import {
   AppItem,
   DocumentItem,
@@ -19,6 +22,7 @@ import {
   H5PItem,
   LinkItem,
   Loader,
+  SaveButton,
 } from '@graasp/ui';
 
 import {
@@ -30,6 +34,7 @@ import {
   H5P_INTEGRATION_URL,
   ITEM_DEFAULT_HEIGHT,
 } from '../../config/constants';
+import { useCommonTranslation } from '../../config/i18n';
 import { hooks, useMutation } from '../../config/queryClient';
 import {
   DOCUMENT_ITEM_TEXT_EDITOR_ID,
@@ -45,10 +50,11 @@ import { LayoutContext } from '../context/LayoutContext';
 import ItemActions from '../main/ItemActions';
 import Items from '../main/Items';
 import NewItemButton from '../main/NewItemButton';
+import { DocumentExtraForm } from './form/DocumentForm';
 
 const { useChildren, useFileContent, useEtherpad } = hooks;
 
-const FileWrapper = styled(Container)(() => ({
+const StyledContainer = styled(Container)(() => ({
   textAlign: 'center',
   height: '80vh',
   flexGrow: 1,
@@ -62,6 +68,8 @@ type Props = {
 };
 
 const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
+  const { t: translateCommon } = useCommonTranslation();
+
   const { id: itemId, type: itemType } = item;
   const { mutate: editItem } = useMutation<any, any, any>(
     MUTATION_KEYS.EDIT_ITEM,
@@ -89,6 +97,14 @@ const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
   );
   const isEditing = enableEditing && editingItemId === itemId;
 
+  const maybeDocumentExtra = getDocumentExtra(item?.extra);
+  const [content, setContent] = useState<
+    DocumentItemExtraProperties['content']
+  >(maybeDocumentExtra.content);
+  const [flavor, setFlavor] = useState<DocumentItemExtraProperties['flavor']>(
+    maybeDocumentExtra.flavor,
+  );
+
   const etherpadQuery = useEtherpad(item, 'write'); // server will return read view if no write access allowed
 
   if (
@@ -112,11 +128,11 @@ const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
     setEditingItemId(null);
   };
 
-  const onSaveDocument = (text) => {
-    // edit item only when description has changed
-    if (text !== getDocumentExtra(item?.extra).content) {
-      editItem({ id: itemId, extra: buildDocumentExtra({ content: text }) });
-    }
+  const onSaveDocument = () => {
+    editItem({
+      id: itemId,
+      extra: buildDocumentExtra({ content, flavor }),
+    });
     setEditingItemId(null);
   };
 
@@ -132,7 +148,7 @@ const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
       // todo: remove when query client is correctly typed
       const file = fileData as Record<string, string>;
       return (
-        <FileWrapper>
+        <StyledContainer>
           <FileItem
             editCaption={isEditing}
             fileUrl={file?.url}
@@ -142,12 +158,12 @@ const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
             pdfViewerLink={buildPdfViewerLink(GRAASP_ASSETS_URL)}
             saveButtonId={saveButtonId}
           />
-        </FileWrapper>
+        </StyledContainer>
       );
     }
     case ItemType.LINK:
       return (
-        <FileWrapper>
+        <StyledContainer>
           <LinkItem
             isResizable
             item={item}
@@ -162,21 +178,45 @@ const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
               item.settings?.showLinkIframe ?? DEFAULT_LINK_SHOW_IFRAME,
             )}
           />
-        </FileWrapper>
+        </StyledContainer>
       );
     case ItemType.DOCUMENT:
       return (
-        <FileWrapper>
-          <DocumentItem
-            id={DOCUMENT_ITEM_TEXT_EDITOR_ID}
-            item={item}
-            edit={isEditing}
-            onSave={onSaveDocument}
-            onCancel={onCancel}
-            saveButtonId={saveButtonId}
-            maxHeight="70vh"
-          />
-        </FileWrapper>
+        <StyledContainer>
+          {isEditing ? (
+            <>
+              <DocumentExtraForm
+                documentItemId={DOCUMENT_ITEM_TEXT_EDITOR_ID}
+                extra={{ content, flavor }}
+                maxHeight="70vh"
+                onCancel={onCancel}
+                onContentChange={setContent}
+                onContentSave={onSaveDocument}
+                onFlavorChange={(f) => setFlavor(f || undefined)}
+                saveButtonId={saveButtonId}
+              />
+              <Button onClick={onCancel} variant="text" sx={{ m: 1 }}>
+                {translateCommon(COMMON.CANCEL_BUTTON)}
+              </Button>
+              <SaveButton
+                sx={{ m: 1 }}
+                id={saveButtonId}
+                onClick={onSaveDocument}
+                text={translateCommon(COMMON.SAVE_BUTTON)}
+                hasChanges={
+                  content !== maybeDocumentExtra.content ||
+                  flavor !== maybeDocumentExtra.flavor
+                }
+              />
+            </>
+          ) : (
+            <DocumentItem
+              id={DOCUMENT_ITEM_TEXT_EDITOR_ID}
+              item={item}
+              maxHeight="70vh"
+            />
+          )}
+        </StyledContainer>
       );
     case ItemType.APP:
       return (
diff --git a/src/components/item/form/DocumentForm.tsx b/src/components/item/form/DocumentForm.tsx
index 9ec9b0c50..8602dc81f 100644
--- a/src/components/item/form/DocumentForm.tsx
+++ b/src/components/item/form/DocumentForm.tsx
@@ -1,15 +1,88 @@
+import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';
 import Box from '@mui/material/Box';
 import Typography from '@mui/material/Typography';
 
-import { DocumentItemExtra, Item, ItemType } from '@graasp/sdk';
+import { useEffect, useState } from 'react';
+
+import {
+  DocumentItemExtra,
+  DocumentItemExtraProperties,
+  Item,
+  ItemType,
+} from '@graasp/sdk';
 import { BUILDER } from '@graasp/translations';
-import { TextEditor } from '@graasp/ui';
+import { DocumentItem } from '@graasp/ui';
 
 import { useBuilderTranslation } from '../../../config/i18n';
 import { ITEM_FORM_DOCUMENT_TEXT_ID } from '../../../config/selectors';
 import { buildDocumentExtra } from '../../../utils/itemExtra';
 import BaseForm from './BaseItemForm';
 
+export const DocumentExtraForm = ({
+  documentItemId,
+  extra,
+  maxHeight,
+  onCancel,
+  onContentChange,
+  onContentSave,
+  onFlavorChange,
+  placeholder,
+  saveButtonId,
+  showActions = false,
+}: {
+  documentItemId?: string;
+  extra: DocumentItemExtraProperties;
+  maxHeight?: string;
+  onCancel?: () => void;
+  onContentChange?: (text: string) => void;
+  onContentSave?: (text: string) => void;
+  onFlavorChange?: (text: DocumentItemExtraProperties['flavor']) => void;
+  placeholder?: string;
+  saveButtonId?: string;
+  showActions?: boolean;
+}): JSX.Element => (
+  <>
+    <Box sx={{ width: '100%' }}>
+      <FormControl variant="standard" sx={{ width: '50%', my: 1 }}>
+        <InputLabel shrink>Flavor</InputLabel>
+        <Select
+          variant="standard"
+          label="flavor"
+          value={extra.flavor}
+          onChange={(event) =>
+            onFlavorChange(
+              event.target.value === ''
+                ? undefined
+                : (event.target.value as DocumentItemExtraProperties['flavor']),
+            )
+          }
+        >
+          <MenuItem value="">None</MenuItem>
+          {['info', 'warning', 'error', 'success'].map((f) => (
+            <MenuItem key={f} value={f}>
+              {f}
+            </MenuItem>
+          ))}
+        </Select>
+      </FormControl>
+    </Box>
+    <Box sx={{ mt: 2 }}>
+      <DocumentItem
+        edit
+        id={documentItemId}
+        item={{ extra: buildDocumentExtra(extra) }}
+        maxHeight={maxHeight}
+        onCancel={onCancel}
+        onChange={onContentChange}
+        onSave={onContentSave}
+        placeholderText={placeholder}
+        saveButtonId={saveButtonId}
+        showActions={showActions}
+      />
+    </Box>
+  </>
+);
+
 type Props = {
   onChange: (item: Partial<Item>) => void;
   item: Partial<Item>;
@@ -23,39 +96,50 @@ const DocumentForm = ({
 }: Props): JSX.Element => {
   const { t: translateBuilder } = useBuilderTranslation();
 
-  const handleOnChange = (content) => {
+  const initContent: string =
+    updatedProperties?.extra?.[ItemType.DOCUMENT]?.content ||
+    item?.extra?.[ItemType.DOCUMENT]?.content;
+
+  const initFlavor: DocumentItemExtraProperties['flavor'] =
+    updatedProperties?.extra?.[ItemType.DOCUMENT]?.flavor ||
+    item?.extra?.[ItemType.DOCUMENT]?.flavor;
+
+  const [content, setContent] =
+    useState<DocumentItemExtraProperties['content']>(initContent);
+  const [flavor, setFlavor] =
+    useState<DocumentItemExtraProperties['flavor']>(initFlavor);
+
+  const currentExtra = buildDocumentExtra({ content, flavor });
+
+  // synchronize upper state after async local state change
+  useEffect(() => {
     onChange({
       ...updatedProperties,
-      extra: buildDocumentExtra({ content }),
+      extra: currentExtra,
     });
-  };
-
-  const value =
-    ((updatedProperties?.extra?.[ItemType.DOCUMENT] as DocumentItemExtra)
-      ?.content as string) ||
-    ((item?.extra?.[ItemType.DOCUMENT] as DocumentItemExtra)
-      ?.content as string);
+    // we only want to execute the state sync on local state change
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [content, flavor]);
 
   return (
     <>
       <Typography variant="h6">
         {translateBuilder(BUILDER.CREATE_NEW_ITEM_DOCUMENT_TITLE)}
       </Typography>
-      <BaseForm
-        onChange={onChange}
-        item={item}
-        updatedProperties={updatedProperties}
-      />
-      <Box sx={{ mt: 2 }}>
-        <TextEditor
-          id={ITEM_FORM_DOCUMENT_TEXT_ID}
-          value={value}
-          onChange={handleOnChange}
-          edit
-          placeholderText={translateBuilder(BUILDER.TEXT_EDITOR_PLACEHOLDER)}
-          showActions={false}
+      <Box>
+        <BaseForm
+          onChange={onChange}
+          item={item}
+          updatedProperties={updatedProperties}
         />
       </Box>
+      <DocumentExtraForm
+        documentItemId={ITEM_FORM_DOCUMENT_TEXT_ID}
+        extra={currentExtra.document}
+        onContentChange={setContent}
+        onFlavorChange={setFlavor}
+        placeholder={translateBuilder(BUILDER.TEXT_EDITOR_PLACEHOLDER)}
+      />
     </>
   );
 };
diff --git a/src/utils/itemExtra.ts b/src/utils/itemExtra.ts
index 67ea40325..228fed2dc 100644
--- a/src/utils/itemExtra.ts
+++ b/src/utils/itemExtra.ts
@@ -89,9 +89,9 @@ export const getItemLoginSchema = (extra: { itemLogin?: ItemLogin }): string =>
   getItemLoginExtra(extra)?.loginSchema;
 
 export const buildDocumentExtra = (
-  text: DocumentItemExtraProperties,
+  document: DocumentItemExtraProperties,
 ): DocumentItemExtra => ({
-  [ItemType.DOCUMENT]: text,
+  [ItemType.DOCUMENT]: document,
 });
 
 export const getDocumentExtra = (
diff --git a/yarn.lock b/yarn.lock
index d537e584b..39375658e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2411,6 +2411,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@fastify/cookie@npm:^8.0.0":
+  version: 8.3.0
+  resolution: "@fastify/cookie@npm:8.3.0"
+  dependencies:
+    cookie: ^0.5.0
+    fastify-plugin: ^4.0.0
+  checksum: d5dfb5c85d4ae02188aad63c8cf055d3b904287bc2750964ee0b8589c5b5ae32850e8fc9456a8830e9f4e9f24d5059e54dac084835d6ef4cbba7fa4b9c4673b3
+  languageName: node
+  linkType: hard
+
 "@fastify/error@npm:^2.0.0":
   version: 2.0.0
   resolution: "@fastify/error@npm:2.0.0"
@@ -2431,6 +2441,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@fastify/secure-session@npm:5.3.0":
+  version: 5.3.0
+  resolution: "@fastify/secure-session@npm:5.3.0"
+  dependencies:
+    "@fastify/cookie": ^8.0.0
+    fastify-plugin: ^4.0.0
+    sodium-native: ^3.0.0
+  bin:
+    secure-session: genkey.js
+  checksum: 584d9321f26ade63b8913758f65693b14400c9babfe23e26ded715e4b97ee26e01bf49df2562630b35f588beccdaff38f016a2c66c0e1357708588aa93a058d0
+  languageName: node
+  linkType: hard
+
 "@gar/promisify@npm:^1.1.3":
   version: 1.1.3
   resolution: "@gar/promisify@npm:1.1.3"
@@ -2490,9 +2513,43 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@graasp/sdk@npm:0.3.0":
-  version: 0.3.0
-  resolution: "@graasp/sdk@npm:0.3.0"
+"@graasp/sdk@github:graasp/graasp-sdk#81-document-flavor":
+  version: 0.4.1
+  resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=b09a7812eb9b07d0d35612d485ae3b84394b30a1"
+  dependencies:
+    "@fastify/secure-session": 5.3.0
+    aws-sdk: 2.1310.0
+    fastify: 3.29.5
+    fluent-json-schema: 3.1.0
+    immutable: 4.2.4
+    js-cookie: 3.0.1
+    qs: 6.11.0
+    slonik: 28.1.1
+    uuid: 9.0.0
+  checksum: 073438ce9a70f89b84a9371fb08ab654fb6818871203da95a7237004f347b5c8f1ba410a68f1ae85f507b3ca06f6f1fddff53bdbb4ec39d3ce71a8442e2917d2
+  languageName: node
+  linkType: hard
+
+"@graasp/sdk@github:graasp/graasp-sdk.git":
+  version: 0.4.1
+  resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=75e7833bbedda2b5c8002dc6e2decc4e8610d885"
+  dependencies:
+    "@fastify/secure-session": 5.3.0
+    aws-sdk: 2.1310.0
+    fastify: 3.29.5
+    fluent-json-schema: 3.1.0
+    immutable: 4.2.4
+    js-cookie: 3.0.1
+    qs: 6.11.0
+    slonik: 28.1.1
+    uuid: 9.0.0
+  checksum: 0c5235e5c3039e19ad484d08c096a8e5508494803a9f483fb0f7b3121c98593ec3dc9493f296b9cce3c45e1a207686bcc3286c77e5e69bbc15f87515de36ed7e
+  languageName: node
+  linkType: hard
+
+"@graasp/sdk@npm:0.2.0":
+  version: 0.2.0
+  resolution: "@graasp/sdk@npm:0.2.0"
   dependencies:
     "@fastify/secure-session": 5.2.0
     aws-sdk: 2.1111.0
@@ -2503,7 +2560,7 @@ __metadata:
     qs: 6.11.0
     slonik: 28.1.1
     uuid: 8.3.2
-  checksum: 9204d48d08ebf5b1ed3b37000c8b3398a6f1ae2420ce5a474da0ba115494e14156aee106d3850eb7d892255d668bb0e018efd60359b674fa4b88d195419f77cb
+  checksum: 95a76c69dd4577f8a0eb51197d83fdb5c61c58c1c6a7c895e7ca543d4883603bc7deb534fd5b7bc4f61b5f15cd0b6c403bf165e6a6a4a756707f9ad877961339
   languageName: node
   linkType: hard
 
@@ -2525,11 +2582,11 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@graasp/ui@npm:0.11.1":
+"@graasp/ui@github:graasp/graasp-ui#240-document-flavor":
   version: 0.11.1
-  resolution: "@graasp/ui@npm:0.11.1"
+  resolution: "@graasp/ui@https://github.com/graasp/graasp-ui.git#commit=fef5ff3a608678367844c648b258c90c2d9cc5dc"
   dependencies:
-    "@graasp/sdk": 0.4.0
+    "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor"
     clsx: 1.1.1
     http-status-codes: 2.2.0
     immutable: 4.2.4
@@ -2560,7 +2617,7 @@ __metadata:
       optional: true
     ag-grid-react:
       optional: true
-  checksum: 599961399ee145e1a82555e6de86a927ae5408b4e145cc88de4eb0da3a22bc9b896b4145d44982e615a61d680b16c40832034f321d738458ea42266874c11627
+  checksum: 8b6f764cacee9af30a6a034a3a2a33106b828f54929a9efffc448f1aeee245dc4f31a0497faa86cbdd35fbf80142e30aec19f819cb669c98636a395cb95b7f83
   languageName: node
   linkType: hard
 
@@ -5691,6 +5748,24 @@ __metadata:
   languageName: node
   linkType: hard
 
+"aws-sdk@npm:2.1310.0":
+  version: 2.1310.0
+  resolution: "aws-sdk@npm:2.1310.0"
+  dependencies:
+    buffer: 4.9.2
+    events: 1.1.1
+    ieee754: 1.1.13
+    jmespath: 0.16.0
+    querystring: 0.2.0
+    sax: 1.2.1
+    url: 0.10.3
+    util: ^0.12.4
+    uuid: 8.0.0
+    xml2js: 0.4.19
+  checksum: 648141461eaaaac1bcd8a2f1df460bb4603176541a28ed0aa7c344cc9fe19d69b2a463cb975c767bfb913ebc462103474fcd0315d07918c5fa35b0fef5f00f56
+  languageName: node
+  linkType: hard
+
 "aws-sign2@npm:~0.7.0":
   version: 0.7.0
   resolution: "aws-sign2@npm:0.7.0"
@@ -9159,6 +9234,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"fast-content-type-parse@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "fast-content-type-parse@npm:1.0.0"
+  checksum: 9e9187be17bea18a2ee715c5737b983181cbe84f286a291db0595e421e04b578da10ca10845639be08664a4db6a793f7709822935cf38cfdf9ecba38d84ead9e
+  languageName: node
+  linkType: hard
+
 "fast-decode-uri-component@npm:^1.0.1":
   version: 1.0.1
   resolution: "fast-decode-uri-component@npm:1.0.1"
@@ -9296,6 +9378,30 @@ __metadata:
   languageName: node
   linkType: hard
 
+"fastify@npm:3.29.5":
+  version: 3.29.5
+  resolution: "fastify@npm:3.29.5"
+  dependencies:
+    "@fastify/ajv-compiler": ^1.0.0
+    "@fastify/error": ^2.0.0
+    abstract-logging: ^2.0.0
+    avvio: ^7.1.2
+    fast-content-type-parse: ^1.0.0
+    fast-json-stringify: ^2.5.2
+    find-my-way: ^4.5.0
+    flatstr: ^1.0.12
+    light-my-request: ^4.2.0
+    pino: ^6.13.0
+    process-warning: ^1.0.0
+    proxy-addr: ^2.0.7
+    rfdc: ^1.1.4
+    secure-json-parse: ^2.0.0
+    semver: ^7.3.2
+    tiny-lru: ^8.0.1
+  checksum: 6f97e67c25c509c584529aad2ffe0d4bf8943a860f09d847bf34a4736586cf73f5748bde154e0199c022b97ff76aba35c3094006e62b07100ab10c63205541e3
+  languageName: node
+  linkType: hard
+
 "fastify@npm:^3.29.1":
   version: 3.29.3
   resolution: "fastify@npm:3.29.3"
@@ -10140,9 +10246,9 @@ __metadata:
     "@emotion/styled": 11.10.5
     "@graasp/chatbox": 1.0.1
     "@graasp/query-client": 0.1.3
-    "@graasp/sdk": 0.3.0
+    "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor"
     "@graasp/translations": 1.3.0
-    "@graasp/ui": 0.11.1
+    "@graasp/ui": "github:graasp/graasp-ui#240-document-flavor"
     "@graasp/websockets": "github:graasp/graasp-websockets.git"
     "@mui/icons-material": 5.11.0
     "@mui/lab": 5.0.0-alpha.117
@@ -11042,6 +11148,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"is-generator-function@npm:^1.0.7":
+  version: 1.0.10
+  resolution: "is-generator-function@npm:1.0.10"
+  dependencies:
+    has-tostringtag: ^1.0.0
+  checksum: d54644e7dbaccef15ceb1e5d91d680eb5068c9ee9f9eb0a9e04173eb5542c9b51b5ab52c5537f5703e48d5fddfd376817c1ca07a84a407b7115b769d4bdde72b
+  languageName: node
+  linkType: hard
+
 "is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1":
   version: 4.0.3
   resolution: "is-glob@npm:4.0.3"
@@ -11242,7 +11357,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-typed-array@npm:^1.1.10":
+"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3":
   version: 1.1.10
   resolution: "is-typed-array@npm:1.1.10"
   dependencies:
@@ -19227,6 +19342,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"util@npm:^0.12.4":
+  version: 0.12.5
+  resolution: "util@npm:0.12.5"
+  dependencies:
+    inherits: ^2.0.3
+    is-arguments: ^1.0.4
+    is-generator-function: ^1.0.7
+    is-typed-array: ^1.1.3
+    which-typed-array: ^1.1.2
+  checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a
+  languageName: node
+  linkType: hard
+
 "utila@npm:~0.4":
   version: 0.4.0
   resolution: "utila@npm:0.4.0"
@@ -19257,6 +19385,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"uuid@npm:8.0.0":
+  version: 8.0.0
+  resolution: "uuid@npm:8.0.0"
+  bin:
+    uuid: dist/bin/uuid
+  checksum: 56d4e23aa7ac26fa2db6bd1778db34cb8c9f5a10df1770a27167874bf6705fc8f14a4ac414af58a0d96c7653b2bd4848510b29d1c2ef8c91ccb17429c1872b5e
+  languageName: node
+  linkType: hard
+
 "uuid@npm:8.3.2, uuid@npm:^8.3.2":
   version: 8.3.2
   resolution: "uuid@npm:8.3.2"
@@ -19690,7 +19827,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"which-typed-array@npm:^1.1.8":
+"which-typed-array@npm:^1.1.2, which-typed-array@npm:^1.1.8":
   version: 1.1.9
   resolution: "which-typed-array@npm:1.1.9"
   dependencies:

From 26625fe05b7ffab81ed1112652a8878dd77edcec Mon Sep 17 00:00:00 2001
From: Alexandre Chau <accounts@chau.moe>
Date: Mon, 20 Feb 2023 16:54:41 +0100
Subject: [PATCH 2/6] fix: nullable type

---
 src/components/item/ItemContent.tsx | 5 ++---
 yarn.lock                           | 2 +-
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/components/item/ItemContent.tsx b/src/components/item/ItemContent.tsx
index dcbdf27b3..3b1acb4dc 100644
--- a/src/components/item/ItemContent.tsx
+++ b/src/components/item/ItemContent.tsx
@@ -6,7 +6,6 @@ import { FC, useContext, useState } from 'react';
 
 import { Api, MUTATION_KEYS } from '@graasp/query-client';
 import {
-  DocumentItemExtra,
   DocumentItemExtraProperties,
   Item,
   ItemType,
@@ -100,9 +99,9 @@ const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
   const maybeDocumentExtra = getDocumentExtra(item?.extra);
   const [content, setContent] = useState<
     DocumentItemExtraProperties['content']
-  >(maybeDocumentExtra.content);
+  >(maybeDocumentExtra?.content);
   const [flavor, setFlavor] = useState<DocumentItemExtraProperties['flavor']>(
-    maybeDocumentExtra.flavor,
+    maybeDocumentExtra?.flavor,
   );
 
   const etherpadQuery = useEtherpad(item, 'write'); // server will return read view if no write access allowed
diff --git a/yarn.lock b/yarn.lock
index 39375658e..ebbac2588 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2617,7 +2617,7 @@ __metadata:
       optional: true
     ag-grid-react:
       optional: true
-  checksum: 8b6f764cacee9af30a6a034a3a2a33106b828f54929a9efffc448f1aeee245dc4f31a0497faa86cbdd35fbf80142e30aec19f819cb669c98636a395cb95b7f83
+  checksum: 66635ff034722614d6045ac9e2b1a21125348d0fd8f79f2171ee3a3a8bbfb8c8da3acf134bdc71c59287c1be3a4c182461da8785256b29380d6e1fe669a91ec6
   languageName: node
   linkType: hard
 

From 01e27d03db3f070238c424e4c79c11b67b47c7ad Mon Sep 17 00:00:00 2001
From: Alexandre Chau <accounts@chau.moe>
Date: Fri, 24 Feb 2023 16:00:44 +0100
Subject: [PATCH 3/6] fix: refactor ItemContent with discriminated item types

---
 src/components/item/ItemContent.tsx | 499 +++++++++++++++++++---------
 yarn.lock                           |   4 +-
 2 files changed, 348 insertions(+), 155 deletions(-)

diff --git a/src/components/item/ItemContent.tsx b/src/components/item/ItemContent.tsx
index 70ac4c360..7e16d6c62 100644
--- a/src/components/item/ItemContent.tsx
+++ b/src/components/item/ItemContent.tsx
@@ -1,6 +1,6 @@
 import { Button, Container, styled } from '@mui/material';
 
-import { FC, useState } from 'react';
+import { useState } from 'react';
 import { UseQueryResult } from 'react-query';
 
 import { Api, MUTATION_KEYS } from '@graasp/query-client';
@@ -10,8 +10,21 @@ import {
   PermissionLevel,
   buildPdfViewerLink,
   getDocumentExtra,
+  getH5PExtra,
 } from '@graasp/sdk';
-import { EtherpadRecord, ItemRecord } from '@graasp/sdk/frontend';
+import {
+  AppItemTypeRecord,
+  DocumentItemTypeRecord,
+  EmbeddedLinkItemTypeRecord,
+  EtherpadItemTypeRecord,
+  EtherpadRecord,
+  FolderItemTypeRecord,
+  H5PItemTypeRecord,
+  ItemRecord,
+  LocalFileItemTypeRecord,
+  MemberRecord,
+  S3FileItemTypeRecord,
+} from '@graasp/sdk/frontend';
 import { COMMON } from '@graasp/translations';
 import {
   AppItem,
@@ -59,78 +72,335 @@ const StyledContainer = styled(Container)(() => ({
   flexGrow: 1,
 }));
 
-type Props = {
-  item: ItemRecord;
-  enableEditing?: boolean;
-  permission: PermissionLevel;
+/**
+ * Helper component to render typed file items
+ */
+const FileContent = ({
+  item,
+  isEditing,
+  onSaveCaption,
+  saveButtonId,
+}: {
+  item: LocalFileItemTypeRecord | S3FileItemTypeRecord;
+  isEditing: boolean;
+  onSaveCaption: (text: string) => void;
+  saveButtonId: string;
+}): JSX.Element => {
+  const {
+    data: fileContent,
+    isLoading,
+    isError,
+  } = useFileContent(item.id, {
+    replyUrl: true,
+  });
+
+  // todo: remove when query client is correctly typed
+  const file = fileContent as Record<string, string>;
+
+  if (isLoading) {
+    return <Loader />;
+  }
+
+  if (isError) {
+    return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
+  }
+
+  return (
+    <StyledContainer>
+      <FileItem
+        editCaption={isEditing}
+        fileUrl={file?.url}
+        id={buildFileItemId(item.id)}
+        item={item}
+        onSaveCaption={onSaveCaption}
+        pdfViewerLink={buildPdfViewerLink(GRAASP_ASSETS_URL)}
+        saveButtonId={saveButtonId}
+      />
+    </StyledContainer>
+  );
 };
 
-const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
+/**
+ * Helper component to render typed link items
+ */
+const LinkContent = ({
+  item,
+  member,
+  isEditing,
+  onSaveCaption,
+  saveButtonId,
+}: {
+  item: EmbeddedLinkItemTypeRecord;
+  member: MemberRecord;
+  isEditing: boolean;
+  onSaveCaption: (caption: string) => void;
+  saveButtonId: string;
+}): JSX.Element => (
+  <StyledContainer>
+    <LinkItem
+      memberId={member.id}
+      isResizable
+      item={item}
+      editCaption={isEditing}
+      onSaveCaption={onSaveCaption}
+      saveButtonId={saveButtonId}
+      height={ITEM_DEFAULT_HEIGHT}
+      showButton={Boolean(
+        item.settings?.showLinkButton ?? DEFAULT_LINK_SHOW_BUTTON,
+      )}
+      showIframe={Boolean(
+        item.settings?.showLinkIframe ?? DEFAULT_LINK_SHOW_IFRAME,
+      )}
+    />
+  </StyledContainer>
+);
+
+/**
+ * Helper component to render typed document items
+ */
+const DocumentContent = ({
+  item,
+  isEditing,
+  onSaveDocument,
+  onCancel,
+  saveButtonId,
+}: {
+  item: DocumentItemTypeRecord;
+  isEditing: boolean;
+  onSaveDocument: (update: DocumentItemExtraProperties) => void;
+  onCancel: () => void;
+  saveButtonId: string;
+}): JSX.Element => {
   const { t: translateCommon } = useCommonTranslation();
 
-  const { id: itemId, type: itemType } = item;
-  const { mutate: editItem } = useMutation<any, any, any>(
-    MUTATION_KEYS.EDIT_ITEM,
+  const extra = getDocumentExtra(item?.extra);
+
+  const [content, setContent] = useState<
+    DocumentItemExtraProperties['content']
+  >(extra?.content ?? '');
+  const [flavor, setFlavor] = useState<DocumentItemExtraProperties['flavor']>(
+    extra?.flavor,
   );
-  const { editingItemId, setEditingItemId } = useLayoutContext();
 
-  // provide user to app
-  const { data: member, isLoading: isLoadingUser } = useCurrentUserContext();
+  const newExtra = { content, flavor };
+
+  const onSave = () => onSaveDocument(newExtra);
 
-  // display children
-  const { data: children, isLoading: isLoadingChildren } = useChildren(itemId, {
+  return (
+    <StyledContainer>
+      {isEditing ? (
+        <>
+          <DocumentExtraForm
+            documentItemId={DOCUMENT_ITEM_TEXT_EDITOR_ID}
+            extra={newExtra}
+            maxHeight="70vh"
+            onCancel={onCancel}
+            onContentChange={setContent}
+            onContentSave={onSave}
+            onFlavorChange={(f) => setFlavor(f || undefined)}
+            saveButtonId={saveButtonId}
+          />
+          <Button onClick={onCancel} variant="text" sx={{ m: 1 }}>
+            {translateCommon(COMMON.CANCEL_BUTTON)}
+          </Button>
+          <SaveButton
+            sx={{ m: 1 }}
+            id={saveButtonId}
+            onClick={onSave}
+            text={translateCommon(COMMON.SAVE_BUTTON)}
+            hasChanges={content !== extra?.content || flavor !== extra?.flavor}
+          />
+        </>
+      ) : (
+        <DocumentItem
+          id={DOCUMENT_ITEM_TEXT_EDITOR_ID}
+          item={item}
+          maxHeight="70vh"
+        />
+      )}
+    </StyledContainer>
+  );
+};
+
+/**
+ * Helper component to render typed app items
+ */
+export const AppContent = ({
+  item,
+  member,
+  permission,
+  isEditing,
+  onSaveCaption,
+  saveButtonId,
+}: {
+  item: AppItemTypeRecord;
+  member: MemberRecord;
+  permission: PermissionLevel;
+  isEditing: boolean;
+  onSaveCaption: (caption: string) => void;
+  saveButtonId: string;
+}): JSX.Element => (
+  <AppItem
+    isResizable
+    item={item}
+    apiHost={API_HOST}
+    editCaption={isEditing}
+    onSaveCaption={onSaveCaption}
+    saveButtonId={saveButtonId}
+    member={member}
+    height={ITEM_DEFAULT_HEIGHT}
+    permission={permission}
+    requestApiAccessToken={Api.requestApiAccessToken}
+    context={CONTEXT_BUILDER}
+  />
+);
+
+/**
+ * Helper component to render typed folder items
+ */
+export const FolderContent = ({
+  item,
+  isEditing,
+  enableEditing,
+}: {
+  item: FolderItemTypeRecord;
+  isEditing: boolean;
+  enableEditing: boolean;
+}): JSX.Element => {
+  const {
+    data: children,
+    isLoading,
+    isError,
+  } = useChildren(item.id, {
     ordered: true,
-    enabled: item?.type === ItemType.FOLDER,
   });
 
-  const { data: fileData, isLoading: isLoadingFileContent } = useFileContent(
-    itemId,
-    {
-      enabled:
-        item &&
-        (itemType === ItemType.LOCAL_FILE || itemType === ItemType.S3_FILE),
-      replyUrl: true,
-    },
+  if (isLoading) {
+    return <Loader />;
+  }
+
+  if (isError) {
+    return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
+  }
+
+  return (
+    <Items
+      parentId={item.id}
+      id={buildItemsTableId(item.id)}
+      title={item.name}
+      items={children}
+      isEditing={isEditing}
+      headerElements={
+        enableEditing ? [<NewItemButton key="newButton" />] : undefined
+      }
+      ToolbarActions={ItemActions}
+      showCreator
+    />
   );
-  const isEditing = enableEditing && editingItemId === itemId;
+};
 
-  const maybeDocumentExtra = getDocumentExtra(item?.extra);
-  const [content, setContent] = useState<
-    DocumentItemExtraProperties['content']
-  >(maybeDocumentExtra?.content);
-  const [flavor, setFlavor] = useState<DocumentItemExtraProperties['flavor']>(
-    maybeDocumentExtra?.flavor,
+/**
+ * Helper component to render typed H5P items
+ */
+export const H5PContent = ({
+  item,
+}: {
+  item: H5PItemTypeRecord;
+}): JSX.Element => {
+  const { contentId } = getH5PExtra(item?.extra);
+
+  if (!contentId) {
+    return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
+  }
+
+  return (
+    <H5PItem
+      itemId={item.id}
+      contentId={contentId}
+      integrationUrl={H5P_INTEGRATION_URL}
+    />
   );
+};
 
-  const etherpadQuery: UseQueryResult<EtherpadRecord> = useEtherpad(
+/**
+ * Helper component to render typed Etherpad items
+ */
+export const EtherpadContent = ({
+  item,
+}: {
+  item: EtherpadItemTypeRecord;
+}): JSX.Element => {
+  const {
+    data: etherpad,
+    isLoading,
+    isError,
+  }: UseQueryResult<EtherpadRecord> = useEtherpad(
     item,
-    'write',
-  ); // server will return read view if no write access allowed
-
-  if (
-    isLoadingFileContent ||
-    isLoadingUser ||
-    isLoadingChildren ||
-    etherpadQuery?.isLoading
-  ) {
+    'write', // server will return read view if no write access allowed
+  );
+
+  if (isLoading) {
+    return <Loader />;
+  }
+
+  if (!etherpad?.padUrl || isError) {
+    return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
+  }
+
+  return (
+    <EtherpadItem
+      itemId={item.id}
+      padUrl={etherpad.padUrl}
+      options={{ showChat: false }}
+    />
+  );
+};
+
+/**
+ * Props for {@see ItemContent}
+ */
+type Props = {
+  item: ItemRecord;
+  enableEditing?: boolean;
+  permission: PermissionLevel;
+};
+
+/**
+ * Main item renderer component
+ */
+const ItemContent = ({
+  item,
+  enableEditing,
+  permission,
+}: Props): JSX.Element => {
+  const { mutate: editItem } = useMutation<any, any, any>(
+    MUTATION_KEYS.EDIT_ITEM,
+  );
+  const { editingItemId, setEditingItemId } = useLayoutContext();
+
+  const { data: member, isLoading, isError } = useCurrentUserContext();
+
+  const isEditing = enableEditing && editingItemId === item.id;
+
+  if (isLoading) {
     return <Loader />;
   }
 
-  if (!item || !itemId || etherpadQuery?.isError) {
+  if (!item || !item.id || isError) {
     return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
   }
 
-  const onSaveCaption = (caption) => {
+  const onSaveCaption = (caption: string) => {
     // edit item only when description has changed
     if (caption !== item.description) {
-      editItem({ id: itemId, description: caption });
+      editItem({ id: item.id, description: caption });
     }
     setEditingItemId(null);
   };
 
-  const onSaveDocument = () => {
+  const onSaveDocument = ({ content, flavor }: DocumentItemExtraProperties) => {
     editItem({
-      id: itemId,
+      id: item.id,
       extra: buildDocumentExtra({ content, flavor }),
     });
     setEditingItemId(null);
@@ -140,143 +410,66 @@ const ItemContent: FC<Props> = ({ item, enableEditing, permission }) => {
     setEditingItemId(null);
   };
 
-  const saveButtonId = buildSaveButtonId(itemId);
+  const saveButtonId = buildSaveButtonId(item.id);
 
-  switch (itemType) {
+  switch (item.type) {
     case ItemType.LOCAL_FILE:
     case ItemType.S3_FILE: {
-      // todo: remove when query client is correctly typed
-      const file = fileData as Record<string, string>;
       return (
-        <StyledContainer>
-          <FileItem
-            editCaption={isEditing}
-            fileUrl={file?.url}
-            id={buildFileItemId(itemId)}
-            item={item}
-            onSaveCaption={onSaveCaption}
-            pdfViewerLink={buildPdfViewerLink(GRAASP_ASSETS_URL)}
-            saveButtonId={saveButtonId}
-          />
-        </StyledContainer>
+        <FileContent
+          item={item}
+          isEditing={isEditing}
+          onSaveCaption={onSaveCaption}
+          saveButtonId={saveButtonId}
+        />
       );
     }
     case ItemType.LINK:
       return (
-        <StyledContainer>
-          <LinkItem
-            memberId={member.id}
-            isResizable
-            item={item}
-            editCaption={isEditing}
-            onSaveCaption={onSaveCaption}
-            saveButtonId={saveButtonId}
-            height={ITEM_DEFAULT_HEIGHT}
-            showButton={Boolean(
-              item.settings?.showLinkButton ?? DEFAULT_LINK_SHOW_BUTTON,
-            )}
-            showIframe={Boolean(
-              item.settings?.showLinkIframe ?? DEFAULT_LINK_SHOW_IFRAME,
-            )}
-          />
-        </StyledContainer>
+        <LinkContent
+          item={item}
+          member={member}
+          isEditing={isEditing}
+          onSaveCaption={onSaveCaption}
+          saveButtonId={saveButtonId}
+        />
       );
     case ItemType.DOCUMENT:
       return (
-        <StyledContainer>
-          {isEditing ? (
-            <>
-              <DocumentExtraForm
-                documentItemId={DOCUMENT_ITEM_TEXT_EDITOR_ID}
-                extra={{ content, flavor }}
-                maxHeight="70vh"
-                onCancel={onCancel}
-                onContentChange={setContent}
-                onContentSave={onSaveDocument}
-                onFlavorChange={(f) => setFlavor(f || undefined)}
-                saveButtonId={saveButtonId}
-              />
-              <Button onClick={onCancel} variant="text" sx={{ m: 1 }}>
-                {translateCommon(COMMON.CANCEL_BUTTON)}
-              </Button>
-              <SaveButton
-                sx={{ m: 1 }}
-                id={saveButtonId}
-                onClick={onSaveDocument}
-                text={translateCommon(COMMON.SAVE_BUTTON)}
-                hasChanges={
-                  content !== maybeDocumentExtra.content ||
-                  flavor !== maybeDocumentExtra.flavor
-                }
-              />
-            </>
-          ) : (
-            <DocumentItem
-              id={DOCUMENT_ITEM_TEXT_EDITOR_ID}
-              item={item}
-              maxHeight="70vh"
-            />
-          )}
-        </StyledContainer>
+        <DocumentContent
+          item={item}
+          isEditing={isEditing}
+          onSaveDocument={onSaveDocument}
+          onCancel={onCancel}
+          saveButtonId={saveButtonId}
+        />
       );
     case ItemType.APP:
       return (
-        <AppItem
-          isResizable
+        <AppContent
           item={item}
-          apiHost={API_HOST}
-          editCaption={isEditing}
+          member={member}
+          isEditing={isEditing}
           onSaveCaption={onSaveCaption}
           saveButtonId={saveButtonId}
-          member={member}
-          height={ITEM_DEFAULT_HEIGHT}
           permission={permission}
-          requestApiAccessToken={Api.requestApiAccessToken}
-          context={CONTEXT_BUILDER}
         />
       );
     case ItemType.FOLDER:
       return (
-        <Items
-          parentId={itemId}
-          id={buildItemsTableId(itemId)}
-          title={item.name}
-          items={children}
+        <FolderContent
+          item={item}
           isEditing={isEditing}
-          headerElements={
-            enableEditing ? [<NewItemButton key="newButton" />] : undefined
-          }
-          ToolbarActions={ItemActions}
-          showCreator
+          enableEditing={enableEditing}
         />
       );
 
     case ItemType.H5P: {
-      const contentId = item.extra?.h5p?.contentId;
-      if (!contentId) {
-        return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
-      }
-
-      return (
-        <H5PItem
-          itemId={itemId}
-          contentId={contentId}
-          integrationUrl={H5P_INTEGRATION_URL}
-        />
-      );
+      return <H5PContent item={item} />;
     }
 
     case ItemType.ETHERPAD: {
-      if (!etherpadQuery?.data?.padUrl) {
-        return <ErrorAlert id={ITEM_SCREEN_ERROR_ALERT_ID} />;
-      }
-      return (
-        <EtherpadItem
-          itemId={itemId}
-          padUrl={etherpadQuery.data.padUrl}
-          options={{ showChat: false }}
-        />
-      );
+      return <EtherpadContent item={item} />;
     }
 
     default:
diff --git a/yarn.lock b/yarn.lock
index ff98ab270..09e45113a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2374,7 +2374,7 @@ __metadata:
 
 "@graasp/sdk@github:graasp/graasp-sdk#81-document-flavor":
   version: 0.4.1
-  resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=b09a7812eb9b07d0d35612d485ae3b84394b30a1"
+  resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=2fdaddd15d48014215192af280b47ddbcef51b3c"
   dependencies:
     "@fastify/secure-session": 5.3.0
     aws-sdk: 2.1310.0
@@ -2385,7 +2385,7 @@ __metadata:
     qs: 6.11.0
     slonik: 28.1.1
     uuid: 9.0.0
-  checksum: 073438ce9a70f89b84a9371fb08ab654fb6818871203da95a7237004f347b5c8f1ba410a68f1ae85f507b3ca06f6f1fddff53bdbb4ec39d3ce71a8442e2917d2
+  checksum: 59e99ba5933013d095d04540e76b46491f5a29c86a14ec9ae67bdae5bc44ad537b25dcd9746b848d5851de8be4a510373147ad0f107fa9be04b0770a9e1c16ba
   languageName: node
   linkType: hard
 

From ae0404b55e2f97d776f4ddc89ada4e8f8634d59b Mon Sep 17 00:00:00 2001
From: Alexandre Chau <accounts@chau.moe>
Date: Fri, 3 Mar 2023 17:38:57 +0100
Subject: [PATCH 4/6] feat: add document flavors translations

---
 package.json                              |  2 +-
 src/components/item/form/DocumentForm.tsx | 93 +++++++++++++----------
 yarn.lock                                 | 22 +++---
 3 files changed, 63 insertions(+), 54 deletions(-)

diff --git a/package.json b/package.json
index f66d2eda8..dac18f53a 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
     "@graasp/chatbox": "1.0.3",
     "@graasp/query-client": "0.2.1",
     "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor",
-    "@graasp/translations": "1.5.0",
+    "@graasp/translations": "github:graasp/graasp-translations#116-document-styles",
     "@graasp/ui": "github:graasp/graasp-ui#240-document-flavor",
     "@mui/icons-material": "5.11.0",
     "@mui/lab": "5.0.0-alpha.117",
diff --git a/src/components/item/form/DocumentForm.tsx b/src/components/item/form/DocumentForm.tsx
index 1e1dcc0e0..3bf9de772 100644
--- a/src/components/item/form/DocumentForm.tsx
+++ b/src/components/item/form/DocumentForm.tsx
@@ -5,6 +5,7 @@ import Typography from '@mui/material/Typography';
 import { useEffect, useState } from 'react';
 
 import {
+  DocumentItemExtraFlavor,
   DocumentItemExtraProperties,
   DocumentItemType,
   ItemType,
@@ -40,48 +41,56 @@ export const DocumentExtraForm = ({
   placeholder?: string;
   saveButtonId?: string;
   showActions?: boolean;
-}): JSX.Element => (
-  <>
-    <Box sx={{ width: '100%' }}>
-      <FormControl variant="standard" sx={{ width: '50%', my: 1 }}>
-        <InputLabel shrink>Flavor</InputLabel>
-        <Select
-          variant="standard"
-          label="flavor"
-          value={extra.flavor}
-          onChange={(event) =>
-            onFlavorChange(
-              event.target.value === ''
-                ? undefined
-                : (event.target.value as DocumentItemExtraProperties['flavor']),
-            )
-          }
-        >
-          <MenuItem value="">None</MenuItem>
-          {['info', 'warning', 'error', 'success'].map((f) => (
-            <MenuItem key={f} value={f}>
-              {f}
-            </MenuItem>
-          ))}
-        </Select>
-      </FormControl>
-    </Box>
-    <Box sx={{ mt: 2 }}>
-      <DocumentItem
-        edit
-        id={documentItemId}
-        item={{ extra: buildDocumentExtra(extra) }}
-        maxHeight={maxHeight}
-        onCancel={onCancel}
-        onChange={onContentChange}
-        onSave={onContentSave}
-        placeholderText={placeholder}
-        saveButtonId={saveButtonId}
-        showActions={showActions}
-      />
-    </Box>
-  </>
-);
+}): JSX.Element => {
+  const { t } = useBuilderTranslation();
+  const flavorsTranslations = Object.values(DocumentItemExtraFlavor).map(
+    (f) => [f, t(BUILDER[`DOCUMENT_FLAVOR_${f.toUpperCase()}`])],
+  );
+
+  return (
+    <>
+      <Box sx={{ width: '100%' }}>
+        <FormControl variant="standard" sx={{ width: '50%', my: 1 }}>
+          <InputLabel shrink>Flavor</InputLabel>
+          <Select
+            variant="standard"
+            label="flavor"
+            value={extra.flavor}
+            onChange={(event) =>
+              onFlavorChange(
+                event.target.value === ''
+                  ? undefined
+                  : (event.target
+                      .value as DocumentItemExtraProperties['flavor']),
+              )
+            }
+          >
+            <MenuItem value="">None</MenuItem>
+            {flavorsTranslations.map(([f, name]) => (
+              <MenuItem key={f} value={f}>
+                {name}
+              </MenuItem>
+            ))}
+          </Select>
+        </FormControl>
+      </Box>
+      <Box sx={{ mt: 2 }}>
+        <DocumentItem
+          edit
+          id={documentItemId}
+          item={{ extra: buildDocumentExtra(extra) }}
+          maxHeight={maxHeight}
+          onCancel={onCancel}
+          onChange={onContentChange}
+          onSave={onContentSave}
+          placeholderText={placeholder}
+          saveButtonId={saveButtonId}
+          showActions={showActions}
+        />
+      </Box>
+    </>
+  );
+};
 
 type Props = {
   onChange: (item: Partial<DocumentItemType>) => void;
diff --git a/yarn.lock b/yarn.lock
index ee8c22582..848b21789 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2370,7 +2370,7 @@ __metadata:
 
 "@graasp/sdk@github:graasp/graasp-sdk#81-document-flavor":
   version: 0.4.1
-  resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=2fdaddd15d48014215192af280b47ddbcef51b3c"
+  resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=4db009347156d2efc5b43ed56d5a54c1446dca26"
   dependencies:
     "@fastify/secure-session": 5.3.0
     aws-sdk: 2.1310.0
@@ -2381,7 +2381,7 @@ __metadata:
     qs: 6.11.0
     slonik: 28.1.1
     uuid: 9.0.0
-  checksum: 59e99ba5933013d095d04540e76b46491f5a29c86a14ec9ae67bdae5bc44ad537b25dcd9746b848d5851de8be4a510373147ad0f107fa9be04b0770a9e1c16ba
+  checksum: 74462fbc8081aa3ae3fef06e930b18bd4ce56d138a01f917707f05d1f73111fe398aeb4b11b5d566b05e56c1558ea5fe66269a3723ea14f10f6619ad7d6fc1d3
   languageName: node
   linkType: hard
 
@@ -2419,21 +2419,21 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@graasp/translations@npm:1.4.0":
-  version: 1.4.0
-  resolution: "@graasp/translations@npm:1.4.0"
+"@graasp/translations@github:graasp/graasp-translations#116-document-styles":
+  version: 1.6.0
+  resolution: "@graasp/translations@https://github.com/graasp/graasp-translations.git#commit=a9188a1e605f17cbaf44e011ec32bcf283390d3e"
   dependencies:
     i18next: 21.8.1
-  checksum: ca787604906c4531eded20941e968667a13f7c2fb5b4c2c03a149123ce6994ec685d1b5e125e37f420fa5d6b8edf4cbd0bb48452c554cde07d81bc0309b5dc1f
+  checksum: 508a298c1b159fc79e99fce36d0543b955430bbb3582f1a92496c5491c5504d60f4b9f6b65e2fc6aca4a6caf3863437740532bfdcdfcfc5fae0391a2a4c9c2a3
   languageName: node
   linkType: hard
 
-"@graasp/translations@npm:1.5.0":
-  version: 1.5.0
-  resolution: "@graasp/translations@npm:1.5.0"
+"@graasp/translations@npm:1.4.0":
+  version: 1.4.0
+  resolution: "@graasp/translations@npm:1.4.0"
   dependencies:
     i18next: 21.8.1
-  checksum: e5e3e00b334cab2a71776c6a13dce0284239ed83fcc5622d5b31df791616c82ad4dda40a9a829bc5ed0d46457ecdf7d28ff0aa568d977ce7f66f7dab4c89580e
+  checksum: ca787604906c4531eded20941e968667a13f7c2fb5b4c2c03a149123ce6994ec685d1b5e125e37f420fa5d6b8edf4cbd0bb48452c554cde07d81bc0309b5dc1f
   languageName: node
   linkType: hard
 
@@ -9933,7 +9933,7 @@ __metadata:
     "@graasp/chatbox": 1.0.3
     "@graasp/query-client": 0.2.1
     "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor"
-    "@graasp/translations": 1.5.0
+    "@graasp/translations": "github:graasp/graasp-translations#116-document-styles"
     "@graasp/ui": "github:graasp/graasp-ui#240-document-flavor"
     "@graasp/websockets": "github:graasp/graasp-websockets.git"
     "@mui/icons-material": 5.11.0

From 48975c3ba7eeeee021d4adf2f240556387759096 Mon Sep 17 00:00:00 2001
From: Alexandre Chau <accounts@chau.moe>
Date: Mon, 6 Mar 2023 10:11:34 +0100
Subject: [PATCH 5/6] chore: bump @graasp dependencies

---
 package.json |   8 ++--
 yarn.lock    | 112 +++++++++++++++++++++++++--------------------------
 2 files changed, 60 insertions(+), 60 deletions(-)

diff --git a/package.json b/package.json
index af220f17a..1de3ccca9 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,10 @@
     "@emotion/react": "11.10.5",
     "@emotion/styled": "11.10.5",
     "@graasp/chatbox": "1.0.3",
-    "@graasp/query-client": "0.2.1",
-    "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor",
-    "@graasp/translations": "github:graasp/graasp-translations#116-document-styles",
-    "@graasp/ui": "github:graasp/graasp-ui#240-document-flavor",
+    "@graasp/query-client": "0.3.0",
+    "@graasp/sdk": "0.6.0",
+    "@graasp/translations": "1.7.0",
+    "@graasp/ui": "1.0.0",
     "@mui/icons-material": "5.11.0",
     "@mui/lab": "5.0.0-alpha.117",
     "@mui/material": "5.11.6",
diff --git a/yarn.lock b/yarn.lock
index 38a3f2ae4..5c254883c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2349,9 +2349,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@graasp/query-client@npm:0.2.1":
-  version: 0.2.1
-  resolution: "@graasp/query-client@npm:0.2.1"
+"@graasp/query-client@npm:0.3.0":
+  version: 0.3.0
+  resolution: "@graasp/query-client@npm:0.3.0"
   dependencies:
     "@graasp/sdk": 0.4.1
     "@graasp/translations": 1.4.0
@@ -2364,24 +2364,7 @@ __metadata:
     uuid: 9.0.0
   peerDependencies:
     react: ^17.0.0
-  checksum: 63337026c3fe71d9a772cc5a9f2144fc523fa1682db29bb3718fb096e1a8dd9945ae32eef900a129baa32979ca5c89d9457956bf95552d06977e4f1ecd0505a2
-  languageName: node
-  linkType: hard
-
-"@graasp/sdk@github:graasp/graasp-sdk#81-document-flavor":
-  version: 0.4.1
-  resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=4db009347156d2efc5b43ed56d5a54c1446dca26"
-  dependencies:
-    "@fastify/secure-session": 5.3.0
-    aws-sdk: 2.1310.0
-    fastify: 3.29.5
-    fluent-json-schema: 3.1.0
-    immutable: 4.2.4
-    js-cookie: 3.0.1
-    qs: 6.11.0
-    slonik: 28.1.1
-    uuid: 9.0.0
-  checksum: 74462fbc8081aa3ae3fef06e930b18bd4ce56d138a01f917707f05d1f73111fe398aeb4b11b5d566b05e56c1558ea5fe66269a3723ea14f10f6619ad7d6fc1d3
+  checksum: 6d52a706f7b6418e4c67d4ca0835107c27b113d54fe090503694d6908f6ee1d48dd25fc00eac3d6d4436f14aa30f460ea1e9e300fd0e0a68398214bbaca00eef
   languageName: node
   linkType: hard
 
@@ -2419,12 +2402,20 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@graasp/translations@github:graasp/graasp-translations#116-document-styles":
-  version: 1.6.0
-  resolution: "@graasp/translations@https://github.com/graasp/graasp-translations.git#commit=a9188a1e605f17cbaf44e011ec32bcf283390d3e"
+"@graasp/sdk@npm:0.6.0":
+  version: 0.6.0
+  resolution: "@graasp/sdk@npm:0.6.0"
   dependencies:
-    i18next: 21.8.1
-  checksum: 508a298c1b159fc79e99fce36d0543b955430bbb3582f1a92496c5491c5504d60f4b9f6b65e2fc6aca4a6caf3863437740532bfdcdfcfc5fae0391a2a4c9c2a3
+    "@fastify/secure-session": 5.3.0
+    aws-sdk: 2.1310.0
+    fastify: 3.29.5
+    fluent-json-schema: 3.1.0
+    immutable: 4.2.4
+    js-cookie: 3.0.1
+    qs: 6.11.0
+    slonik: 28.1.1
+    uuid: 9.0.0
+  checksum: 450ec10d93b2139ea21728b6ce633e6947092707b1caa66055a0a251607b0c2b28ed03d3c9baa1864e930a460289927209256aef7509e7b8ff4973d17703a2ba
   languageName: node
   linkType: hard
 
@@ -2437,30 +2428,39 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@graasp/ui@github:graasp/graasp-ui#240-document-flavor":
-  version: 0.11.2
-  resolution: "@graasp/ui@https://github.com/graasp/graasp-ui.git#commit=79c12b980ec34fb89f3092d868efabe6a75f9823"
+"@graasp/translations@npm:1.7.0":
+  version: 1.7.0
+  resolution: "@graasp/translations@npm:1.7.0"
   dependencies:
-    "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor"
+    i18next: 21.8.1
+  checksum: fa4b2e3b6ea86d42c1c3fb928762b708dc0fc7bcb960654eff6e810b9fd97baecdee54cb43564ae47650e20878833035ac52564ca2e4f9ee8745322bc952d49c
+  languageName: node
+  linkType: hard
+
+"@graasp/ui@npm:0.10.5":
+  version: 0.10.5
+  resolution: "@graasp/ui@npm:0.10.5"
+  dependencies:
+    "@graasp/sdk": 0.2.0
     clsx: 1.1.1
     http-status-codes: 2.2.0
-    immutable: 4.2.4
+    immutable: 4.1.0
     katex: 0.16.0
     lodash.truncate: 4.4.2
-    qs: 6.11.0
+    qs: 6.10.5
     quill-emoji: 0.2.0
     react-cookie-consent: 7.4.1
     react-i18next: 11.17.0
     react-quill: 2.0.0-beta.4
     react-rnd: 10.3.7
     react-text-mask: 5.4.3
-    uuid: 9.0.0
+    uuid: 8.3.2
   peerDependencies:
-    "@emotion/react": ~11.10.6
-    "@emotion/styled": ~11.10.6
-    "@mui/icons-material": ~5.11.9
-    "@mui/lab": ~5.0.0-alpha.120
-    "@mui/material": ~5.11.9
+    "@emotion/react": 11.10.4
+    "@emotion/styled": 11.10.4
+    "@mui/icons-material": 5.8.3
+    "@mui/lab": ~5.0.0-alpha.85
+    "@mui/material": 5.10.7
     ag-grid-community: 28.1.1
     ag-grid-react: 28.1.1
     i18next: ~21.8.9
@@ -2472,34 +2472,34 @@ __metadata:
       optional: true
     ag-grid-react:
       optional: true
-  checksum: f5f6ae4ac98ae749d9241773aae54656dca620667b16c93aa26e96407b17573cd7700ae64473ae8fe2790d647146b7c25d6fc33f96867c27f7f3cc76509e54c8
+  checksum: 922cf6a9de9402178b906eda12081eb571feaecc240e621fceed3f0937b82c052c8a2700a414488f99d6da39deb7ba02b4f23343124ea4df01b55717ba17d51e
   languageName: node
   linkType: hard
 
-"@graasp/ui@npm:0.10.5":
-  version: 0.10.5
-  resolution: "@graasp/ui@npm:0.10.5"
+"@graasp/ui@npm:1.0.0":
+  version: 1.0.0
+  resolution: "@graasp/ui@npm:1.0.0"
   dependencies:
-    "@graasp/sdk": 0.2.0
+    "@graasp/sdk": 0.6.0
     clsx: 1.1.1
     http-status-codes: 2.2.0
-    immutable: 4.1.0
+    immutable: 4.2.4
     katex: 0.16.0
     lodash.truncate: 4.4.2
-    qs: 6.10.5
+    qs: 6.11.0
     quill-emoji: 0.2.0
     react-cookie-consent: 7.4.1
     react-i18next: 11.17.0
     react-quill: 2.0.0-beta.4
     react-rnd: 10.3.7
     react-text-mask: 5.4.3
-    uuid: 8.3.2
+    uuid: 9.0.0
   peerDependencies:
-    "@emotion/react": 11.10.4
-    "@emotion/styled": 11.10.4
-    "@mui/icons-material": 5.8.3
-    "@mui/lab": ~5.0.0-alpha.85
-    "@mui/material": 5.10.7
+    "@emotion/react": ~11.10.6
+    "@emotion/styled": ~11.10.6
+    "@mui/icons-material": ~5.11.9
+    "@mui/lab": ~5.0.0-alpha.120
+    "@mui/material": ~5.11.9
     ag-grid-community: 28.1.1
     ag-grid-react: 28.1.1
     i18next: ~21.8.9
@@ -2511,7 +2511,7 @@ __metadata:
       optional: true
     ag-grid-react:
       optional: true
-  checksum: 922cf6a9de9402178b906eda12081eb571feaecc240e621fceed3f0937b82c052c8a2700a414488f99d6da39deb7ba02b4f23343124ea4df01b55717ba17d51e
+  checksum: 717c6ce2c03abd338fc13a7e98c85117b520aeff716824940bf63cf1902e608b9910f4c30b6be061e440c113ea0e7f4772c9ac94ee4bd665cec0828ef4b4f9fe
   languageName: node
   linkType: hard
 
@@ -9931,10 +9931,10 @@ __metadata:
     "@emotion/react": 11.10.5
     "@emotion/styled": 11.10.5
     "@graasp/chatbox": 1.0.3
-    "@graasp/query-client": 0.2.1
-    "@graasp/sdk": "github:graasp/graasp-sdk#81-document-flavor"
-    "@graasp/translations": "github:graasp/graasp-translations#116-document-styles"
-    "@graasp/ui": "github:graasp/graasp-ui#240-document-flavor"
+    "@graasp/query-client": 0.3.0
+    "@graasp/sdk": 0.6.0
+    "@graasp/translations": 1.7.0
+    "@graasp/ui": 1.0.0
     "@graasp/websockets": "github:graasp/graasp-websockets.git"
     "@mui/icons-material": 5.11.0
     "@mui/lab": 5.0.0-alpha.117

From 86b94fbd05c72579e896aa2e97bd469e6dfa56cd Mon Sep 17 00:00:00 2001
From: Alexandre Chau <accounts@chau.moe>
Date: Mon, 6 Mar 2023 11:46:53 +0100
Subject: [PATCH 6/6] chore: bump @graasp/ui

---
 package.json                             |  2 +-
 src/components/common/CollapseButton.tsx | 10 +++++-----
 src/components/common/DeleteButton.tsx   |  7 +++++--
 src/components/common/FavoriteButton.tsx |  7 +++++--
 src/components/common/HideButton.tsx     | 11 ++++++-----
 src/components/common/MoveButton.tsx     | 11 +++++++----
 src/components/common/PinButton.tsx      |  4 ++--
 src/components/common/RecycleButton.tsx  | 10 +++++-----
 src/components/main/CopyButton.tsx       |  7 +++++--
 src/components/main/ItemMenu.tsx         | 20 ++++++++++++--------
 src/config/constants.ts                  |  5 -----
 src/utils/itemExtra.ts                   |  3 ++-
 yarn.lock                                | 10 +++++-----
 13 files changed, 60 insertions(+), 47 deletions(-)

diff --git a/package.json b/package.json
index 1de3ccca9..87ded3167 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
     "@graasp/query-client": "0.3.0",
     "@graasp/sdk": "0.6.0",
     "@graasp/translations": "1.7.0",
-    "@graasp/ui": "1.0.0",
+    "@graasp/ui": "2.0.0",
     "@mui/icons-material": "5.11.0",
     "@mui/lab": "5.0.0-alpha.117",
     "@mui/material": "5.11.6",
diff --git a/src/components/common/CollapseButton.tsx b/src/components/common/CollapseButton.tsx
index 9c399082f..531c3b65c 100644
--- a/src/components/common/CollapseButton.tsx
+++ b/src/components/common/CollapseButton.tsx
@@ -10,21 +10,21 @@ import { useEffect, useState } from 'react';
 import { MUTATION_KEYS } from '@graasp/query-client';
 import { Item } from '@graasp/sdk';
 import { BUILDER } from '@graasp/translations';
+import { ActionButton, ActionButtonVariant } from '@graasp/ui';
 
-import { ButtonType } from '../../config/constants';
 import { useBuilderTranslation } from '../../config/i18n';
 import { useMutation } from '../../config/queryClient';
 import { COLLAPSE_ITEM_BUTTON_CLASS } from '../../config/selectors';
 
 type Props = {
   item: Item;
-  type?: ButtonType;
+  type?: ActionButtonVariant;
   onClick?: () => void;
 };
 
 const CollapseButton = ({
   item,
-  type = ButtonType.ICON_BUTTON,
+  type = ActionButton.ICON_BUTTON,
   onClick,
 }: Props): JSX.Element => {
   const { t: translateBuilder } = useBuilderTranslation();
@@ -57,7 +57,7 @@ const CollapseButton = ({
     : translateBuilder(BUILDER.COLLAPSE_ITEM_COLLAPSE_TEXT);
 
   switch (type) {
-    case ButtonType.MENU_ITEM:
+    case ActionButton.MENU_ITEM:
       return (
         <MenuItem
           key={text}
@@ -68,7 +68,7 @@ const CollapseButton = ({
           {text}
         </MenuItem>
       );
-    case ButtonType.ICON_BUTTON:
+    case ActionButton.ICON_BUTTON:
     default:
       return (
         <Tooltip title={text}>
diff --git a/src/components/common/DeleteButton.tsx b/src/components/common/DeleteButton.tsx
index b50692219..05554b852 100644
--- a/src/components/common/DeleteButton.tsx
+++ b/src/components/common/DeleteButton.tsx
@@ -3,7 +3,10 @@ import { IconButtonProps } from '@mui/material/IconButton';
 import { FC, useState } from 'react';
 
 import { BUILDER } from '@graasp/translations';
-import { DeleteButton as GraaspDeleteButton } from '@graasp/ui';
+import {
+  ActionButtonVariant,
+  DeleteButton as GraaspDeleteButton,
+} from '@graasp/ui';
 
 import { useBuilderTranslation } from '../../config/i18n';
 import { ITEM_DELETE_BUTTON_CLASS } from '../../config/selectors';
@@ -13,7 +16,7 @@ type Props = {
   itemIds: string[];
   color?: IconButtonProps['color'];
   id?: string;
-  type?: string;
+  type?: ActionButtonVariant;
   onClick?: () => void;
 };
 
diff --git a/src/components/common/FavoriteButton.tsx b/src/components/common/FavoriteButton.tsx
index 2f3605096..b9d1610ba 100644
--- a/src/components/common/FavoriteButton.tsx
+++ b/src/components/common/FavoriteButton.tsx
@@ -5,7 +5,10 @@ import { FC } from 'react';
 import { MUTATION_KEYS } from '@graasp/query-client';
 import { ItemRecord, MemberRecord } from '@graasp/sdk/frontend';
 import { BUILDER } from '@graasp/translations';
-import { FavoriteButton as GraaspFavoriteButton } from '@graasp/ui';
+import {
+  ActionButtonVariant,
+  FavoriteButton as GraaspFavoriteButton,
+} from '@graasp/ui';
 
 import { useBuilderTranslation } from '../../config/i18n';
 import { useMutation } from '../../config/queryClient';
@@ -14,7 +17,7 @@ import { useCurrentUserContext } from '../context/CurrentUserContext';
 
 type Props = {
   item: ItemRecord;
-  type?: string;
+  type?: ActionButtonVariant;
   onClick?: () => void;
   size?: IconButtonProps['size'];
 };
diff --git a/src/components/common/HideButton.tsx b/src/components/common/HideButton.tsx
index aa5dc532a..1669ddaaa 100644
--- a/src/components/common/HideButton.tsx
+++ b/src/components/common/HideButton.tsx
@@ -8,21 +8,22 @@ import Tooltip from '@mui/material/Tooltip';
 import { MUTATION_KEYS } from '@graasp/query-client';
 import { Item } from '@graasp/sdk';
 import { BUILDER } from '@graasp/translations';
+import { ActionButton, ActionButtonVariant } from '@graasp/ui';
 
-import { ButtonType, HIDDEN_ITEM_TAG_ID } from '../../config/constants';
+import { HIDDEN_ITEM_TAG_ID } from '../../config/constants';
 import { useBuilderTranslation } from '../../config/i18n';
 import { hooks, useMutation } from '../../config/queryClient';
 import { HIDDEN_ITEM_BUTTON_CLASS } from '../../config/selectors';
 
 type Props = {
   item: Item;
-  type?: ButtonType;
+  type?: ActionButtonVariant;
   onClick?: () => void;
 };
 
 const HideButton = ({
   item,
-  type = ButtonType.ICON_BUTTON,
+  type = ActionButton.ICON_BUTTON,
   onClick,
 }: Props): JSX.Element => {
   const { t: translateBuilder } = useBuilderTranslation();
@@ -69,7 +70,7 @@ const HideButton = ({
   const icon = hiddenTag ? <VisibilityOff /> : <Visibility />;
 
   switch (type) {
-    case ButtonType.MENU_ITEM: {
+    case ActionButton.MENU_ITEM: {
       const menuItem = (
         <MenuItem
           key={text}
@@ -92,7 +93,7 @@ const HideButton = ({
         </Tooltip>
       );
     }
-    case ButtonType.ICON_BUTTON:
+    case ActionButton.ICON_BUTTON:
     default:
       return (
         <Tooltip title={tooltip}>
diff --git a/src/components/common/MoveButton.tsx b/src/components/common/MoveButton.tsx
index 3cef53089..df9081e21 100644
--- a/src/components/common/MoveButton.tsx
+++ b/src/components/common/MoveButton.tsx
@@ -3,9 +3,12 @@ import { IconButtonProps } from '@mui/material/IconButton';
 import { FC, useContext } from 'react';
 
 import { BUILDER } from '@graasp/translations';
-import { MoveButton as GraaspMoveButton } from '@graasp/ui';
+import {
+  ActionButton,
+  ActionButtonVariant,
+  MoveButton as GraaspMoveButton,
+} from '@graasp/ui';
 
-import { ButtonType } from '../../config/constants';
 import { useBuilderTranslation } from '../../config/i18n';
 import {
   ITEM_MENU_MOVE_BUTTON_CLASS,
@@ -17,7 +20,7 @@ type MoveButtonProps = {
   itemIds: string[];
   color?: IconButtonProps['color'];
   id?: string;
-  type?: ButtonType;
+  type?: ActionButtonVariant;
   onClick?: () => void;
 };
 
@@ -25,7 +28,7 @@ const MoveButton: FC<MoveButtonProps> = ({
   itemIds,
   color = 'default',
   id,
-  type = ButtonType.ICON_BUTTON,
+  type = ActionButton.ICON_BUTTON,
   onClick,
 }) => {
   const { t: translateBuilder } = useBuilderTranslation();
diff --git a/src/components/common/PinButton.tsx b/src/components/common/PinButton.tsx
index fae0c180a..3649fe437 100644
--- a/src/components/common/PinButton.tsx
+++ b/src/components/common/PinButton.tsx
@@ -3,14 +3,14 @@ import { useState } from 'react';
 import { MUTATION_KEYS } from '@graasp/query-client';
 import { Item } from '@graasp/sdk';
 import { BUILDER } from '@graasp/translations';
-import { PinButton as GraaspPinButton } from '@graasp/ui';
+import { ActionButtonVariant, PinButton as GraaspPinButton } from '@graasp/ui';
 
 import { useBuilderTranslation } from '../../config/i18n';
 import { useMutation } from '../../config/queryClient';
 import { PIN_ITEM_BUTTON_CLASS } from '../../config/selectors';
 
 type Props = {
-  type?: string;
+  type?: ActionButtonVariant;
   item: Item;
   onClick?: () => void;
 };
diff --git a/src/components/common/RecycleButton.tsx b/src/components/common/RecycleButton.tsx
index 21d34bec9..8a22da38e 100644
--- a/src/components/common/RecycleButton.tsx
+++ b/src/components/common/RecycleButton.tsx
@@ -8,8 +8,8 @@ import { FC } from 'react';
 
 import { MUTATION_KEYS } from '@graasp/query-client';
 import { BUILDER } from '@graasp/translations';
+import { ActionButton, ActionButtonVariant } from '@graasp/ui';
 
-import { ButtonType } from '../../config/constants';
 import { useBuilderTranslation } from '../../config/i18n';
 import { useMutation } from '../../config/queryClient';
 import {
@@ -21,7 +21,7 @@ type Props = {
   itemIds: string[];
   color?: IconButtonProps['color'];
   id?: string;
-  type?: ButtonType;
+  type?: ActionButtonVariant;
   onClick?: () => void;
 };
 
@@ -29,7 +29,7 @@ const RecycleButton: FC<Props> = ({
   itemIds,
   color = 'default',
   id,
-  type = ButtonType.ICON_BUTTON,
+  type = ActionButton.ICON_BUTTON,
   onClick,
 }) => {
   const { t: translateBuilder } = useBuilderTranslation();
@@ -45,7 +45,7 @@ const RecycleButton: FC<Props> = ({
   const text = translateBuilder(BUILDER.RECYCLE_ITEM_BUTTON);
 
   switch (type) {
-    case ButtonType.MENU_ITEM:
+    case ActionButton.MENU_ITEM:
       return (
         <MenuItem
           key={text}
@@ -58,7 +58,7 @@ const RecycleButton: FC<Props> = ({
           {text}
         </MenuItem>
       );
-    case ButtonType.ICON_BUTTON:
+    case ActionButton.ICON_BUTTON:
     default:
       return (
         <Tooltip title={text}>
diff --git a/src/components/main/CopyButton.tsx b/src/components/main/CopyButton.tsx
index 293bcf79e..d45f7ce32 100644
--- a/src/components/main/CopyButton.tsx
+++ b/src/components/main/CopyButton.tsx
@@ -3,7 +3,10 @@ import { IconButtonProps } from '@mui/material';
 import { FC, MouseEventHandler } from 'react';
 
 import { BUILDER } from '@graasp/translations';
-import { CopyButton as GraaspCopyButton } from '@graasp/ui';
+import {
+  ActionButtonVariant,
+  CopyButton as GraaspCopyButton,
+} from '@graasp/ui';
 
 import { useBuilderTranslation } from '../../config/i18n';
 import {
@@ -16,7 +19,7 @@ export type Props = {
   color?: IconButtonProps['color'];
   id?: string;
   onClick?: MouseEventHandler<HTMLButtonElement | HTMLLIElement>;
-  type?: string;
+  type?: ActionButtonVariant;
   itemIds: string[];
 };
 
diff --git a/src/components/main/ItemMenu.tsx b/src/components/main/ItemMenu.tsx
index 13f1cac3e..b8ebfe5b9 100644
--- a/src/components/main/ItemMenu.tsx
+++ b/src/components/main/ItemMenu.tsx
@@ -10,8 +10,8 @@ import { FC, useContext, useState } from 'react';
 
 import { Item, convertJs } from '@graasp/sdk';
 import { BUILDER } from '@graasp/translations';
+import { ActionButton } from '@graasp/ui';
 
-import { ButtonType } from '../../config/constants';
 import { useBuilderTranslation } from '../../config/i18n';
 import {
   ITEM_MENU_BUTTON_CLASS,
@@ -70,16 +70,20 @@ const ItemMenu: FC<Props> = ({ item, canEdit = false }) => {
     return [
       <MoveButton
         key="move"
-        type={ButtonType.MENU_ITEM}
+        type={ActionButton.MENU_ITEM}
         itemIds={[item.id]}
         onClick={handleClose}
       />,
-      <HideButton key="hide" type={ButtonType.MENU_ITEM} item={item} />,
-      <PinButton key="pin" type={ButtonType.MENU_ITEM} item={item} />,
-      <CollapseButton key="collapse" type={ButtonType.MENU_ITEM} item={item} />,
+      <HideButton key="hide" type={ActionButton.MENU_ITEM} item={item} />,
+      <PinButton key="pin" type={ActionButton.MENU_ITEM} item={item} />,
+      <CollapseButton
+        key="collapse"
+        type={ActionButton.MENU_ITEM}
+        item={item}
+      />,
       <RecycleButton
         key="recycle"
-        type={ButtonType.MENU_ITEM}
+        type={ActionButton.MENU_ITEM}
         itemIds={[item.id]}
         onClick={handleClose}
       />,
@@ -94,12 +98,12 @@ const ItemMenu: FC<Props> = ({ item, canEdit = false }) => {
       <FavoriteButton
         size="medium"
         key="favorite"
-        type={ButtonType.MENU_ITEM}
+        type={ActionButton.MENU_ITEM}
         item={convertJs(item)}
       />,
       <CopyButton
         key="copy"
-        type={ButtonType.MENU_ITEM}
+        type={ActionButton.MENU_ITEM}
         itemIds={[item.id]}
         onClick={handleClose}
       />,
diff --git a/src/config/constants.ts b/src/config/constants.ts
index 80077a8f2..8d51d8eb7 100644
--- a/src/config/constants.ts
+++ b/src/config/constants.ts
@@ -231,11 +231,6 @@ export const HOST_MAP = {
   [Context.EXPLORER]: '',
 };
 
-export enum ButtonType {
-  MENU_ITEM = 'menuItem',
-  ICON_BUTTON = 'iconButton',
-}
-
 export const MEMBERSHIP_TABLE_HEIGHT = 400;
 export const MEMBERSHIP_TABLE_ROW_HEIGHT = 75;
 
diff --git a/src/utils/itemExtra.ts b/src/utils/itemExtra.ts
index e82e09412..2b9dcb439 100644
--- a/src/utils/itemExtra.ts
+++ b/src/utils/itemExtra.ts
@@ -4,12 +4,13 @@ import {
   EmbeddedLinkItemExtra,
   EmbeddedLinkItemExtraProperties,
   FileItemProperties,
+  ItemLoginSchema,
   ItemSettings,
   ItemType,
   LocalFileItemExtra,
   S3FileItemExtra,
 } from '@graasp/sdk';
-import { ItemLogin, ItemLoginSchema } from '@graasp/ui/dist/types';
+import { ItemLogin } from '@graasp/sdk/frontend';
 
 export const buildFileExtra = (
   file: FileItemProperties,
diff --git a/yarn.lock b/yarn.lock
index 5c254883c..a5bceb5de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2476,9 +2476,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@graasp/ui@npm:1.0.0":
-  version: 1.0.0
-  resolution: "@graasp/ui@npm:1.0.0"
+"@graasp/ui@npm:2.0.0":
+  version: 2.0.0
+  resolution: "@graasp/ui@npm:2.0.0"
   dependencies:
     "@graasp/sdk": 0.6.0
     clsx: 1.1.1
@@ -2511,7 +2511,7 @@ __metadata:
       optional: true
     ag-grid-react:
       optional: true
-  checksum: 717c6ce2c03abd338fc13a7e98c85117b520aeff716824940bf63cf1902e608b9910f4c30b6be061e440c113ea0e7f4772c9ac94ee4bd665cec0828ef4b4f9fe
+  checksum: 0bfd67465c0b572732329e8d4b493f5113d8dfb4a5a51ec5a5b541f11f8425b2dd38032c5f54eccf798f400998a9effb8ed5a0526c2dd0fc6a6d31e26c87a42c
   languageName: node
   linkType: hard
 
@@ -9934,7 +9934,7 @@ __metadata:
     "@graasp/query-client": 0.3.0
     "@graasp/sdk": 0.6.0
     "@graasp/translations": 1.7.0
-    "@graasp/ui": 1.0.0
+    "@graasp/ui": 2.0.0
     "@graasp/websockets": "github:graasp/graasp-websockets.git"
     "@mui/icons-material": 5.11.0
     "@mui/lab": 5.0.0-alpha.117