diff --git a/common/services/ASC.Studio.Notify/Program.cs b/common/services/ASC.Studio.Notify/Program.cs index 17aed4d97c..7e38916590 100644 --- a/common/services/ASC.Studio.Notify/Program.cs +++ b/common/services/ASC.Studio.Notify/Program.cs @@ -14,7 +14,7 @@ using ASC.Web.Studio.Core.Notify; using Autofac; using Autofac.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/packages/asc-web-components/submenu/README.md b/packages/asc-web-components/submenu/README.md new file mode 100644 index 0000000000..8b1a60882d --- /dev/null +++ b/packages/asc-web-components/submenu/README.md @@ -0,0 +1,71 @@ +# Submenu + +### Usage + +```js +import Submenu from "@appserver/components/submenu"; +``` + +```jsx + + ), + }, + { + id: "ToggleButton", + name: "Toggle Button", + content: ( + {}} + /> + ), + }, + ]} + startSelect={1} +/> +``` + +#### Data is an array of objects with following fields: + +- id - unique id +- name - header in submenu +- content - HTML object that will be rendered under submenu + +##### Example: + +```jsx +{ + id: "FileInput", + name: "File Input", + content: ( + + ), +}, +``` + +### Properties + +| Props | Type | Required | Values | Default | Description | +| ------------- | :-------------: | :------: | :----: | :-----: | ----------------------------------------------------------------------------- | +| `data` | `array` | ✅ | - | - | List of elements | +| `startSelect` | `obj`, `number` | - | - | 0 | Object from data that will be chosen first **OR** Its index in **data** array | diff --git a/packages/asc-web-components/submenu/autoOffset.js b/packages/asc-web-components/submenu/autoOffset.js new file mode 100644 index 0000000000..595ace8950 --- /dev/null +++ b/packages/asc-web-components/submenu/autoOffset.js @@ -0,0 +1,126 @@ +import { tablet } from "../utils/device"; +import DomHelpers from "../utils/domHelpers"; + +const paddingGap = 14; +const flexGap = 4; +const offset = 32; +const wrapperPadding = DomHelpers.getViewport() <= tablet ? 16 : 20; + +export const countAutoOffset = (data, submenuItemsRef) => { + const [marker, itemsAndGaps, itemOnMarker] = countParams( + data, + submenuItemsRef + ); + + if (itemOnMarker === undefined) return 0; + if ( + itemOnMarker.type === "gap" && + itemOnMarker !== itemsAndGaps[itemsAndGaps.length - 1] + ) + return itemOnMarker.end - marker + offset - wrapperPadding; + if (itemOnMarker.type === "item" && marker - itemOnMarker.start < 32) { + return -(marker - itemOnMarker.start - offset) - wrapperPadding; + } + if ( + itemOnMarker.type === "item" && + itemOnMarker.end - marker < 7.5 && + itemOnMarker !== itemsAndGaps[itemsAndGaps.length - 2] + ) { + return itemOnMarker.end - marker + offset * 2 - wrapperPadding; + } + return 0; +}; + +export const countAutoFocus = (itemId, data, submenuItemsRef) => { + const [marker, itemsAndGaps, itemOnMarker] = countParams( + data, + submenuItemsRef + ); + + const [focusedItem] = itemsAndGaps.filter((obj) => obj.id === itemId); + const submenuWidth = submenuItemsRef.current.offsetWidth; + + if (itemOnMarker.id && focusedItem.id === itemOnMarker.id) + return focusedItem.end - marker; + if ( + focusedItem.start < marker - submenuWidth || + focusedItem.start - offset < marker - submenuWidth + ) + return focusedItem.start - marker + submenuWidth - wrapperPadding - offset; + return 0; +}; + +const countParams = (data, submenuItemsRef) => { + const refCurrent = submenuItemsRef.current; + + const texts = data.map((d) => countText(d.name)); + const itemsAndGaps = countItemsAndGaps(texts); + + const submenuWidth = refCurrent.offsetWidth; + const marker = refCurrent.scrollLeft + submenuWidth - wrapperPadding; + + const [itemOnMarker] = itemsAndGaps.filter( + (obj) => obj.start < marker && marker < obj.end + ); + + return [marker, itemsAndGaps, itemOnMarker]; +}; + +const countText = (text) => { + const inputText = text; + const font = "600 13px open sans"; + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + context.font = font; + return { id: text, width: context.measureText(inputText).width }; +}; + +const countItemsAndGaps = (texts) => { + const result = []; + + texts.forEach(({ id, width }) => { + if (!result.length) + result.push( + { + type: "gap", + length: paddingGap, + start: 0, + end: paddingGap + wrapperPadding, + }, + { + id: id, + type: "item", + length: width, + start: paddingGap, + end: paddingGap + width, + } + ); + else { + const lastItem = result[result.length - 1]; + result.push( + { + type: "gap", + length: paddingGap * 2 + flexGap, + start: lastItem.end, + end: lastItem.end + paddingGap * 2 + flexGap, + }, + { + id: id, + type: "item", + length: width, + start: lastItem.end + paddingGap * 2 + flexGap, + end: lastItem.end + paddingGap * 2 + flexGap + width, + } + ); + } + }); + + result.push({ + type: "gap", + length: paddingGap, + start: result[result.length - 1].end, + end: result[result.length - 1].end + paddingGap + wrapperPadding, + }); + + return result; +}; diff --git a/packages/asc-web-components/submenu/data.js b/packages/asc-web-components/submenu/data.js new file mode 100644 index 0000000000..998027aeb9 --- /dev/null +++ b/packages/asc-web-components/submenu/data.js @@ -0,0 +1,89 @@ +import React from "react"; +import FileInput from "@appserver/components/file-input"; +import Row from "@appserver/components/row"; +import Textarea from "@appserver/components/textarea"; +import Text from "../text"; + +export const data = [ + { + id: "Overview", + name: "Overview", + content: ( + {}} + placeholder="Input file" + /> + ), + }, + { + id: "Documents", + name: "Documents", + content: ( +