diff --git a/label_studio/core/static/css/login.css b/label_studio/core/static/css/login.css index 79f0a29fe749..c88106dc801f 100644 --- a/label_studio/core/static/css/login.css +++ b/label_studio/core/static/css/login.css @@ -207,9 +207,20 @@ html, body { font-family: 'Hellix', sans-serif; } -.login_page_new_ui form .lsf-button-ls.lsf-button-ls_look_primary { +.login_page_new_ui form input { + padding: 10px 15px; + border-radius: 100px; + box-sizing: border-box; + border: 1px solid var(--sand_400); +} + +.login_page_new_ui form button { --button-color: var(--color-neutral-inverted-content); border-radius: 4rem; + background: var(--sand_900); + color: white; + padding: 10px 0; + border: none; background: var(--color-neutral-inverted-surface); } @@ -270,6 +281,10 @@ label{ display: block; } +.login-button { + font-size: 16px; +} + .form-group { display: block; margin-bottom: .5rem; diff --git a/label_studio/core/static/css/uikit.css b/label_studio/core/static/css/uikit.css new file mode 100644 index 000000000000..dd7d998049fd --- /dev/null +++ b/label_studio/core/static/css/uikit.css @@ -0,0 +1,250 @@ +body { + --border-color: rgba(0, 0, 0, 0.15); + --color-error: #d00; +} + +.full_content { + padding: 20px; +} + +.full_content > header { + font-size: 1.5em; + margin-bottom: 20px; +} + +.field { + display: grid; + grid-template-columns: 92px auto; + align-items: center; + gap: 16px; + row-gap: 4px; +} + +.field--wide { + display: block; +} + +.field--wide > *:not(:first-child) { + display: block; + margin-top: 4px; + width: 100%; + box-sizing: border-box; +} + +.field > * { + grid-column: 2; +} + +.field > label { + grid-column: 1; +} + +.field .actions { + display: flex; + gap: 10px; +} + +.field.error { + background: unset; +} + +.field.error input { + color: var(--color-error); +} + +span.error { + background: unset; + color: var(--color-error); +} + +/** +* [data-ignore-uikit] allows skip styling from this file +* uikit is a redundant legacy and it messes up the styles +* in a lot of places by using global styles for elements +* like inputs and buttonts. if your button looks weird, try +* adding data-ignore-uikit. most probably it will help +*/ + +input:not([data-ignore-uikit]), +textarea:not([data-ignore-uikit]) { + font: inherit; +} + +a.button:not([data-ignore-uikit]), +input:not([data-ignore-uikit]), +textarea:not([data-ignore-uikit]) { + border: 1px solid var(--border-color); + padding: 9px 16px; +} + +input[type=text][disabled]:not([data-ignore-uikit]) { + background: rgba(0, 0, 0, 0.03); + color: rgba(0, 0, 0, 0.4) +} + +a.button:not([data-ignore-uikit]), +input[type=button]:not([data-ignore-uikit]), +input[type=submit]:not([data-ignore-uikit]), +input[type=reset]:not([data-ignore-uikit]) { + background: white; + border-radius: 5px; + /* min-width: 140px; */ + text-align: center; + line-height: 1; + font-weight: 500; + color: #555; + cursor: pointer; +} + +a.button:hover, +input[type=button]:hover, +input[type=submit]:hover, +input[type=reset]:hover { + filter: brightness(0.98); +} + +a.button.primary, +input[type=button].primary, +input[type=submit].primary, +input[type=reset].primary { + background: #1890FF; + border: 1px solid #1890FF; + color: white; +} + +input[type=button].danger, +input[type=submit].danger, +input[type=reset].danger { + color: var(--color-error); +} + +input[type=button][disabled]:not([data-ignore-uikit]), +input[type=submit][disabled]:not([data-ignore-uikit]), +input[type=reset][disabled]:not([data-ignore-uikit]) { + background: rgba(0, 0, 0, 0.2); + border-color: transparent; + cursor: not-allowed; +} + +ul.toggle { + display: flex; + justify-content: stretch; + list-style: none; + padding: 4px; + margin: 0; + background: rgba(0, 0, 0, 0.05); + box-shadow: inset 0px 1px 0px rgb(0 0 0 / 5%), inset 0px 0px 0px 1px rgb(0 0 0 / 5%); + border-radius: 8px; + font-weight: 500; +} + +ul.toggle--big { + font-size: 16px; +} + +ul.toggle--big li { + padding: 4px 20px; +} + +ul.toggle li { + cursor: pointer; + color: rgba(0, 0, 0, 0.6); + border-radius: 4px; + padding: 2px 16px; + flex-grow: 1; + text-align: center; +} + +ul.toggle li.active { + color: black; + background: white; + box-shadow: 0px 1px 0px rgb(0 0 0 / 10%), 0px 0px 0px 1px rgb(0 0 0 / 2%), 0px 5px 10px rgb(0 0 0 / 15%); +} + + +.userpic { + width: 28px; + height: 28px; + display: flex; + overflow: hidden; + position: relative; + align-items: center; + border-radius: 50%; + justify-content: center; + background: rgba(0, 0, 0, 0.04); + box-shadow: inset 0px 0px 0px 1px rgba(0, 0, 0, 0.1); + + font-size: 12px; + font-weight: bold; + color: rgba(0, 0, 0, 0.4); +} + +.userpic--medium { + width: 64px; + height: 64px; + font-size: 28px; +} + +.userpic--big { + width: 92px; + height: 92px; + font-size: 32px; +} + +.userpic--small { + width: 28px; + height: 28px; + font-size: 14px; +} + +.userpic img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + left: 0; + top: 0; +} + + +/* file upload button */ +.file-input { + border: none; + padding-left: 0; + overflow: hidden; + width: 100%; +} + +.file-input::-webkit-file-upload-button { + visibility: hidden; +} + +.file-input::before { + content: 'Choose file'; + display: inline-block; + background: none; + border: 1px solid #CCC; + border-radius: 3px; + padding: 5px 8px; + outline: none; + white-space: nowrap; + -webkit-user-select: none; + cursor: pointer; + text-shadow: 1px 1px #fff; + font-weight: 400; + font-size: 1em; +} + +.file-input:hover::before { + border-color: #333; +} + +.file-input:active::before { + background: #e3e3e3; +} + +.file-input:active { + border: none; + box-shadow: none; + outline: none; +} diff --git a/label_studio/users/templates/users/new-ui/user_login.html b/label_studio/users/templates/users/new-ui/user_login.html index 0e169c356773..3541d48dfa70 100644 --- a/label_studio/users/templates/users/new-ui/user_login.html +++ b/label_studio/users/templates/users/new-ui/user_login.html @@ -36,7 +36,7 @@

Log in

- + {% if not settings.DISABLE_SIGNUP_WITHOUT_LINK %} diff --git a/web/apps/labelstudio/src/components/Button/Button.jsx b/web/apps/labelstudio/src/components/Button/Button.jsx deleted file mode 100644 index c595ed912a2f..000000000000 --- a/web/apps/labelstudio/src/components/Button/Button.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; -import { Block, Elem } from "../../utils/bem"; -import { isDefined } from "../../utils/helpers"; -import { FormSubmissionContext } from "../Form/FormContext"; -import "./Button.scss"; - -export const Button = React.forwardRef( - ({ children, type, extra, className, rawClassName, size, waiting, icon, tag, look, ...rest }, ref) => { - const finalTag = tag ?? (rest.href ? "a" : "button"); - - const mods = { - size, - waiting, - type, - look: look ?? [], - withIcon: !!icon, - withExtra: !!extra, - }; - - const formSubmitting = React.useContext(FormSubmissionContext); - - if (formSubmitting === true) { - if (mods.look?.includes?.("primary") && type === "submit") { - mods.waiting = true; - } else { - rest.disabled = true; - } - } - - if (rest.primary) { - mods.look = "primary"; - delete rest.primary; - } - - const iconElem = React.useMemo(() => { - if (!icon) return null; - if (isDefined(icon.props.size)) return icon; - - switch (size) { - case "small": - return React.cloneElement(icon, { ...icon.props, size: 12, width: 12, height: 12 }); - case "compact": - return React.cloneElement(icon, { ...icon.props, size: 14, width: 14, height: 14 }); - default: - return icon; - } - }, [icon, size]); - - return ( - - <> - {iconElem && ( - - {iconElem} - - )} - {iconElem && children ? {children} : children} - {extra !== undefined ? {extra} : null} - - - ); - }, -); -Button.displayName = "Button"; - -Button.Group = ({ className, children, collapsed }) => { - return ( - - {children} - - ); -}; diff --git a/web/apps/labelstudio/src/components/Button/Button.scss b/web/apps/labelstudio/src/components/Button/Button.scss deleted file mode 100644 index 51942dafff80..000000000000 --- a/web/apps/labelstudio/src/components/Button/Button.scss +++ /dev/null @@ -1,380 +0,0 @@ -.button-ls { - --button-color: var(--color-neutral-content); - --button-background-color: var(--color-neutral-surface-hover); - --button-background-image: none; - --button-shadow: inset 0 -1px 1px rgba(var(--color-neutral-shadow-raw) / 4%); - --button-content-align: center; - --button-content-justify: center; - --button-events: all; - --button-extra-color: var(--color-primary-surface-content-subtle); - --button-height: 40px; - --button-width: auto; - --button-min-width: 0; - --icon-size: 16px; - --button-padding: 0 16px; - --button-radius: var(--corner-radius-smaller); - --button-font-size: var(--font-size-400); - --button-margin-left: 0; - --button-margin-right: 0; - --button-border: 1px solid var(--color-neutral-border); - --button-gap: 6px; - - border: var(--button-border); - cursor: pointer; - outline: none; - flex-shrink: 0; - display: inline-flex; - text-align: center; - position: relative; - box-sizing: border-box; - box-shadow: var(--button-shadow); - color: var(--button-color) !important; - font-size: var(--button-font-size); - font-family: var(--font-sans); - font-weight: var(--font-weight-500); - line-height: var(--font-line-height-medium); - width: var(--button-width); - height: var(--button-height); - min-width: var(--button-min-width); - padding: var(--button-padding); - align-items: var(--button-content-align); - justify-content: var(--button-content-justify); - margin-left: var(--button-margin-left); - margin-right: var(--button-margin-right); - background-color: var(--button-background-color); - background-image: var(--button-background-image); - pointer-events: var(--button-events); - border-radius: var(--button-radius); - text-decoration: none; - gap: var(--button-gap); - transition: all 150ms ease-out; - - &:hover:not(:disabled, .button-ls_waiting, .button-ls_look_danger, .button-ls_look_primary) { - --button-color: var(--color-neutral-content); - --button-background-color: var(--color-neutral-surface-hover); - --button-border-color: var(--color-neutral-border-bold); - - background-color: var(--button-background-color); - border-color: var(--button-border-color); - } - - &:active { - color: var(--button-color); - - --button-background-image: linear-gradient(0deg, transparent, rgba(var(--color-neutral-shadow-raw) / 4%)); - } - - &:focus { - outline: none; - box-shadow: 0 0 0 4px var(--color-primary-focus-outline); - } - - &_waiting, - &:disabled, - &_disabled { - --button-color: var(--color-neutral-content-subtlest); - --button-background-color: var(--color-neutral-background); - --button-events: none; - - border: 1px solid var(--color-neutral-border); - - & svg { - color: var(--color-neutral-content-subtlest) !important; - } - } - - &__extra { - --button-font-size: var(--font-size-300); - - line-height: var(--font-line-height-small); - display: flex; - color: var(--button-extra-color); - align-items: center; - margin-left: 7px; - margin-right: -7px; - } - - &__label { - padding: 0 var(--spacing-tight); - } - - &__icon { - display: flex; - width: var(--icon-size); - height: var(--icon-size); - align-items: center; - - &:only-child { - flex: 1; - - --button-content-align: center; - --button-content-justify: center; - } - - svg { - width: 100%; - height: 100%; - } - } - - &_align { - &_left { - --button-content-justify: flex-start; - } - - &_right { - --button-content-justify: flex-end; - } - } - - &_type { - &_text, - &_link { - --button-padding: 0; - - min-width: 0; - - --button-background-color: none; - - border: none; - } - - &_link { - --button-color: var(--primary_link); - } - - } - - &_look { - &_primary { - --button-color: var(--color-primary-surface-content); - --button-background-color: var(--color-primary-surface); - --button-border: 1px solid var(--color-primary-border); - - box-shadow: inset 0 -1px 2px rgba(var(--color-neutral-shadow-raw) / 10%); - - &:hover:not(:disabled, .button-ls_waiting) { - --button-background-color: var(--color-primary-surface-hover); - --button-color: var(--color-primary-surface-content); - } - - &:active:not(:disabled) { - --button-background-color: var(--color-primary-content); - } - - &:focus:not(:disabled) { - box-shadow: 0 0 0 4px var(--color-primary-focus-outline); - } - } - - &_danger { - --button-color: var(--color-negative-content); - - border-color: var(--color-negative-border); - - &:hover:not(:disabled, &_waiting) { - --button-color: var(--color-neutral-content); - - border-color: var(--color-negative-border-bold); - background: var(--color-negative-emphasis-subtle); - } - } - - &_destructive { - --button-color: var(--color-negative-surface-content); - --button-background-color: var(--color-negative-surface); - - border-color: var(--color-negative-border); - - &:hover:not(:disabled, .button-ls_waiting) { - --button-color: var(--color-negative-surface-content); - - background: var(--color-negative-surface-hover); - border-color: var(--color-negative-border-bold); - } - - &:active { - --button-background-color: var(--color-negative-surface-active); - - border-color: var(--color-negative-border-bold); - } - } - - &_ghost { - --button-color: var(--color-neutral-background); - - border: none; - - --button-background-color: transparent; - } - } - - &_look_danger:disabled, - &_look_danger &_waiting, - &_look_danger &_disabled { - --button-color: var(--color-negative-content-subtlest); - } - - &_look_destructive:disabled, - &_look_destructive &_waiting, - &_look_destructive &_disabled { - --button-color: var(--color-neutral-content-subtlest); - --button-background-color: var(--color-neutral-background); - } - - &_look_primary:disabled, - &_look_primary.button-ls_disabled { - --button-color: var(--color-neutral-content-subtlest); - --button-background-color: var(--color-neutral-background); - - border: 1px solid var(--color-neutral-border); - } - - &_look_primary.button-ls_waiting { - --button-color: var(--color-primary-surface-content); - } - - &_size { - &_compact { - --button-height: 36px; - --icon-size: 16px; - --button-font-size: var(--font-size-300); - } - - &_medium { - --button-height: 32px; - --icon-size: 16px; - --button-font-size: var(--font-size-300); - } - - &_small { - --button-height: 24px; - --icon-size: 12px; - --button-font-size: var(--font-size-200); - --button-padding: 0 10px; - } - - &_large { - --button-height: 40px; - --icon-size: 28px; - --button-font-size: var(--font-size-400); - } - } - - &_size_small.button-ls__extra { - --button-margin-left: 5px; - --button-margin-right: -5px; - } - - &_size_medium.button-ls__extra { - --button-margin-left: 7px; - --button-margin-right: -7px; - } - - &_size_compact.button-ls__extra { - --button-margin-left: 7px; - --button-margin-right: -7px; - } - - &_size_large.button-ls__extra { - --button-margin-left: 10px; - --button-margin-right: -10px; - } - - &_withIcon:not(.button-ls_type_link).button-ls_noContent { - border: none; - } - - &_withIcon:not(.button-ls_type_link, .button-ls_noContent) { - --button-padding: 0 14px; - } - - &_withIcon &_size_small:not(.button-ls_noContent) { - --button-padding: 0 10px; - } - - &_withIcon { - --button-content-justify: space-between; - } - - &_withIcon:not(.button-ls_noContent) { - --button-padding: 0 14px; - } - - &_withIcon.button-ls_size_small { - --button-padding: 0 10px; - } - - &_waiting { - pointer-events: none; - background-repeat: repeat; - background-position: 40px; - background-size: 37px 100%; - - --button-background-image: var(--button-waiting-animation-bg); - --button-background-color: var(--color-neutral-background); - - animation: button-waiting 1s linear infinite; - } - - &_waiting.button-ls_look_primary { - --button-background-image: var(--primary-button-waiting-animation-bg); - --button-background-color: var(--color-primary-surface); - } - - &_waiting.button-ls_look_danger, - &_waiting.button-ls_look_destructive { - --button-background-color: var(--color-negative-emphasis-subtle); - - background-image: var(--negative-button-waiting-animation-bg); - } - - &_size_small &__icon { - width: 12px; - - &:not(:only-child) { - --button-margin-right: 8px; - } - } - - &_noContent { - --button-min-width: var(--button-height); - --button-padding: 0; - - border: none; - } -} - -.button-group-ls { - display: flex; - - &:not(.button-group-ls_collapsed) { - .button-ls+.button-ls { - --button-margin-left: 16px; - } - } - - &_collapsed { - .button-ls:first-child { - --button-radius: 5px 0 0 5px; - } - - .button-ls:last-child { - --button-radius: 0 5px 5px 0; - } - - .button-ls:not(:first-child, :last-child) { - --button-radius: 0; - } - } -} - -@keyframes button-waiting { - 0% { - background-position: 0 0; - } - - 100% { - background-position: 37px 0; - } -} diff --git a/web/apps/labelstudio/src/components/Error/Error.jsx b/web/apps/labelstudio/src/components/Error/Error.jsx index 152931a6b520..affda1bf0c97 100644 --- a/web/apps/labelstudio/src/components/Error/Error.jsx +++ b/web/apps/labelstudio/src/components/Error/Error.jsx @@ -3,7 +3,7 @@ import sanitizeHtml from "sanitize-html"; import { IconSlack } from "@humansignal/icons"; import { Block, Elem } from "../../utils/bem"; import { absoluteURL, copyText } from "../../utils/helpers"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { Space } from "../Space/Space"; import "./Error.scss"; @@ -93,12 +93,25 @@ export const ErrorWrapper = ({ {preparedStackTrace && ( - )} - {onGoBack && } - {onReload && } + {onGoBack && ( + + )} + {onReload && ( + + )} diff --git a/web/apps/labelstudio/src/components/Form/Form.jsx b/web/apps/labelstudio/src/components/Form/Form.jsx index bd3e7c0195c2..aa301efdc1f6 100644 --- a/web/apps/labelstudio/src/components/Form/Form.jsx +++ b/web/apps/labelstudio/src/components/Form/Form.jsx @@ -5,7 +5,7 @@ import { MultiProvider } from "../../providers/MultiProvider"; import { Block, cn, Elem } from "../../utils/bem"; import { debounce } from "../../utils/debounce"; import { isDefined, objectClean } from "../../utils/helpers"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { Oneof } from "../Oneof/Oneof"; import { Space } from "../Space/Space"; import { Counter, Input, Select, Toggle } from "./Elements"; @@ -524,7 +524,7 @@ Form.Builder = React.forwardRef( {children} {props.autosubmit !== true && withActions === true && ( - diff --git a/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.scss b/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.scss index 3686c52d57ee..26a32468de4c 100644 --- a/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.scss +++ b/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.scss @@ -47,45 +47,6 @@ } } - &__dismiss { - width: 24px; - height: 24px; - padding: 0; - border: none; - display: flex; - box-shadow: none; - justify-content: center; - align-items: center; - - svg { - width: 14px; - height: 14px; - - - .spike-fill { - fill: var(--color-neutral-surface); - display: none; - } - - .spike-stroke { - fill: var(--color-neutral-border); - display: none; - } - } - - svg > path { - stroke: var(--color-neutral-content-subtlest); - } - - &:hover { - background: var(--color-primary-emphasis-subtle); - - svg path { - stroke: var(--color-primary-surface-content); - } - } - } - &__heidi { margin-top: -12px; padding-left: 16px; @@ -99,4 +60,4 @@ } } -} \ No newline at end of file +} diff --git a/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.tsx b/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.tsx index 877395b18b93..4b8cd810e207 100644 --- a/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.tsx +++ b/web/apps/labelstudio/src/components/HeidiTips/HeidiTip.tsx @@ -2,10 +2,9 @@ import { type FC, type MouseEvent, useCallback, useMemo } from "react"; import { Block, Elem } from "../../utils/bem"; import { IconCross } from "@humansignal/icons"; import "./HeidiTip.scss"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { HeidiSpeaking } from "../../assets/images"; import type { HeidiTipProps, Tip } from "./types"; -import { Tooltip } from "@humansignal/ui"; import { createURL } from "./utils"; const HeidiLink: FC<{ link: Tip["link"]; onClick: () => void }> = ({ link, onClick }) => { @@ -37,13 +36,9 @@ export const HeidiTip: FC = ({ tip, onDismiss, onLinkClick }) => {tip.title} {tip.closable && ( - /* @ts-ignore-next-line */ - - {/* @ts-ignore-next-line */} - - - - + )} diff --git a/web/apps/labelstudio/src/components/Modal/Modal.jsx b/web/apps/labelstudio/src/components/Modal/Modal.jsx index e79e67450ef0..6e037d2a7ed3 100644 --- a/web/apps/labelstudio/src/components/Modal/Modal.jsx +++ b/web/apps/labelstudio/src/components/Modal/Modal.jsx @@ -5,7 +5,7 @@ import { ConfigProvider } from "../../providers/ConfigProvider"; import { CurrentUserProvider } from "../../providers/CurrentUser"; import { MultiProvider } from "../../providers/MultiProvider"; import { cn } from "../../utils/bem"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { Space } from "../Space/Space"; import { Modal } from "./ModalPopup"; import { ToastProvider, ToastViewport } from "@humansignal/ui"; @@ -78,8 +78,10 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro onCancel?.(); modal.close(); }} - size="compact" + size="small" + look="outlined" autoFocus + className="min-w-[120px]" > {cancelText ?? "Cancel"} @@ -89,8 +91,9 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro onOk?.(); modal.close(); }} - size="compact" - look={buttonLook ?? "primary"} + size="small" + variant={buttonLook ?? "primary"} + className="min-w-[120px]" > {okText ?? "OK"} @@ -113,6 +116,7 @@ export const info = ({ okText, onOkPress, ...props }) => { }} look="primary" size="compact" + className="min-w-[120px]" > {okText ?? "OK"} diff --git a/web/apps/labelstudio/src/components/Modal/ModalPopup.jsx b/web/apps/labelstudio/src/components/Modal/ModalPopup.jsx index b5512864115e..a97c18a344f2 100644 --- a/web/apps/labelstudio/src/components/Modal/ModalPopup.jsx +++ b/web/apps/labelstudio/src/components/Modal/ModalPopup.jsx @@ -3,7 +3,7 @@ import { createPortal } from "react-dom"; import { IconCross } from "@humansignal/icons"; import { BemWithSpecifiContext, cn } from "../../utils/bem"; import { aroundTransition } from "@humansignal/core/lib/utils/transition"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import "./Modal.scss"; const { Block, Elem } = BemWithSpecifiContext(); @@ -101,7 +101,11 @@ export class Modal extends React.Component { {!bare && ( {this.state.title} - {this.props.allowClose !== false && } />} + {this.props.allowClose !== false && ( + + )} )} diff --git a/web/apps/labelstudio/src/components/index.js b/web/apps/labelstudio/src/components/index.js index 7e5c01d485e7..446afa282b8e 100644 --- a/web/apps/labelstudio/src/components/index.js +++ b/web/apps/labelstudio/src/components/index.js @@ -1,5 +1,4 @@ export { Breadcrumbs } from "./Breadcrumbs/Breadcrumbs"; -export { Button } from "./Button/Button"; export { Card } from "./Card/Card"; export { Columns } from "./Columns/Columns"; export { Dropdown } from "./Dropdown/Dropdown"; diff --git a/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx b/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx index 3277174f5c03..2e6afc575b92 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx +++ b/web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx @@ -1,7 +1,8 @@ import React, { useEffect, useMemo, useState } from "react"; import CM from "codemirror"; - -import { Button, ToggleItems } from "../../../components"; +import { Button } from "@humansignal/ui"; +import { IconTrash } from "@humansignal/icons"; +import { ToggleItems } from "../../../components"; import { Form, Input } from "../../../components/Form"; import { useAPI } from "../../../providers/ApiProvider"; import { Block, cn, Elem } from "../../../utils/bem"; @@ -40,36 +41,28 @@ const Label = ({ label, template, color }) => { return (
  • - - {value} - + +
  • ); }; @@ -107,7 +100,7 @@ const ConfigureControl = ({ control, template }) => { onKeyPress={onKeyPress} className="lsf-textarea-ls p-2 px-3" /> - @@ -489,12 +482,12 @@ const Configurator = ({

    Labeling Interface{hasChanges ? " *" : ""}

    @@ -556,7 +549,13 @@ const Configurator = ({ )} - {isFF(FF_UNSAVED_CHANGES) && } diff --git a/web/apps/labelstudio/src/pages/CreateProject/Config/Config.scss b/web/apps/labelstudio/src/pages/CreateProject/Config/Config.scss index 0145bdf595dc..d5d22126aa01 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/Config/Config.scss +++ b/web/apps/labelstudio/src/pages/CreateProject/Config/Config.scss @@ -404,6 +404,7 @@ $scroll-width: 5px; .configure__label { display: flex; align-items: stretch; + justify-content: space-between; position: relative; &:not(:first-child) { diff --git a/web/apps/labelstudio/src/pages/CreateProject/Config/TemplatesList.jsx b/web/apps/labelstudio/src/pages/CreateProject/Config/TemplatesList.jsx index 4aeb3efd58e4..6f8c9bf523c2 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/Config/TemplatesList.jsx +++ b/web/apps/labelstudio/src/pages/CreateProject/Config/TemplatesList.jsx @@ -4,6 +4,7 @@ import { useAPI } from "../../../providers/ApiProvider"; import { cn } from "../../../utils/bem"; import "./Config.scss"; import { IconInfo } from "@humansignal/icons"; +import { Button } from "@humansignal/ui"; const listClass = cn("templates-list"); @@ -70,9 +71,17 @@ export const TemplatesList = ({ selectedGroup, selectedRecipe, onCustomTemplate, ))} - +
    {!templates && } diff --git a/web/apps/labelstudio/src/pages/CreateProject/Config/UnsavedChanges.tsx b/web/apps/labelstudio/src/pages/CreateProject/Config/UnsavedChanges.tsx index 9954bbb8f40d..f2c4597ed931 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/Config/UnsavedChanges.tsx +++ b/web/apps/labelstudio/src/pages/CreateProject/Config/UnsavedChanges.tsx @@ -1,11 +1,11 @@ import { useCallback, useRef, useState } from "react"; -import { Button } from "../../../components"; +import { Button } from "@humansignal/ui"; import { LeaveBlocker, type LeaveBlockerCallbacks } from "../../../components/LeaveBlocker/LeaveBlocker"; import { modal } from "../../../components/Modal/Modal"; import { Space } from "../../../components/Space/Space"; type SaveAndLeaveButtonProps = { - onSave: () => void; + onSave: () => Promise; text?: string; }; const SaveAndLeaveButton = ({ onSave, text = "Save and Leave" }: SaveAndLeaveButtonProps) => { @@ -16,7 +16,7 @@ const SaveAndLeaveButton = ({ onSave, text = "Save and Leave" }: SaveAndLeaveBut setSaving(false); }, [onSave]); return ( - ); @@ -57,11 +57,12 @@ export const unsavedChangesModal = ({ footer: ( diff --git a/web/apps/labelstudio/src/pages/CreateProject/CreateProject.jsx b/web/apps/labelstudio/src/pages/CreateProject/CreateProject.jsx index 35eda72b7dae..629a10fcfb8c 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/CreateProject.jsx +++ b/web/apps/labelstudio/src/pages/CreateProject/CreateProject.jsx @@ -1,7 +1,8 @@ import { EnterpriseBadge, Select } from "@humansignal/ui"; import React from "react"; import { useHistory } from "react-router"; -import { Button, ToggleItems } from "../../components"; +import { ToggleItems } from "../../components"; +import { Button } from "@humansignal/ui"; import { Modal } from "../../components/Modal/Modal"; import { Space } from "../../components/Space/Space"; import { HeidiTips } from "../../components/HeidiTips/HeidiTips"; @@ -201,12 +202,19 @@ export const CreateProject = ({ onClose }) => { - @@ -362,9 +365,9 @@ export const ImportPage = ({ {ff.isActive(ff.FF_SAMPLE_DATASETS) && ( @@ -503,14 +506,14 @@ export const ImportPage = ({ ) : ff.isFF(ff.FF_JSON_PREVIEW) ? ( Set up your{" "} - {" "} + {" "} to generate an input preview. ) : null} diff --git a/web/apps/labelstudio/src/pages/CreateProject/Import/Import.scss b/web/apps/labelstudio/src/pages/CreateProject/Import/Import.scss index 59c9fed220f0..81f581343cb2 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/Import/Import.scss +++ b/web/apps/labelstudio/src/pages/CreateProject/Import/Import.scss @@ -47,7 +47,7 @@ button { margin-left: -1px; - border-radius: 0 5px 5px 0; + border-radius: 0 var(--corner-radius-smaller) var(--corner-radius-smaller) 0; background: var(--color-primary-surface); color: var(--color-primary-surface-content); cursor: pointer; @@ -117,27 +117,6 @@ background: rgb(255 255 255 / 50%); } - &__upload-button { - display: flex; - align-items: center; - padding: 8px; - border: 1px solid var(--color-primary-border); - background: var(--color-primary-background); - border-radius: 4px; - cursor: pointer; - color: var(--color-primary-content); - transition: all 150ms ease-out; - - &:hover { - background: var(--color-primary-emphasis-subtle); - } - } - - &__upload-icon { - height: 20px; - width: 20px; - } - &__info-icon { vertical-align: -5px; height: 20px; diff --git a/web/apps/labelstudio/src/pages/CreateProject/Import/ImportModal.jsx b/web/apps/labelstudio/src/pages/CreateProject/Import/ImportModal.jsx index 5bf8266f4852..f83a9e5de248 100644 --- a/web/apps/labelstudio/src/pages/CreateProject/Import/ImportModal.jsx +++ b/web/apps/labelstudio/src/pages/CreateProject/Import/ImportModal.jsx @@ -1,6 +1,6 @@ import { useCallback, useRef, useState } from "react"; import { useHistory } from "react-router"; -import { Button } from "../../../components"; +import { Button } from "@humansignal/ui"; import { Modal } from "../../../components/Modal/Modal"; import { Space } from "../../../components/Space/Space"; import { useAPI } from "../../../providers/ApiProvider"; @@ -77,10 +77,23 @@ export const Inner = () => { - - diff --git a/web/apps/labelstudio/src/pages/DataManager/DataManager.jsx b/web/apps/labelstudio/src/pages/DataManager/DataManager.jsx index 6788947fb0ae..96a4a1792c98 100644 --- a/web/apps/labelstudio/src/pages/DataManager/DataManager.jsx +++ b/web/apps/labelstudio/src/pages/DataManager/DataManager.jsx @@ -1,8 +1,8 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { generatePath, useHistory } from "react-router"; -import { NavLink } from "react-router-dom"; +import { Link, NavLink } from "react-router-dom"; import { Spinner } from "../../components"; -import { Button } from "../../components/Button/Button"; +import { Button, buttonVariant } from "@humansignal/ui"; import { modal } from "../../components/Modal/Modal"; import { Space } from "../../components/Space/Space"; import { useAPI } from "../../providers/ApiProvider"; @@ -208,7 +208,9 @@ export const DataManagerPage = ({ ...props }) => { Project was deleted or not yet created - + ) : ( <> @@ -282,7 +284,8 @@ DataManagerPage.context = ({ dmRef }) => { {project.expert_instruction && mode !== "explorer" && ( + ))} ) : null; diff --git a/web/apps/labelstudio/src/pages/ExportPage/ExportPage.jsx b/web/apps/labelstudio/src/pages/ExportPage/ExportPage.jsx index 9351af0f72ef..0da14ecbc15f 100644 --- a/web/apps/labelstudio/src/pages/ExportPage/ExportPage.jsx +++ b/web/apps/labelstudio/src/pages/ExportPage/ExportPage.jsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { useHistory } from "react-router"; -import { Button } from "../../components"; +import { Button } from "@humansignal/ui"; import { Form, Input } from "../../components/Form"; import { Modal } from "../../components/Modal/Modal"; import { Space } from "../../components/Space/Space"; @@ -132,9 +132,9 @@ export const ExportPage = () => { {downloadingMessage && "Files are being prepared. It might take some time."} - + diff --git a/web/apps/labelstudio/src/pages/ExportPage/ExportPage.scss b/web/apps/labelstudio/src/pages/ExportPage/ExportPage.scss index de50a7a97641..b540b52ee84c 100644 --- a/web/apps/labelstudio/src/pages/ExportPage/ExportPage.scss +++ b/web/apps/labelstudio/src/pages/ExportPage/ExportPage.scss @@ -1,8 +1,4 @@ .export-page { - &__finish { - width: 135px; - } - &__recent { display: grid; grid-auto-flow: rows; diff --git a/web/apps/labelstudio/src/pages/Home/HomePage.tsx b/web/apps/labelstudio/src/pages/Home/HomePage.tsx index 456b2417e6d0..644bd61fbf98 100644 --- a/web/apps/labelstudio/src/pages/Home/HomePage.tsx +++ b/web/apps/labelstudio/src/pages/Home/HomePage.tsx @@ -1,16 +1,15 @@ -import type { Page } from "../types/Page"; -import { SimpleCard, Spinner } from "@humansignal/ui"; import { IconExternal, IconFolderAdd, IconHumanSignal, IconUserAdd, IconFolderOpen } from "@humansignal/icons"; -import { HeidiTips } from "../../components/HeidiTips/HeidiTips"; +import { Heading, Sub } from "@humansignal/typography"; +import { Button, SimpleCard, Spinner } from "@humansignal/ui"; import { useQuery } from "@tanstack/react-query"; -import { useAPI } from "../../providers/ApiProvider"; import { useState } from "react"; -import { CreateProject } from "../CreateProject/CreateProject"; -import { InviteLink } from "../Organization/PeoplePage/InviteLink"; -import { Heading, Sub } from "@humansignal/typography"; import { useHistory } from "react-router"; import { Link } from "react-router-dom"; -import { Button } from "../../components"; +import { HeidiTips } from "../../components/HeidiTips/HeidiTips"; +import { useAPI } from "../../providers/ApiProvider"; +import { CreateProject } from "../CreateProject/CreateProject"; +import { InviteLink } from "../Organization/PeoplePage/InviteLink"; +import type { Page } from "../types/Page"; const PROJECTS_TO_SHOW = 10; @@ -92,10 +91,12 @@ export const HomePage: Page = () => { return ( ); @@ -120,7 +121,7 @@ export const HomePage: Page = () => { ) : isError ? (
    can't load projects
    - ) : isSuccess && data.results.length === 0 ? ( + ) : isSuccess && data && data.results.length === 0 ? (
    {
    Create your first project Import your data and set up the labeling interface to start annotating -
    - ) : isSuccess && data.results.length > 0 ? ( + ) : isSuccess && data && data.results.length > 0 ? (
    {data.results.map((project) => { return ; diff --git a/web/apps/labelstudio/src/pages/Organization/Models/@components/EmptyList.tsx b/web/apps/labelstudio/src/pages/Organization/Models/@components/EmptyList.tsx index c415feaf0abb..f90bca71d124 100644 --- a/web/apps/labelstudio/src/pages/Organization/Models/@components/EmptyList.tsx +++ b/web/apps/labelstudio/src/pages/Organization/Models/@components/EmptyList.tsx @@ -1,4 +1,4 @@ -import { Button } from "apps/labelstudio/src/components"; +import { Button } from "@humansignal/ui"; import { Block, Elem } from "apps/labelstudio/src/utils/bem"; import type { FC } from "react"; import "./EmptyList.scss"; @@ -13,7 +13,7 @@ export const EmptyList: FC = () => { Create a Model Build a high quality model to auto-label your data using LLMs - + ); diff --git a/web/apps/labelstudio/src/pages/Organization/Models/ModelsPage.tsx b/web/apps/labelstudio/src/pages/Organization/Models/ModelsPage.tsx index a38f05b11484..67586ec91a6b 100644 --- a/web/apps/labelstudio/src/pages/Organization/Models/ModelsPage.tsx +++ b/web/apps/labelstudio/src/pages/Organization/Models/ModelsPage.tsx @@ -1,7 +1,7 @@ -import { Button } from "apps/labelstudio/src/components"; -import type { Page } from "../../types/Page"; -import { Space } from "apps/labelstudio/src/components/Space/Space"; +import { buttonVariant, Space } from "@humansignal/ui"; import { Block } from "apps/labelstudio/src/utils/bem"; +import { Link } from "react-router-dom"; +import type { Page } from "../../types/Page"; import { EmptyList } from "./@components/EmptyList"; export const ModelsPage: Page = () => { @@ -19,9 +19,9 @@ ModelsPage.path = "/models"; ModelsPage.context = () => { return ( - + ); }; diff --git a/web/apps/labelstudio/src/pages/Organization/PeoplePage/InviteLink.tsx b/web/apps/labelstudio/src/pages/Organization/PeoplePage/InviteLink.tsx index 65faf804b436..30ef9e3760dd 100644 --- a/web/apps/labelstudio/src/pages/Organization/PeoplePage/InviteLink.tsx +++ b/web/apps/labelstudio/src/pages/Organization/PeoplePage/InviteLink.tsx @@ -7,7 +7,7 @@ import { API } from "apps/labelstudio/src/providers/ApiProvider"; import { atomWithQuery } from "jotai-tanstack-query"; import { useAtomValue } from "jotai"; import { Modal } from "apps/labelstudio/src/components/Modal/ModalPopup"; -import { Button } from "apps/labelstudio/src/components"; +import { Button } from "@humansignal/ui"; const linkAtom = atomWithQuery(() => ({ queryKey: ["invite-link"], @@ -84,12 +84,17 @@ const InvitationFooter = () => { return ( - - diff --git a/web/apps/labelstudio/src/pages/Organization/PeoplePage/PeoplePage.jsx b/web/apps/labelstudio/src/pages/Organization/PeoplePage/PeoplePage.jsx index a2185db58a18..a3c632d9b981 100644 --- a/web/apps/labelstudio/src/pages/Organization/PeoplePage/PeoplePage.jsx +++ b/web/apps/labelstudio/src/pages/Organization/PeoplePage/PeoplePage.jsx @@ -1,5 +1,5 @@ import { useCallback, useMemo, useRef, useState } from "react"; -import { Button } from "../../../components"; +import { Button } from "@humansignal/ui"; import { Description } from "../../../components/Description/Description"; import { Input } from "../../../components/Form"; import { HeidiTips } from "../../../components/HeidiTips/HeidiTips"; @@ -101,8 +101,16 @@ export const PeoplePage = () => { - {isFF(FF_AUTH_TOKENS) && } - + )} + diff --git a/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.jsx b/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.jsx index e251ce482a34..ce44cb4d2f86 100644 --- a/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.jsx +++ b/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.jsx @@ -1,8 +1,7 @@ import { format } from "date-fns"; import { NavLink } from "react-router-dom"; import { IconCross } from "@humansignal/icons"; -import { Userpic } from "@humansignal/ui"; -import { Button } from "../../../components"; +import { Userpic, Button } from "@humansignal/ui"; import { Block, Elem } from "../../../utils/bem"; import "./SelectedUser.scss"; @@ -32,9 +31,14 @@ export const SelectedUser = ({ user, onClose }) => { return ( - + diff --git a/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.scss b/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.scss index dbdb8faca730..77a7a6999066 100644 --- a/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.scss +++ b/web/apps/labelstudio/src/pages/Organization/PeoplePage/SelectedUser.scss @@ -9,23 +9,6 @@ border-radius: 4px; min-height: 100%; - &__close { - top: 20px; - right: 24px; - width: 32px; - height: 32px; - position: absolute; - - --button-color: var(--color-primary-content); - - border: none; - - &:hover { - color: var(--color-primary-content); - background-color: var(--color-primary-emphasis-subtle); - } - } - &__header { display: grid; grid-template: auto / 64px auto; @@ -86,4 +69,4 @@ background-color: var(--color-primary-emphasis-subtle); } } -} \ No newline at end of file +} diff --git a/web/apps/labelstudio/src/pages/Projects/Projects.jsx b/web/apps/labelstudio/src/pages/Projects/Projects.jsx index 094ce343f45f..0346b081120b 100644 --- a/web/apps/labelstudio/src/pages/Projects/Projects.jsx +++ b/web/apps/labelstudio/src/pages/Projects/Projects.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { useParams as useRouterParams } from "react-router"; import { Redirect } from "react-router-dom"; -import { Button } from "../../components"; +import { Button } from "@humansignal/ui"; import { Oneof } from "../../components/Oneof/Oneof"; import { Spinner } from "../../components/Spinner/Spinner"; import { ApiContext } from "../../providers/ApiProvider"; @@ -10,9 +10,9 @@ import { Block, Elem } from "../../utils/bem"; import { CreateProject } from "../CreateProject/CreateProject"; import { DataManagerPage } from "../DataManager/DataManager"; import { SettingsPage } from "../Settings"; -import "./Projects.scss"; import { EmptyProjectsList, ProjectsList } from "./ProjectsList"; import { useAbortController } from "@humansignal/core"; +import "./Projects.scss"; const getCurrentPage = () => { const pageNumberFromURL = new URLSearchParams(location.search).get("page"); @@ -160,7 +160,7 @@ ProjectsPage.routes = ({ store }) => [ ProjectsPage.context = ({ openModal, showButton }) => { if (!showButton) return null; return ( - ); diff --git a/web/apps/labelstudio/src/pages/Projects/Projects.scss b/web/apps/labelstudio/src/pages/Projects/Projects.scss index 2230e0a3076a..faca43cccba5 100644 --- a/web/apps/labelstudio/src/pages/Projects/Projects.scss +++ b/web/apps/labelstudio/src/pages/Projects/Projects.scss @@ -78,10 +78,6 @@ color: var(--color-neutral-content-subtle); margin: 0; } - - &__action { - margin: 2rem 0; - } } .project-card { diff --git a/web/apps/labelstudio/src/pages/Projects/ProjectsList.jsx b/web/apps/labelstudio/src/pages/Projects/ProjectsList.jsx index 6f80fc7af970..fe582bca43b0 100644 --- a/web/apps/labelstudio/src/pages/Projects/ProjectsList.jsx +++ b/web/apps/labelstudio/src/pages/Projects/ProjectsList.jsx @@ -3,8 +3,8 @@ import { format } from "date-fns"; import { useMemo } from "react"; import { NavLink } from "react-router-dom"; import { IconCheck, IconEllipsis, IconMinus, IconSparks } from "@humansignal/icons"; -import { Userpic } from "@humansignal/ui"; -import { Button, Dropdown, Menu, Pagination } from "../../components"; +import { Userpic, Button } from "@humansignal/ui"; +import { Dropdown, Menu, Pagination } from "../../components"; import { Block, Elem } from "../../utils/bem"; import { absoluteURL } from "../../utils/helpers"; @@ -39,12 +39,12 @@ export const EmptyProjectsList = ({ openModal }) => { - Heidi doesn’t see any projects here! + Heidi doesn't see any projects here!

    Create one and start labeling your data.

    - +
    ); }; @@ -91,7 +91,9 @@ const ProjectCard = ({ project }) => { } > -
    @@ -119,7 +121,7 @@ const ProjectCard = ({ project }) => { {project.description} - {format(new Date(project.created_at), "dd MMM ’yy, HH:mm")} + {format(new Date(project.created_at), "dd MMM 'yy, HH:mm")} diff --git a/web/apps/labelstudio/src/pages/Settings/AnnotationSettings.jsx b/web/apps/labelstudio/src/pages/Settings/AnnotationSettings.jsx index 3866b05a8370..335465256fc1 100644 --- a/web/apps/labelstudio/src/pages/Settings/AnnotationSettings.jsx +++ b/web/apps/labelstudio/src/pages/Settings/AnnotationSettings.jsx @@ -1,5 +1,5 @@ import { useCallback, useContext, useEffect, useRef, useState } from "react"; -import { Button } from "../../components"; +import { Button } from "@humansignal/ui"; import { Form, TextArea, Toggle } from "../../components/Form"; import { MenubarContext } from "../../components/Menubar/Menubar"; import { Block, Elem } from "../../utils/bem"; @@ -71,7 +71,7 @@ export const AnnotationSettings = () => { Saved! - diff --git a/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx b/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx index 87a1c8a709d8..d5568269107f 100644 --- a/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx +++ b/web/apps/labelstudio/src/pages/Settings/DangerZone.jsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; import { useHistory } from "react-router"; -import { Button } from "../../components"; +import { Button } from "@humansignal/ui"; import { Label } from "../../components/Form"; import { confirm } from "../../components/Modal/Modal"; import { Spinner } from "../../components/Spinner/Spinner"; @@ -110,7 +110,8 @@ export const DangerZone = () => { {btn.help &&
    diff --git a/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/MachineLearningSettings.jsx b/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/MachineLearningSettings.jsx index 7a804e824434..88bb4fbf12f1 100644 --- a/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/MachineLearningSettings.jsx +++ b/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/MachineLearningSettings.jsx @@ -1,6 +1,6 @@ import { useCallback, useContext, useEffect, useState } from "react"; import { NavLink } from "react-router-dom"; -import { Button, Spinner } from "../../../components"; +import { Button, Spinner } from "@humansignal/ui"; import { Description } from "../../../components/Description/Description"; import { Form, Label, Toggle } from "../../../components/Form"; import { modal } from "../../../components/Modal/Modal"; @@ -105,7 +105,7 @@ export const MachineLearningSettings = () => { title="Let’s connect your first model" description="Connect a machine learning model to generate predictions. These predictions can be compared side by side, used for efficient pre‒labeling and, to aid in active learning, directing users to the most impactful labeling tasks." action={ - } @@ -172,7 +172,7 @@ export const MachineLearningSettings = () => { Saved! - diff --git a/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/StartModelTraining.jsx b/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/StartModelTraining.jsx index 69304bab6985..a3577a8c45b0 100644 --- a/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/StartModelTraining.jsx +++ b/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/StartModelTraining.jsx @@ -1,5 +1,5 @@ import { useCallback, useState } from "react"; -import { Button } from "../../../components"; +import { Button } from "@humansignal/ui"; import { useAPI } from "../../../providers/ApiProvider"; import { Description } from "../../../components/Description/Description"; import { Block } from "../../../utils/bem"; diff --git a/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/TestRequest.jsx b/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/TestRequest.jsx index 4c3723e44d47..d3866baf8944 100644 --- a/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/TestRequest.jsx +++ b/web/apps/labelstudio/src/pages/Settings/MachineLearningSettings/TestRequest.jsx @@ -1,5 +1,5 @@ import { useCallback, useState } from "react"; -import { Button } from "../../../components"; +import { Button } from "@humansignal/ui"; import { useAPI } from "../../../providers/ApiProvider"; import { Caption } from "../../../components/Caption/Caption"; import { Block, Elem } from "../../../utils/bem"; diff --git a/web/apps/labelstudio/src/pages/Settings/PredictionsSettings/PredictionsList.jsx b/web/apps/labelstudio/src/pages/Settings/PredictionsSettings/PredictionsList.jsx index 1b634ea01209..5bc7442298a2 100644 --- a/web/apps/labelstudio/src/pages/Settings/PredictionsSettings/PredictionsList.jsx +++ b/web/apps/labelstudio/src/pages/Settings/PredictionsSettings/PredictionsList.jsx @@ -1,7 +1,8 @@ import { useCallback, useContext } from "react"; import { format, formatDistanceToNow, parseISO } from "date-fns"; -import { Button, Dropdown, Menu } from "../../../components"; +import { Dropdown, Menu } from "../../../components"; +import { Button } from "@humansignal/ui"; import { IconInfoOutline, IconPredictions, IconEllipsis } from "@humansignal/icons"; import { Tooltip } from "@humansignal/ui"; import { confirm } from "../../../components/Modal/Modal"; @@ -89,7 +90,9 @@ const VersionCard = ({ version, selected, onSelect, editable, onDelete }) => { } > - diff --git a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageCard.jsx b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageCard.jsx index ad772ccbf4b9..5c8e8c566fbf 100644 --- a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageCard.jsx +++ b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageCard.jsx @@ -1,5 +1,6 @@ import { useCallback, useContext, useEffect, useState } from "react"; -import { Button, Card, Dropdown, Menu } from "../../../components"; +import { Card, Dropdown, Menu } from "../../../components"; +import { Button } from "@humansignal/ui"; import { ApiContext } from "../../../providers/ApiProvider"; import { StorageSummary } from "./StorageSummary"; import { IconEllipsisVertical } from "@humansignal/icons"; @@ -49,7 +50,9 @@ export const StorageCard = ({ rootClass, target, storage, onEditStorage, onDelet } > - } > @@ -61,7 +64,13 @@ export const StorageCard = ({ rootClass, target, storage, onEditStorage, onDelet />
    - {notSyncedYet && ( diff --git a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageForm.jsx b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageForm.jsx index f2ef226d6b57..3addc7a8fea7 100644 --- a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageForm.jsx +++ b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageForm.jsx @@ -1,5 +1,5 @@ import { forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; -import { Button } from "../../../components"; +import { Button } from "@humansignal/ui"; import { InlineError } from "../../../components/Error/InlineError"; import { Form, Input } from "../../../components/Form"; import { Oneof } from "../../../components/Oneof/Oneof"; @@ -111,14 +111,20 @@ export const StorageForm = forwardRef(({ onSubmit, target, project, rootClass, s } > - - - - +
    diff --git a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSet.jsx b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSet.jsx index cc607e21455c..e0fa612a1e80 100644 --- a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSet.jsx +++ b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSet.jsx @@ -1,5 +1,6 @@ import { useCallback, useContext } from "react"; -import { Button, Columns } from "../../../components"; +import { Columns } from "../../../components"; +import { Button } from "@humansignal/ui"; import { confirm, modal } from "../../../components/Modal/Modal"; import { Spinner } from "../../../components/Spinner/Spinner"; import { ApiContext } from "../../../providers/ApiProvider"; @@ -96,7 +97,7 @@ export const StorageSet = ({ title, target, rootClass, buttonLabel }) => { return (
    -
    diff --git a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSummary.jsx b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSummary.jsx index 829c2c9b3c1f..ad07c34b7611 100644 --- a/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSummary.jsx +++ b/web/apps/labelstudio/src/pages/Settings/StorageSettings/StorageSummary.jsx @@ -1,7 +1,6 @@ import { format } from "date-fns/esm"; -import { Button } from "../../../components"; import { DescriptionList } from "../../../components/DescriptionList/DescriptionList"; -import { Tooltip } from "@humansignal/ui"; +import { Tooltip, Button } from "@humansignal/ui"; import { modal } from "../../../components/Modal/Modal"; import { Oneof } from "../../../components/Oneof/Oneof"; import { getLastTraceback } from "../../../utils/helpers"; @@ -43,7 +42,8 @@ export const StorageSummary = ({ target, storage, className, storageTypes = [] } <>
    {msg}
    diff --git a/web/apps/labelstudio/src/pages/WebhookPage/WebhookDetail.jsx b/web/apps/labelstudio/src/pages/WebhookPage/WebhookDetail.jsx index 3369c633728b..1afac4cb4844 100644 --- a/web/apps/labelstudio/src/pages/WebhookPage/WebhookDetail.jsx +++ b/web/apps/labelstudio/src/pages/WebhookPage/WebhookDetail.jsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Button } from "../../components"; +import { Button } from "@humansignal/ui"; import { Form, Input, Label, Toggle } from "../../components/Form"; import { Block, cn, Elem } from "../../utils/bem"; import { cloneDeep } from "lodash"; @@ -145,9 +145,12 @@ const WebhookDetail = ({ webhook, webhooksInfo, fetchWebhooks, onBack, onSelectA {headers.map(([headKey, headValue], index) => { return ( @@ -170,6 +173,7 @@ const WebhookDetail = ({ webhook, webhooksInfo, fetchWebhooks, onBack, onSelectA )} -
    - -
    - - + +
    + +
    + + +
    diff --git a/web/apps/labelstudio/src/pages/WebhookPage/WebhookList.jsx b/web/apps/labelstudio/src/pages/WebhookPage/WebhookList.jsx index 0d66bb72c318..0b3a8a5882d1 100644 --- a/web/apps/labelstudio/src/pages/WebhookPage/WebhookList.jsx +++ b/web/apps/labelstudio/src/pages/WebhookPage/WebhookList.jsx @@ -1,7 +1,6 @@ import { useCallback } from "react"; -import { Button } from "../../components"; import { IconCross, IconPencil } from "@humansignal/icons"; -import { Toggle } from "@humansignal/ui"; +import { Button, Toggle } from "@humansignal/ui"; import { Block, Elem } from "../../utils/bem"; import "./WebhookPage.scss"; import { format } from "date-fns"; @@ -31,7 +30,9 @@ const WebhookList = ({ onSelectActive, onAddWebhook, webhooks, fetchWebhooks })

    Webhooks

    - + {webhooks.length === 0 ? null : ( @@ -50,7 +51,12 @@ const WebhookList = ({ onSelectActive, onAddWebhook, webhooks, fetchWebhooks }) Created {format(new Date(obj.created_at), "dd MMM yyyy, HH:mm")} - + ); diff --git a/web/libs/app-common/src/pages/AccountSettings/sections/PersonalAccessToken.tsx b/web/libs/app-common/src/pages/AccountSettings/sections/PersonalAccessToken.tsx index 25886f27ceab..f69eac970c8f 100644 --- a/web/libs/app-common/src/pages/AccountSettings/sections/PersonalAccessToken.tsx +++ b/web/libs/app-common/src/pages/AccountSettings/sections/PersonalAccessToken.tsx @@ -2,6 +2,7 @@ import { IconLaunch, IconFileCopy, Label } from "@humansignal/ui"; import styles from "./PersonalAccessToken.module.scss"; import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query"; import { atom, useAtomValue } from "jotai"; +import { Button } from "@humansignal/ui"; import { useCopyText } from "@humansignal/core/lib/hooks/useCopyText"; /** @@ -9,7 +10,6 @@ import { useCopyText } from "@humansignal/core/lib/hooks/useCopyText"; * each one of these eventually has to be migrated to core/ui */ import { Input, TextArea } from "apps/labelstudio/src/components/Form"; -import { Button } from "apps/labelstudio/src/components/Button/Button"; const tokenAtom = atomWithQuery(() => ({ queryKey: ["access-token"], @@ -56,10 +56,16 @@ export const PersonalAccessToken = () => {
    diff --git a/web/libs/app-common/src/pages/AccountSettings/sections/PersonalInfo.tsx b/web/libs/app-common/src/pages/AccountSettings/sections/PersonalInfo.tsx index 244f94865bf2..7509907e3231 100644 --- a/web/libs/app-common/src/pages/AccountSettings/sections/PersonalInfo.tsx +++ b/web/libs/app-common/src/pages/AccountSettings/sections/PersonalInfo.tsx @@ -1,6 +1,6 @@ import { type FormEventHandler, useCallback, useEffect, useRef, useState } from "react"; import clsx from "clsx"; -import { InputFile, ToastType, useToast, Userpic } from "@humansignal/ui"; +import { Button, InputFile, ToastType, useToast, Userpic } from "@humansignal/ui"; // @todo we should not use anything from `apps` in `libs` import { API } from "apps/labelstudio/src/providers/ApiProvider"; import styles from "../AccountSettings.module.scss"; @@ -13,7 +13,6 @@ import { useAtomValue } from "jotai"; * each one of these eventually has to be migrated to core or ui */ import { Input } from "apps/labelstudio/src/components/Form/Elements"; -import { Button } from "apps/labelstudio/src/components/Button/Button"; const updateUserAvatarAtom = atomWithMutation(() => ({ mutationKey: ["update-user"], @@ -117,7 +116,7 @@ export const PersonalInfo = () => { /> {user?.avatar && ( - )} diff --git a/web/libs/app-common/src/pages/AccountSettings/sections/PersonalJWTToken.tsx b/web/libs/app-common/src/pages/AccountSettings/sections/PersonalJWTToken.tsx index 68b2eab2c5e0..8786502cae49 100644 --- a/web/libs/app-common/src/pages/AccountSettings/sections/PersonalJWTToken.tsx +++ b/web/libs/app-common/src/pages/AccountSettings/sections/PersonalJWTToken.tsx @@ -7,6 +7,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { format } from "date-fns"; import { useCopyText } from "@humansignal/core/lib/hooks/useCopyText"; import styles from "./PersonalJWTToken.module.scss"; +import { Button } from "@humansignal/ui"; /** * FIXME: This is legacy imports. We're not supposed to use such statements @@ -14,7 +15,6 @@ import styles from "./PersonalJWTToken.module.scss"; */ import { API } from "apps/labelstudio/src/providers/ApiProvider"; import { modal, confirm } from "apps/labelstudio/src/components/Modal/Modal"; -import { Button } from "apps/labelstudio/src/components/Button/Button"; import { Input, Label } from "apps/labelstudio/src/components/Form/Elements"; import { Tooltip } from "@humansignal/ui"; @@ -113,7 +113,7 @@ export function PersonalJWTToken() { window?.APP_SETTINGS?.app_name || "Label Studio" }`, okText: "Revoke", - buttonLook: "danger", + buttonLook: "negative", onOk: async () => { await revokeToken.mutateAsync({ token }); }, @@ -159,7 +159,7 @@ export function PersonalJWTToken() {
    {token.token}
    - @@ -173,7 +173,12 @@ export function PersonalJWTToken() {
    -
    @@ -202,7 +207,7 @@ function CreateTokenForm() { readOnly value={data} /> - diff --git a/web/libs/datamanager/src/components/Common/Button/Button.jsx b/web/libs/datamanager/src/components/Common/Button/Button.jsx deleted file mode 100644 index a7855238395b..000000000000 --- a/web/libs/datamanager/src/components/Common/Button/Button.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import { cloneElement, forwardRef, useMemo } from "react"; -import { Block, Elem } from "../../../utils/bem"; -import { isDefined } from "../../../utils/utils"; -import "./Button.scss"; - -export const Button = forwardRef( - ({ children, type, extra, className, href, size, waiting, icon, tag, look, ...rest }, ref) => { - const finalTag = (tag ?? href) ? "a" : "button"; - - const mods = { - size, - waiting, - type, - look, - withIcon: !!icon, - withExtra: !!extra, - disabled: !!rest.disabled, - }; - - const iconElem = useMemo(() => { - if (!icon) return null; - - switch (size) { - case "small": - return cloneElement(icon, { ...icon.props, size: 12 }); - case "compact": - return cloneElement(icon, { ...icon.props, size: 14 }); - default: - return icon; - } - }, [icon, size]); - - return ( - - <> - {isDefined(iconElem) && ( - - {iconElem ?? null} - - )} - {isDefined(iconElem) && isDefined(children) ? ( - - {children} - - ) : ( - (children ?? null) - )} - {isDefined(extra) ? {extra} : null} - - - ); - }, -); -Button.displayName = "Button"; - -Button.Group = ({ className, children, collapsed, ...rest }) => { - return ( - - {children} - - ); -}; diff --git a/web/libs/datamanager/src/components/Common/Button/Button.scss b/web/libs/datamanager/src/components/Common/Button/Button.scss deleted file mode 100644 index bd96c2921f9b..000000000000 --- a/web/libs/datamanager/src/components/Common/Button/Button.scss +++ /dev/null @@ -1,364 +0,0 @@ -.button-dm { - --button-color: var(--color-neutral-content); - --button-height: 32px; - - height: var(--button-height); - cursor: pointer; - padding: 0 12px; - outline: none; - display: inline-flex; - background-color: var(--color-neutral-background); - align-items: center; - border-radius: 5px; - text-align: center; - text-decoration: none; - justify-content: center; - color: var(--button-color); - font-weight: 500; - font-size: 14px; - border: 1px solid var(--color-neutral-border); - transition: all 150ms ease-out; - - svg { - max-height: 100%; - } - - &_waiting, - &_disabled, - &:disabled, - &[disabled] { - --button-color: var(--color-neutral-content-subtlest); - - pointer-events: none; - background-color: var(--color-neutral-background); - border: 1px solid var(--color-neutral-border); - } - - &:not(&_look_primary):hover { - --button-color: var(--color-neutral-content); - - border: 1px solid var(--color-neutral-border-bold); - } - - &:active { - background: var(--color-neutral-surface-active); - } - - &:focus { - outline: none; - box-shadow: 0 0 0 6px rgb(var(--accent_color-raw) / 20%), inset 0 -1px 0 var(--black_10), inset 0 0 0 1px var(--black_15), inset 0 0 0 1px rgb(var(--accent_color-raw) / 20%); - } - - &__extra { - font-size: 14px; - line-height: 16px; - display: flex; - align-items: center; - margin-left: 7px; - margin-right: -7px; - } - - &__content { - display: inline-flex; - align-items: center; - } - - &__icon { - display: flex; - width: 16px; - height: 100%; - align-items: center; - - &:not(:only-child) { - margin-right: 12px; - } - - &:only-child { - flex: 1; - align-items: center; - justify-content: center; - } - } - - &[href] { - padding: 0; - min-width: 0; - box-shadow: none; - background: none; - - --button-color: var(--accent_color); - - text-decoration: underline; - - &:hover { - box-shadow: none; - } - } - - &_type { - &_text { - padding: 0; - } - - &_text:not(.lsf-button-dm_look_primary), - &_link:not(.lsf-button-dm_look_primary) { - padding: 0; - min-width: 0; - box-shadow: none; - background: none; - border: 0; - - &:hover { - box-shadow: none; - border: 0; - } - } - - &_link { - --button-color: var(--accent_color); - - text-decoration: underline; - } - } - - &_look { - &_primary { - --button-color: var(--white); - - background-color: var(--grape_500); - border: 0; - box-shadow: inset 0 -1px 0 var(--black_10); - - &:disabled { - --button-color: rgb(var(--white-raw) / 80%); - - background-color: #BBB; - } - - &:hover { - color: var(--button-color); - background: linear-gradient(0deg, rgb(var(--white-raw) / 10%), rgb(var(--white-raw) / 10%)), var(--accent_color); - box-shadow: 0 2px 4px rgb(var(--accent_color-raw) / 30%), inset 0 -1px 0 var(--black_10); - } - - &:active { - color: var(--button-color); - background: linear-gradient(0deg, var(--black_4), var(--black_4)), var(--accent_color); - box-shadow: inset 0 1px 0 var(--black_10); - } - - &:focus { - box-shadow: 0 0 0 6px rgb(var(--accent_color-raw) / 20%), inset 0 -1px 0 var(--black_10); - } - } - - &_danger { - --button-color: var(--color-negative-content); - - border-color: var(--color-negative-border); - - &:not(:disabled):hover { - --button-color: var(--color-negative-surface-content) !important; - - background-color: var(--color-negative-surface-hover); - } - } - - &_destructive { - --button-color: var(--color-negative-surface-content); - - background-color: var(--color-negative-surface); - border-color: var(--color-negative-border); - - &:hover:not(:disabled) { - --button-color: var(--color-negative-surface-content); - - background-color: var(--color-negative-surface-hover); - border-color: var(--color-negative-border-bold); - } - } - } - - &_look_destructive:disabled, - &_look_destructive.button-dm_waiting { - --button-color: rgb(var(--white-raw) / 50%); - - background-color: var(--color-negative-content); - } - - &_size { - &_compact { - --button-height: 36px; - - font-size: 16px; - line-height: 20px; - } - - &_medium { - --button-height: 32px; - - font-size: 14px; - line-height: 20px; - } - - &_small { - --button-height: 24px; - - font-size: 12px; - line-height: 12px; - padding: 0 10px; - border: 0; - } - - &_large { - --button-height: 40px; - - font-size: 16px; - } - } - - &_size_small &__extra { - margin-left: 5px; - margin-right: -5px; - } - - &_size_medium &__extra { - margin-left: 7px; - margin-right: -7px; - } - - &_size_compact &__extra { - margin-left: 7px; - margin-right: -7px; - } - - &_size_large &__extra { - margin-left: 10px; - margin-right: -10px; - } - - &_withIcon { - justify-content: space-between; - } - - &_withIcon:not(.button-dm_type_link), - &_withIcon:not([href]) { - padding: 0 14px; - } - - &_withIcon.button-dm_size_small { - padding: 0 10px; - } - - &_waiting { - pointer-events: none; - background-repeat: repeat; - background-position: 40px; - background-size: 37px 100%; - animation: button-waiting 1s linear infinite; - background-image: repeating-linear-gradient(-63.43deg, - rgb(var(--white-raw) / 20%) 1px, #efefef 2px, #efefef 6px, - rgb(var(--white-raw) / 20%) 7px, - rgb(var(--white-raw) / 20%) 12px); - background-color: var(--white); - } - - &_waiting.button-dm_look_primary { - background-image: repeating-linear-gradient(-63.43deg, - rgb(var(--white-raw) / 20%) 1px, transparent 2px, transparent 6px, - rgb(var(--white-raw) / 20%) 7px, - rgb(var(--white-raw) / 20%) 12px); - background-color: var(--accent_color); - } - - &_waiting.button-dm_look_danger, - &_waiting.button-dm_look_destructive { - --button-background-color: var(--color-negative-emphasis-subtle); - - background-image: var(--negative-button-waiting-animation-bg); - } - - &_size_small &__icon { - width: 12px; - - &:not(:only-child) { - margin-right: 8px; - } - } -} - -.button-group-dm { - display: flex; - - &:not(.button-group-dm_collapsed) { - .button-dm+.button-dm { - margin-left: 16px; - } - } - - &_collapsed { - .button-dm { - &:first-child { - border-radius: 5px 0 0 5px; - } - - &:last-child { - border-radius: 0 5px 5px 0; - } - - &:not(:first-child, :last-child) { - border-radius: 0; - } - } - } -} - -@keyframes button-waiting { - 0% { - background-position: 0 0; - } - - 100% { - background-position: 37px 0; - } -} - -@keyframes button-waiting { - 0% { - background-position: 0 0; - } - - 100% { - background-position: 37px 0; - } -} - -@keyframes button-waiting { - 0% { - background-position: 0 0; - } - - 100% { - background-position: 37px 0; - } -} - -@keyframes button-waiting { - 0% { - background-position: 0 0; - } - - 100% { - background-position: 37px 0; - } -} - -@keyframes button-waiting { - 0% { - background-position: 0 0; - } - - 100% { - background-position: 37px 0; - } -} diff --git a/web/libs/datamanager/src/components/Common/Dropdown/DropdownComponent.jsx b/web/libs/datamanager/src/components/Common/Dropdown/DropdownComponent.jsx index 201818cdf2cd..8c526547c55c 100644 --- a/web/libs/datamanager/src/components/Common/Dropdown/DropdownComponent.jsx +++ b/web/libs/datamanager/src/components/Common/Dropdown/DropdownComponent.jsx @@ -10,7 +10,7 @@ import { DropdownTrigger } from "./DropdownTrigger"; let lastIndex = 1; -export const Dropdown = React.forwardRef(({ animated = true, visible = false, ...props }, ref) => { +export const Dropdown = React.forwardRef(({ animated = true, visible = false, rawClassName, ...props }, ref) => { const rootName = cn("dropdown-dm"); /**@type {import('react').RefObject} */ @@ -153,10 +153,11 @@ export const Dropdown = React.forwardRef(({ animated = true, visible = false, .. ...(offset ?? {}), zIndex: 1000 + dropdownIndex, }; + console.log({ rawClassName }); const result = (
    e.stopPropagation()} > diff --git a/web/libs/datamanager/src/components/Common/ErrorBox.jsx b/web/libs/datamanager/src/components/Common/ErrorBox.jsx index b4f9eea8b46a..019eea5b4a05 100644 --- a/web/libs/datamanager/src/components/Common/ErrorBox.jsx +++ b/web/libs/datamanager/src/components/Common/ErrorBox.jsx @@ -1,5 +1,5 @@ import { inject } from "mobx-react"; -import { Button } from "./Button/Button"; +import { Button } from "@humansignal/ui"; import { Dropdown } from "./Dropdown/Dropdown"; import { Menu } from "./Menu/Menu"; import { IconInfo } from "@humansignal/icons"; @@ -21,16 +21,7 @@ const injector = inject(({ store }) => { export const ErrorBox = injector(({ errors }) => { return errors?.size > 0 ? ( {Array.from(errors.values()).map(ErrorRenderer)}}> - diff --git a/web/libs/datamanager/src/components/Common/FieldsButton.jsx b/web/libs/datamanager/src/components/Common/FieldsButton.jsx index 0a6842ba07f7..ae774fd889e8 100644 --- a/web/libs/datamanager/src/components/Common/FieldsButton.jsx +++ b/web/libs/datamanager/src/components/Common/FieldsButton.jsx @@ -1,10 +1,9 @@ +import { Button, Checkbox } from "@humansignal/ui"; import { inject, observer } from "mobx-react"; import React from "react"; -import { Button } from "./Button/Button"; -import { Checkbox, Tooltip } from "@humansignal/ui"; +import { Elem } from "../../utils/bem"; import { Dropdown } from "./Dropdown/Dropdown"; import { Menu } from "./Menu/Menu"; -import { Elem } from "../../utils/bem"; const injector = inject(({ store }) => { return { @@ -80,7 +79,7 @@ export const FieldsButton = injector( const renderButton = () => { return ( - ); @@ -98,17 +97,21 @@ export const FieldsButton = injector( resetTitle={resetTitle} /> } - style={{ - maxHeight: 280, - overflow: "auto", - }} + style={{ maxHeight: 280, overflow: "auto" }} openUpwardForShortViewport={openUpwardForShortViewport} > {tooltip ? ( - - - {renderButton()} - + + ) : ( renderButton() diff --git a/web/libs/datamanager/src/components/Common/FiltersPane.jsx b/web/libs/datamanager/src/components/Common/FiltersPane.jsx index e8409e915a09..f63fea81938e 100644 --- a/web/libs/datamanager/src/components/Common/FiltersPane.jsx +++ b/web/libs/datamanager/src/components/Common/FiltersPane.jsx @@ -3,8 +3,9 @@ import React, { useEffect, useRef } from "react"; import { IconChevronDown } from "@humansignal/icons"; import { Filters } from "../Filters/Filters"; import { Badge } from "./Badge/Badge"; -import { Button } from "./Button/Button"; +import { Button } from "@humansignal/ui"; import { Dropdown } from "./Dropdown/Dropdown"; +import { Icon } from "./Icon/Icon"; const buttonInjector = inject(({ store }) => { const { viewsStore, currentView } = store; @@ -22,14 +23,22 @@ export const FiltersButton = buttonInjector( const hasFilters = activeFiltersNumber > 0; return ( - ); }), diff --git a/web/libs/datamanager/src/components/Common/Form/Form.jsx b/web/libs/datamanager/src/components/Common/Form/Form.jsx index 033cf5d6cf6b..5da8956645f4 100644 --- a/web/libs/datamanager/src/components/Common/Form/Form.jsx +++ b/web/libs/datamanager/src/components/Common/Form/Form.jsx @@ -2,7 +2,7 @@ import { Component, createRef, forwardRef, useCallback, useContext, useEffect, u import { shallowEqualObjects } from "shallow-equal"; import { Block, cn, Elem } from "../../../utils/bem"; import { objectClean } from "../../../utils/helpers"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { Oneof } from "../Oneof/Oneof"; import { Space } from "../Space/Space"; import { Counter, Input, Select, Toggle } from "./Elements"; @@ -537,7 +537,7 @@ Form.Builder = forwardRef( {children} {props.autosubmit !== true && withActions === true && ( - diff --git a/web/libs/datamanager/src/components/Common/Modal/Modal.jsx b/web/libs/datamanager/src/components/Common/Modal/Modal.jsx index c943ce748906..f1d106b85dfe 100644 --- a/web/libs/datamanager/src/components/Common/Modal/Modal.jsx +++ b/web/libs/datamanager/src/components/Common/Modal/Modal.jsx @@ -1,7 +1,7 @@ import { createRef } from "react"; import { render } from "react-dom"; import { cn } from "../../../utils/bem"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { Space } from "../Space/Space"; import { Modal } from "./ModalPopup"; @@ -51,8 +51,10 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro onCancel?.(); modal.close(); }} - size="compact" + size="small" + look="outlined" autoFocus + aria-label="Cancel" > {cancelText ?? "Cancel"} @@ -62,8 +64,8 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro onOk?.(); modal.close(); }} - size="compact" - look={buttonLook ?? "primary"} + size="small" + aria-label={okText ?? "OK"} > {okText ?? "OK"} @@ -84,8 +86,8 @@ export const info = ({ okText, onOkPress, ...props }) => { onOkPress?.(); modal.close(); }} - look="primary" - size="compact" + size="small" + aria-label="OK" > {okText ?? "OK"} diff --git a/web/libs/datamanager/src/components/Common/Modal/ModalPopup.jsx b/web/libs/datamanager/src/components/Common/Modal/ModalPopup.jsx index 10e19211f0f4..3049ad84ec51 100644 --- a/web/libs/datamanager/src/components/Common/Modal/ModalPopup.jsx +++ b/web/libs/datamanager/src/components/Common/Modal/ModalPopup.jsx @@ -3,8 +3,7 @@ import { createPortal } from "react-dom"; import { IconCross } from "@humansignal/icons"; import { BemWithSpecifiContext, cn } from "../../../utils/bem"; import { aroundTransition } from "@humansignal/core/lib/utils/transition"; -import { Button } from "../Button/Button"; -import { Icon } from "../Icon/Icon"; +import { Button } from "@humansignal/ui"; import "./Modal.scss"; const { Block, Elem } = BemWithSpecifiContext(); @@ -85,12 +84,9 @@ export class Modal extends React.Component { {this.state.title} {this.props.allowClose !== false && ( - } - /> + )} )} diff --git a/web/libs/datamanager/src/components/Common/SDKButtons.jsx b/web/libs/datamanager/src/components/Common/SDKButtons.jsx index ca512807ab9e..16f9b6b5b5c1 100644 --- a/web/libs/datamanager/src/components/Common/SDKButtons.jsx +++ b/web/libs/datamanager/src/components/Common/SDKButtons.jsx @@ -1,5 +1,5 @@ import { useSDK } from "../../providers/SDKProvider"; -import { Button } from "./Button/Button"; +import { Button } from "@humansignal/ui"; const SDKButton = ({ eventName, ...props }) => { const sdk = useSDK(); @@ -7,6 +7,10 @@ const SDKButton = ({ eventName, ...props }) => { return sdk.hasHandler(eventName) ? ( ); }, diff --git a/web/libs/datamanager/src/components/Common/Table/TableHead/TableHead.jsx b/web/libs/datamanager/src/components/Common/Table/TableHead/TableHead.jsx index 29033bb477b8..66f0768be3f8 100644 --- a/web/libs/datamanager/src/components/Common/Table/TableHead/TableHead.jsx +++ b/web/libs/datamanager/src/components/Common/Table/TableHead/TableHead.jsx @@ -2,7 +2,7 @@ import { observer, useLocalStore } from "mobx-react"; import { toJS } from "mobx"; import React, { forwardRef, useCallback, useEffect, useRef } from "react"; import { ViewColumnType, ViewColumnTypeName, ViewColumnTypeShort } from "../../../../stores/Tabs/tab_column"; -import { Button } from "../../Button/Button"; +import { Button } from "@humansignal/ui"; import { Dropdown } from "../../Dropdown/Dropdown"; import { Menu } from "../../Menu/Menu"; import { Resizer } from "../../Resizer/Resizer"; @@ -57,18 +57,7 @@ const DropdownWrapper = observer(({ column, cellViews, children, onChange }) => } > - diff --git a/web/libs/datamanager/src/components/Common/Tabs/Tabs.jsx b/web/libs/datamanager/src/components/Common/Tabs/Tabs.jsx index 2d69988a0f12..30bcbc52de9d 100644 --- a/web/libs/datamanager/src/components/Common/Tabs/Tabs.jsx +++ b/web/libs/datamanager/src/components/Common/Tabs/Tabs.jsx @@ -1,10 +1,9 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { DragDropContext, Droppable } from "react-beautiful-dnd"; -import { IconEllipsisVertical } from "@humansignal/icons"; +import { IconEllipsisVertical, IconPlus } from "@humansignal/icons"; import { cn } from "../../../utils/bem"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { Dropdown } from "../Dropdown/DropdownComponent"; -import { Icon } from "../Icon/Icon"; import Input from "../Input/Input"; import "./Tabs.scss"; import { TabsMenu } from "./TabsMenu"; @@ -61,7 +60,9 @@ export const Tabs = ({ {allowedActions.add !== false && ( - )} {tabBarExtraContent} @@ -193,12 +194,9 @@ export const TabsItem = ({ } >
    -
    )} diff --git a/web/libs/datamanager/src/components/Common/Tabs/Tabs.scss b/web/libs/datamanager/src/components/Common/Tabs/Tabs.scss index 289cef8734d4..c545a6b69beb 100644 --- a/web/libs/datamanager/src/components/Common/Tabs/Tabs.scss +++ b/web/libs/datamanager/src/components/Common/Tabs/Tabs.scss @@ -26,6 +26,7 @@ &__list { display: flex; min-width: 0; + align-items: center; gap: 2px; } diff --git a/web/libs/datamanager/src/components/DataManager/DataManager.jsx b/web/libs/datamanager/src/components/DataManager/DataManager.jsx index 3ec58963249b..3c8d16b3340b 100644 --- a/web/libs/datamanager/src/components/DataManager/DataManager.jsx +++ b/web/libs/datamanager/src/components/DataManager/DataManager.jsx @@ -1,7 +1,6 @@ import { inject, observer } from "mobx-react"; import { useCallback } from "react"; import { Draggable } from "react-beautiful-dnd"; -import { IconPlus } from "@humansignal/icons"; import { cn } from "../../utils/bem"; import { Interface } from "../Common/Interface"; import { Space } from "../Common/Space/Space"; @@ -85,7 +84,6 @@ const TabsSwitch = switchInjector( onChange={(key) => views.setSelected(key)} onDragEnd={onDragEnd} tabBarExtraContent={} - addIcon={} allowedActions={editable} > {tabs.map((tab, index) => ( diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/ActionsButton.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/ActionsButton.jsx index 4f51cac738b7..7b0b1aec9b2a 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/ActionsButton.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/ActionsButton.jsx @@ -3,12 +3,13 @@ import { useCallback, useRef } from "react"; import { IconChevronRight, IconChevronDown, IconTrash } from "@humansignal/icons"; import { Block, Elem } from "../../../utils/bem"; import { FF_LOPS_E_3, isFF } from "../../../utils/feature-flags"; -import { Button } from "../../Common/Button/Button"; +import { Button } from "@humansignal/ui"; import { Dropdown } from "../../Common/Dropdown/DropdownComponent"; import Form from "../../Common/Form/Form"; import { Menu } from "../../Common/Menu/Menu"; import { Modal } from "../../Common/Modal/ModalPopup"; import "./ActionsButton.scss"; +import { Icon } from "../../Common/Icon/Icon"; const isFFLOPSE3 = isFF(FF_LOPS_E_3); const injector = inject(({ store }) => ({ @@ -135,9 +136,15 @@ export const ActionsButton = injector( openUpwardForShortViewport={false} disabled={!hasSelected} > - ); diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx index 8cee9be11c0a..f14b75d01624 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/GridWidthButton.jsx @@ -1,7 +1,7 @@ import { inject } from "mobx-react"; import { useCallback, useState } from "react"; import { IconMinus, IconPlus } from "@humansignal/icons"; -import { Button } from "../../Common/Button/Button"; +import { Button, ButtonGroup } from "@humansignal/ui"; import { Icon } from "../../Common/Icon/Icon"; import { Space } from "../../Common/Space/Space"; @@ -30,20 +30,24 @@ export const GridWidthButton = injector(({ view, gridWidth, size }) => { return view.type === "grid" ? ( Columns: {width} - + + ) : null; }); diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/LabelButton.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/LabelButton.jsx index e2711375340f..dfaf2b61c2b9 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/LabelButton.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/LabelButton.jsx @@ -1,8 +1,10 @@ import { inject } from "mobx-react"; -import { Button } from "../../Common/Button/Button"; +import { Button, ButtonGroup } from "@humansignal/ui"; import { Interface } from "../../Common/Interface"; import { useCallback, useEffect, useRef, useState } from "react"; -import { IconChevron, IconChevronDown } from "@humansignal/icons"; +import { IconChevronDown } from "@humansignal/icons"; +import { Dropdown } from "../../Common/Dropdown/DropdownComponent"; +import { Menu } from "../../Common/Menu/Menu"; const injector = inject(({ store }) => { const { dataStore, currentView } = store; @@ -84,30 +86,30 @@ export const LabelButton = injector(({ store, canLabel, size, target, selectedCo return canLabel ? (
    -
    + - -
    - + + +
    ) : null; diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/OrderButton.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/OrderButton.jsx index befdc01ef0fe..125f532e57bd 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/OrderButton.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/OrderButton.jsx @@ -1,6 +1,6 @@ -import { inject } from "mobx-react"; import { IconSortDown, IconSortUp } from "@humansignal/icons"; -import { Button } from "../../Common/Button/Button"; +import { Button, ButtonGroup } from "@humansignal/ui"; +import { inject } from "mobx-react"; import { FieldsButton } from "../../Common/FieldsButton"; import { Space } from "../../Common/Space/Space"; @@ -16,7 +16,7 @@ const injector = inject(({ store }) => { export const OrderButton = injector(({ size, ordering, view, ...rest }) => { return ( - + { /> + ); }); diff --git a/web/libs/datamanager/src/components/DataManager/Toolbar/RefreshButton.jsx b/web/libs/datamanager/src/components/DataManager/Toolbar/RefreshButton.jsx index 1f1a2296cc6b..1cd1e1ff2869 100644 --- a/web/libs/datamanager/src/components/DataManager/Toolbar/RefreshButton.jsx +++ b/web/libs/datamanager/src/components/DataManager/Toolbar/RefreshButton.jsx @@ -1,6 +1,6 @@ import { inject } from "mobx-react"; import { IconRefresh } from "@humansignal/icons"; -import { Button } from "../../Common/Button/Button"; +import { Button } from "@humansignal/ui"; const injector = inject(({ store }) => { return { @@ -13,22 +13,17 @@ const injector = inject(({ store }) => { export const RefreshButton = injector(({ store, needsDataFetch, projectFetch, size, style, ...rest }) => { return ( ); }); diff --git a/web/libs/datamanager/src/components/Filters/FilterLine/FilterLine.jsx b/web/libs/datamanager/src/components/Filters/FilterLine/FilterLine.jsx index 67cb6d4cbe80..4ce160e14702 100644 --- a/web/libs/datamanager/src/components/Filters/FilterLine/FilterLine.jsx +++ b/web/libs/datamanager/src/components/Filters/FilterLine/FilterLine.jsx @@ -1,9 +1,8 @@ import { observer } from "mobx-react"; import { Fragment } from "react"; -import { IconClose } from "@humansignal/icons"; import { BemWithSpecifiContext } from "../../../utils/bem"; -import { Button } from "../../Common/Button/Button"; -import { Icon } from "../../Common/Icon/Icon"; +import { Button } from "@humansignal/ui"; +import { IconTrash } from "@humansignal/icons"; import { Tag } from "../../Common/Tag/Tag"; import { FilterDropdown } from "../FilterDropdown"; import "./FilterLine.scss"; @@ -68,15 +67,15 @@ export const FilterLine = observer(({ filter, availableFilters, index, view, sid ); diff --git a/web/libs/datamanager/src/components/Filters/Filters.jsx b/web/libs/datamanager/src/components/Filters/Filters.jsx index d4d3a0253b26..f77558d61271 100644 --- a/web/libs/datamanager/src/components/Filters/Filters.jsx +++ b/web/libs/datamanager/src/components/Filters/Filters.jsx @@ -1,8 +1,7 @@ import { inject } from "mobx-react"; import React from "react"; import { Block, cn, Elem } from "../../utils/bem"; -import { Button } from "../Common/Button/Button"; -import { Icon } from "../Common/Icon/Icon"; +import { Button } from "@humansignal/ui"; import { Tooltip } from "@humansignal/ui"; import { FilterLine } from "./FilterLine/FilterLine"; import { IconChevronRight, IconPlus } from "@humansignal/icons"; @@ -70,20 +69,20 @@ export const Filters = injector(({ views, currentView, filters }) => { )}
    - {!sidebarEnabled ? ( - ) : null} diff --git a/web/libs/datamanager/src/components/Filters/FiltersSidebar/FilterSidebar.jsx b/web/libs/datamanager/src/components/Filters/FiltersSidebar/FilterSidebar.jsx index 491c39ea324b..785cc8dedc77 100644 --- a/web/libs/datamanager/src/components/Filters/FiltersSidebar/FilterSidebar.jsx +++ b/web/libs/datamanager/src/components/Filters/FiltersSidebar/FilterSidebar.jsx @@ -1,8 +1,7 @@ import { inject } from "mobx-react"; import { IconChevronLeft } from "@humansignal/icons"; import { Block, Elem } from "../../../utils/bem"; -import { Button } from "../../Common/Button/Button"; -import { Icon } from "../../Common/Icon/Icon"; +import { Button } from "@humansignal/ui"; import { Filters } from "../Filters"; import "./FilterSidebar.scss"; @@ -19,18 +18,11 @@ const sidebarInjector = inject(({ store }) => { export const FiltersSidebar = sidebarInjector(({ viewsStore, sidebarEnabled, sidebarVisible }) => { return sidebarEnabled && sidebarVisible ? ( - - - - Filters diff --git a/web/libs/datamanager/src/components/Label/Label.jsx b/web/libs/datamanager/src/components/Label/Label.jsx index 6a12bb281701..72fbf35b5b7e 100644 --- a/web/libs/datamanager/src/components/Label/Label.jsx +++ b/web/libs/datamanager/src/components/Label/Label.jsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite"; import { useCallback, useEffect, useMemo, useRef } from "react"; import { IconChevronDown, IconChevronLeft, IconGearNewUI } from "@humansignal/icons"; import { Block, Elem } from "../../utils/bem"; -import { Button } from "../Common/Button/Button"; +import { Button } from "@humansignal/ui"; import { FieldsButton } from "../Common/FieldsButton"; import { Icon } from "../Common/Icon/Icon"; import { Resizer } from "../Common/Resizer/Resizer"; diff --git a/web/libs/datamanager/src/components/MainView/DataView/Table.jsx b/web/libs/datamanager/src/components/MainView/DataView/Table.jsx index eab39595c2b6..37c53f582510 100644 --- a/web/libs/datamanager/src/components/MainView/DataView/Table.jsx +++ b/web/libs/datamanager/src/components/MainView/DataView/Table.jsx @@ -14,7 +14,7 @@ import { Tooltip } from "@humansignal/ui"; import { IconQuestionOutline } from "@humansignal/icons"; import { GridView } from "../GridView/GridView"; import "./Table.scss"; -import { Button } from "../../Common/Button/Button"; +import { Button } from "@humansignal/ui"; import { useState } from "react"; import { useEffect } from "react"; @@ -168,6 +168,8 @@ export const DataView = injector( Press the button below to see any synced records
    -
    - +
    + - +
    ); diff --git a/web/libs/datamanager/src/components/MainView/GridView/ImagePreview.tsx b/web/libs/datamanager/src/components/MainView/GridView/ImagePreview.tsx index 0272a5d292f5..d8b2720385af 100644 --- a/web/libs/datamanager/src/components/MainView/GridView/ImagePreview.tsx +++ b/web/libs/datamanager/src/components/MainView/GridView/ImagePreview.tsx @@ -1,6 +1,7 @@ import { useState, useRef, useEffect, type CSSProperties, useCallback } from "react"; import { observer } from "mobx-react"; import styles from "./GridPreview.module.scss"; +import { cn } from "@humansignal/ui"; const MAX_ZOOM = 20; const ZOOM_FACTOR = 0.01; @@ -195,7 +196,6 @@ const ImagePreview = observer(({ task, field }: ImagePreviewProps) => { ? { maxWidth: "100%", maxHeight: "100%", - transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})`, transformOrigin: "0 0", } : { @@ -208,7 +208,7 @@ const ImagePreview = observer(({ task, field }: ImagePreviewProps) => {
    diff --git a/web/libs/datamanager/src/utils/bem.tsx b/web/libs/datamanager/src/utils/bem.tsx index 8fb6768bff0f..50468c07c472 100644 --- a/web/libs/datamanager/src/utils/bem.tsx +++ b/web/libs/datamanager/src/utils/bem.tsx @@ -188,15 +188,20 @@ export const BemWithSpecifiContext = (context?: Context) => { const Block = forwardRef( , D extends TagNames>( - { tag = "div", name, mod, mix, ...rest }: WrappedComponentProps, + { tag = "div", name, mod, mix, rawClassName, ...rest }: WrappedComponentProps, ref: any, ) => { const rootClass = cn(name); const finalMix = ([] as [CNMix?]).concat(mix).filter((cn) => !!cn); - const className = rootClass - .mod(mod) - .mix(...(finalMix as CNMix[]), rest.className) - .toClassName(); + const className = [ + rootClass + .mod(mod) + .mix(...(finalMix as CNMix[]), rest.className) + .toClassName(), + rawClassName, + ] + .filter(Boolean) + .join(" "); const finalProps = tag.toString() === "Symbol(react.fragment)" ? { ...rest, ref } : ({ ...rest, ref, className } as any); @@ -212,18 +217,23 @@ export const BemWithSpecifiContext = (context?: Context) => { const Elem = forwardRef( , D extends TagNames>( - { tag = "div", component, block, name, mod, mix, ...rest }: WrappedComponentProps, + { tag = "div", component, block, name, mod, mix, rawClassName, ...rest }: WrappedComponentProps, ref: any, ) => { const blockCtx = useContext(Context); const finalMix = ([] as [CNMix?]).concat(mix).filter((cn) => !!cn); - const className = (block ? cn(block) : blockCtx)! - .elem(name) - .mod(mod) - .mix(...(finalMix as CNMix[]), rest.className) - .toClassName(); + const className = [ + (block ? cn(block) : blockCtx)! + .elem(name) + .mod(mod) + .mix(...(finalMix as CNMix[]), rest.className) + .toClassName(), + rawClassName, + ] + .filter(Boolean) + .join(" "); const finalProps: any = { ...rest, ref, className }; diff --git a/web/libs/editor/src/common/Button/Button.tsx b/web/libs/editor/src/common/Button/Button.tsx index 126d77f95bea..9beb8ad4cbf2 100644 --- a/web/libs/editor/src/common/Button/Button.tsx +++ b/web/libs/editor/src/common/Button/Button.tsx @@ -1,3 +1,4 @@ +import { Tooltip } from "@humansignal/ui"; import type Keymaster from "keymaster"; import { type ButtonHTMLAttributes, @@ -12,7 +13,6 @@ import { Hotkey } from "../../core/Hotkey"; import { useHotkey } from "../../hooks/useHotkey"; import { Block, type CNTagName, Elem } from "../../utils/bem"; import { isDefined } from "../../utils/utilities"; -import { Tooltip } from "@humansignal/ui"; import "./Button.scss"; type HTMLButtonProps = Omit, "type">; diff --git a/web/libs/editor/src/common/Hotkey/WithHotkey.tsx b/web/libs/editor/src/common/Hotkey/WithHotkey.tsx new file mode 100644 index 000000000000..301d120250af --- /dev/null +++ b/web/libs/editor/src/common/Hotkey/WithHotkey.tsx @@ -0,0 +1,36 @@ +import { Children, cloneElement, forwardRef, type ReactElement } from "react"; +import { Hotkey } from "../../core/Hotkey"; +import type { ButtonProps } from "@humansignal/ui"; +import type Keymaster from "keymaster"; +import { useHotkey } from "../../hooks/useHotkey"; +import { isDefined } from "../../utils/utilities"; + +type HotkeyProps = { + binging?: keyof typeof Hotkey.keymap; + hotkeyScope?: string; + displayedHotkey?: keyof typeof Hotkey.keymap; + children: ReactElement; +}; +export const WithHotkey = forwardRef( + ({ children, binging: hotkey, hotkeyScope, displayedHotkey }: HotkeyProps, ref) => { + Children.only(children); + + const { onClick, tooltip } = children.props; + const clone = cloneElement(children, { ref, tooltip: undefined }); + + useHotkey(hotkey, onClick as unknown as Keymaster.KeyHandler, hotkeyScope); + + if ( + (hotkey && isDefined(Hotkey.keymap[hotkey])) || + (displayedHotkey && isDefined(Hotkey.keymap[displayedHotkey])) + ) { + return ( + + {clone} + + ); + } + + return clone; + }, +); diff --git a/web/libs/editor/src/common/Modal/Modal.jsx b/web/libs/editor/src/common/Modal/Modal.jsx index 4a62804e67af..7f1b2384743f 100644 --- a/web/libs/editor/src/common/Modal/Modal.jsx +++ b/web/libs/editor/src/common/Modal/Modal.jsx @@ -1,7 +1,7 @@ import { createRef } from "react"; import { render } from "react-dom"; import { cn } from "../../utils/bem"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import { Space } from "../Space/Space"; import { Modal } from "./ModalPopup"; @@ -51,8 +51,10 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro onCancel?.(); modal.close(); }} - size="compact" + size="small" + look="outlined" autoFocus + aria-label="Cancel" > {cancelText ?? "Cancel"} @@ -62,8 +64,9 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro onOk?.(); modal.close(); }} - size="compact" + size="small" look={buttonLook ?? "primary"} + aria-label="Confirm" > {okText ?? "OK"} @@ -84,8 +87,8 @@ export const info = ({ okText, onOkPress, ...props }) => { onOkPress?.(); modal.close(); }} - look="primary" - size="compact" + size="small" + aria-label={okText ?? "OK"} > {okText ?? "OK"} diff --git a/web/libs/editor/src/common/Modal/ModalPopup.jsx b/web/libs/editor/src/common/Modal/ModalPopup.jsx index 32f469b5e628..d2f4f667ce75 100644 --- a/web/libs/editor/src/common/Modal/ModalPopup.jsx +++ b/web/libs/editor/src/common/Modal/ModalPopup.jsx @@ -3,7 +3,7 @@ import { createPortal } from "react-dom"; import { IconRemove } from "@humansignal/icons"; import { BemWithSpecifiContext, cn } from "../../utils/bem"; import { aroundTransition } from "@humansignal/core/lib/utils/transition"; -import { Button } from "../Button/Button"; +import { Button } from "@humansignal/ui"; import "./Modal.scss"; const { Block, Elem } = BemWithSpecifiContext(); @@ -76,7 +76,9 @@ export class Modal extends Component { {this.state.title} {this.props.allowClose !== false && ( - } /> + )} )} diff --git a/web/libs/editor/src/common/Pagination/Pagination.tsx b/web/libs/editor/src/common/Pagination/Pagination.tsx index 71d8878bc4c8..bc084a3f7e30 100644 --- a/web/libs/editor/src/common/Pagination/Pagination.tsx +++ b/web/libs/editor/src/common/Pagination/Pagination.tsx @@ -1,8 +1,7 @@ import { type ChangeEvent, type FC, forwardRef, type KeyboardEvent, useCallback, useMemo, useState } from "react"; -import { Hotkey } from "../../core/Hotkey"; -import { useHotkey } from "../../hooks/useHotkey"; import { Block, Elem } from "../../utils/bem"; import { Select } from "@humansignal/ui"; +import { WithHotkey } from "../Hotkey/WithHotkey"; import "./Pagination.scss"; interface PaginationProps { @@ -173,13 +172,9 @@ const NavigationButton: FC = ({ mod, disabled, hotkey, on buttonMod.disabled = disabled === true; - useHotkey(hotkey, actionHandler); - - return hotkey ? ( - + return ( + - - ) : ( - + ); }; diff --git a/web/libs/editor/src/components/AnnotationTab/AutoAcceptToggle.jsx b/web/libs/editor/src/components/AnnotationTab/AutoAcceptToggle.jsx index 47397557e826..9ef34aa43e0c 100644 --- a/web/libs/editor/src/components/AnnotationTab/AutoAcceptToggle.jsx +++ b/web/libs/editor/src/components/AnnotationTab/AutoAcceptToggle.jsx @@ -1,10 +1,9 @@ import { inject, observer } from "mobx-react"; import { IconCheck, IconCross } from "@humansignal/icons"; -import { Toggle } from "@humansignal/ui"; -import { Button } from "../../common/Button/Button"; -import { Block, Elem } from "../../utils/bem"; +import { Button, Toggle } from "@humansignal/ui"; import { Space } from "../../common/Space/Space"; +import { Block, Elem } from "../../utils/bem"; import "./AutoAcceptToggle.scss"; diff --git a/web/libs/editor/src/components/Annotations/Annotations.jsx b/web/libs/editor/src/components/Annotations/Annotations.jsx index 00f1a9ce102b..9f428282780f 100644 --- a/web/libs/editor/src/components/Annotations/Annotations.jsx +++ b/web/libs/editor/src/components/Annotations/Annotations.jsx @@ -1,5 +1,6 @@ import { Component } from "react"; -import { Badge, Button, Card, List, Popconfirm } from "antd"; +import { Badge, Card, List, Popconfirm } from "antd"; +import { Button } from "@humansignal/ui"; import { Tooltip } from "@humansignal/ui"; import { observer } from "mobx-react"; import { @@ -31,14 +32,13 @@ export const DraftPanel = observer(({ item }) => { } return (
    - - - + {item.draftSelected ? "draft" : "submitted"} + {saved}
    ); @@ -46,36 +46,35 @@ export const DraftPanel = observer(({ item }) => { const Annotation = observer(({ item, store }) => { const removeHoney = () => ( - + + ); + + const setHoney = () => { + const title = item.ground_truth ? "Unset this result as a ground truth" : "Set this result as a ground truth"; + + return ( - - ); - - const setHoney = () => { - const title = item.ground_truth ? "Unset this result as a ground truth" : "Set this result as a ground truth"; - - return ( - - - ); }; @@ -158,7 +157,7 @@ const Annotation = observer(({ item, store }) => { okType="danger" cancelText="Cancel" > - @@ -196,7 +195,12 @@ const Annotation = observer(({ item, store }) => { )} {store.annotationStore.viewingAll && ( - )} @@ -218,34 +222,33 @@ class Annotations extends Component {
    {store.hasInterface("annotations:add-new") && ( - - - - )} -   - - + )} +   +
    ); diff --git a/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.scss b/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.scss index 8d0fe0e4ccbc..c5a5b1c49981 100644 --- a/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.scss +++ b/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.scss @@ -20,7 +20,7 @@ display: flex; gap: 2px; white-space: nowrap; - padding-right: 70px; + padding-right: 77px; position: relative; transform: translateX(calc(-1 * var(--carousel-left))); transition: all 0.15s ease-in-out 0s; @@ -77,4 +77,4 @@ } } } -} \ No newline at end of file +} diff --git a/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.tsx b/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.tsx index 7134a57f960a..953a73bb7f22 100644 --- a/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.tsx +++ b/web/libs/editor/src/components/AnnotationsCarousel/AnnotationsCarousel.tsx @@ -1,12 +1,9 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { Button, IconChevronLeft, IconChevronRight } from "@humansignal/ui"; import { observer } from "mobx-react"; - -import { IconChevron } from "@humansignal/ui"; -import { Button } from "../../common/Button/Button"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Block, Elem } from "../../utils/bem"; import { clamp, sortAnnotations } from "../../utils/utilities"; import { AnnotationButton } from "./AnnotationButton"; - import "./AnnotationsCarousel.scss"; interface AnnotationsCarouselInterface { @@ -29,7 +26,7 @@ export const AnnotationsCarousel = observer(({ store, annotationStore }: Annotat const [isRightDisabled, setIsRightDisabled] = useState(false); const updatePosition = useCallback( - (e: MouseEvent, goLeft = true) => { + (e: React.MouseEvent, goLeft = true) => { if (containerRef.current && carouselRef.current) { const step = containerRef.current.clientWidth; const carouselWidth = carouselRef.current.clientWidth; @@ -86,26 +83,24 @@ export const AnnotationsCarousel = observer(({ store, annotationStore }: Annotat {(!isLeftDisabled || !isRightDisabled) && ( - !isLeftDisabled && updatePosition(e, true)} + size="small" + variant="neutral" + onClick={(e) => !isLeftDisabled && updatePosition(e, true)} > - - - + + )} diff --git a/web/libs/editor/src/components/App/App.jsx b/web/libs/editor/src/components/App/App.jsx index 67cd16247e3c..d6d8c6d68fe7 100644 --- a/web/libs/editor/src/components/App/App.jsx +++ b/web/libs/editor/src/components/App/App.jsx @@ -24,7 +24,7 @@ import "../../tags/visual"; * Utils and common components */ import { Space } from "../../common/Space/Space"; -import { Button } from "../../common/Button/Button"; +import { Button } from "@humansignal/ui"; import { Block, Elem } from "../../utils/bem"; import { isSelfServe } from "../../utils/billing"; import { @@ -104,7 +104,12 @@ class App extends Component { All tasks in the queue have been completed {store.taskHistory.length > 0 && ( - )} diff --git a/web/libs/editor/src/components/App/Grid.jsx b/web/libs/editor/src/components/App/Grid.jsx index 1ee64bd38429..b0d55f1054cd 100644 --- a/web/libs/editor/src/components/App/Grid.jsx +++ b/web/libs/editor/src/components/App/Grid.jsx @@ -1,5 +1,6 @@ import React, { Component } from "react"; -import { Button, Spin } from "antd"; +import { Spin } from "antd"; +import { Button } from "@humansignal/ui"; import { LeftCircleOutlined, RightCircleOutlined } from "@ant-design/icons"; import styles from "./Grid.module.scss"; import { EntityTab } from "../AnnotationTabs/AnnotationTabs"; @@ -212,8 +213,12 @@ export default class Grid extends Component { )} - + ); } diff --git a/web/libs/editor/src/components/BottomBar/Actions.jsx b/web/libs/editor/src/components/BottomBar/Actions.jsx index 01aa646d07e1..0603ab1b9b8f 100644 --- a/web/libs/editor/src/components/BottomBar/Actions.jsx +++ b/web/libs/editor/src/components/BottomBar/Actions.jsx @@ -1,13 +1,12 @@ import { IconInfoOutline, IconSettings } from "@humansignal/icons"; -import { Tooltip } from "@humansignal/ui"; -import { Button } from "../../common/Button/Button"; +import { Button, Space } from "@humansignal/ui"; import { Elem } from "../../utils/bem"; import { isSelfServe } from "../../utils/billing"; import { FF_BULK_ANNOTATION } from "../../utils/feature-flags"; -import { EditingHistory } from "./HistoryActions"; -import { DynamicPreannotationsToggle } from "../AnnotationTab/DynamicPreannotationsToggle"; import { AutoAcceptToggle } from "../AnnotationTab/AutoAcceptToggle"; +import { DynamicPreannotationsToggle } from "../AnnotationTab/DynamicPreannotationsToggle"; import { GroundTruth } from "../CurrentEntity/GroundTruth"; +import { EditingHistory } from "./HistoryActions"; export const Actions = ({ store }) => { const annotationStore = store.annotationStore; @@ -17,37 +16,33 @@ export const Actions = ({ store }) => { const isBulkMode = isFF(FF_BULK_ANNOTATION) && !isSelfServe() && store.hasInterface("annotation:bulk"); return ( - + {!isPrediction && !isViewAll && store.hasInterface("edit-history") && } {store.description && store.hasInterface("instruction") && ( - - + )} + {store.hasInterface("ground-truth") && !isBulkMode && } @@ -57,6 +52,6 @@ export const Actions = ({ store }) => { )} - +
    ); }; diff --git a/web/libs/editor/src/components/BottomBar/BottomBar.scss b/web/libs/editor/src/components/BottomBar/BottomBar.scss index 8e7002f265a8..ef891726f78e 100644 --- a/web/libs/editor/src/components/BottomBar/BottomBar.scss +++ b/web/libs/editor/src/components/BottomBar/BottomBar.scss @@ -20,9 +20,9 @@ &__section { display: flex; padding: 0 var(--spacing-tight); - gap: var(--spacing-tight); align-items: center; box-sizing: border-box; + gap: var(--spacing-small); &_flat { padding: 0; diff --git a/web/libs/editor/src/components/BottomBar/Controls.scss b/web/libs/editor/src/components/BottomBar/Controls.scss index 32749a9d221c..f6b075a29ef2 100644 --- a/web/libs/editor/src/components/BottomBar/Controls.scss +++ b/web/libs/editor/src/components/BottomBar/Controls.scss @@ -108,6 +108,7 @@ font-size: 16px; svg { + color: var(--color-negative-icon); margin: 0 8px 0 4px; } } @@ -171,8 +172,4 @@ pointer-events: none; transition: all 150ms ease-out; } - - &:hover::before { - opacity: 1; - } } diff --git a/web/libs/editor/src/components/BottomBar/Controls.tsx b/web/libs/editor/src/components/BottomBar/Controls.tsx index 4e00a5b75e15..9a323cc7df4d 100644 --- a/web/libs/editor/src/components/BottomBar/Controls.tsx +++ b/web/libs/editor/src/components/BottomBar/Controls.tsx @@ -8,8 +8,8 @@ import { observer } from "mobx-react"; import type React from "react"; import { useCallback, useState } from "react"; -import { IconBan, IconChevron } from "@humansignal/ui"; -import { Button } from "../../common/Button/Button"; +import { Button, ButtonGroup, type ButtonProps } from "@humansignal/ui"; +import { IconBan, IconChevronDown } from "@humansignal/icons"; import { Dropdown } from "../../common/Dropdown/Dropdown"; import type { CustomButtonType } from "../../stores/CustomButton"; import { Block, cn, Elem } from "../../utils/bem"; @@ -38,6 +38,8 @@ type CustomButtonsField = Map< type ControlButtonProps = { button: CustomButtonType; disabled: boolean; + variant?: ButtonProps["variant"]; + look?: ButtonProps["look"]; onClick: (e: React.MouseEvent) => void; }; @@ -46,28 +48,21 @@ export const EMPTY_SUBMIT_TOOLTIP = "Empty annotations denied in this project"; /** * Custom action button component, rendering buttons from store.customButtons */ -const ControlButton = observer(({ button, disabled, onClick }: ControlButtonProps) => { - const look = button.disabled || disabled ? "disabled" : button.look; - - const result = ( +const ControlButton = observer(({ button, disabled, onClick, variant, look }: ControlButtonProps) => { + return ( ); - if (!button.tooltip) { - return result; - } - return ( - - {result} - - ); }); export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( @@ -78,7 +73,7 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( const { userGenerate, sentUserGenerate, versions, results, editable: annotationEditable } = annotation; const dropdownTrigger = cn("dropdown").elem("trigger").toClassName(); const customButtons: CustomButtonsField = store.customButtons; - const buttons = []; + const buttons: React.ReactNode[] = []; const [isInProgress, setIsInProgress] = useState(false); const disabled = !annotationEditable || store.isSubmitting || historySelected || isInProgress; @@ -120,7 +115,7 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( ], ); - if (annotation.isNonEditableDraft) return null; + if (annotation.isNonEditableDraft) return <>; const buttonsBefore = customButtons.get("_before"); const buttonsReplacement = customButtons.get("_replace"); @@ -153,10 +148,14 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( if (buttonsReplacement) { // do nothing as all custom buttons are rendered already and we don't need internal buttons - } else if (isReview) { + return {buttons}; + } + + if (isReview) { const customRejectButtons = toArray(customButtons.get("reject")); const hasCustomReject = customRejectButtons.length > 0; const originalRejectButton = RejectButtonDefinition; + // @todo implement reuse of internal buttons later (they are set as strings) const rejectButtons: CustomButtonType[] = hasCustomReject ? customRejectButtons.filter((button) => typeof button !== "string") @@ -183,7 +182,7 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( } else if (annotation.skipped) { buttons.push( - Was skipped + Was skipped , ); buttons.push(); @@ -197,37 +196,40 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( } const isDisabled = disabled || submitDisabled; - const look = isDisabled ? "disabled" : "primary"; const useExitOption = !isDisabled && isNotQuickView; const SubmitOption = ({ isUpdate, onClickMethod }: { isUpdate: boolean; onClickMethod: () => any }) => { return ( - + await store.commentStore.commentFormSubmit(); + onClickMethod(); + }} + > + {`${isUpdate ? "Update" : "Submit"} and exit`} + + ); }; @@ -237,35 +239,38 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( buttons.push( - + {useExitOption ? ( + + - - ) : undefined - } - > - Submit - + } + > + + + ) : null} + , ); @@ -278,35 +283,34 @@ export const Controls = controlsInjector<{ annotation: MSTAnnotation }>( const isUpdateDisabled = isDisabled || noChanges; const button = ( - + selected?.submissionInProgress(); + await store.commentStore.commentFormSubmit(); + store.updateAnnotation(); + }} + > + {isUpdate ? "Update" : "Submit"} + + {useExitOption ? ( + } + > + + + ) : null} + ); diff --git a/web/libs/editor/src/components/BottomBar/CurrentTask.jsx b/web/libs/editor/src/components/BottomBar/CurrentTask.jsx index fcaa4bf1e70e..fc163ae284e7 100644 --- a/web/libs/editor/src/components/BottomBar/CurrentTask.jsx +++ b/web/libs/editor/src/components/BottomBar/CurrentTask.jsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; import { observer } from "mobx-react"; -import { Button } from "../../common/Button/Button"; +import { Button, IconChevronLeft, IconChevronRight } from "@humansignal/ui"; import { Block, Elem } from "../../utils/bem"; import { guidGenerator } from "../../utils/unique"; import { isDefined } from "../../utils/utilities"; @@ -40,29 +40,22 @@ export const CurrentTask = observer(({ store }) => { {historyEnabled && ( - - + + + )} diff --git a/web/libs/editor/src/components/BottomBar/HistoryActions.jsx b/web/libs/editor/src/components/BottomBar/HistoryActions.jsx index c9364d977270..30b5094c639a 100644 --- a/web/libs/editor/src/components/BottomBar/HistoryActions.jsx +++ b/web/libs/editor/src/components/BottomBar/HistoryActions.jsx @@ -1,48 +1,52 @@ import { observer } from "mobx-react"; import { IconRedo, IconRemove, IconUndo } from "@humansignal/icons"; -import { Tooltip } from "@humansignal/ui"; -import { Button } from "../../common/Button/Button"; -import { Block, Elem } from "../../utils/bem"; +import { Tooltip, Button, Space } from "@humansignal/ui"; import "./HistoryActions.scss"; export const EditingHistory = observer(({ entity }) => { const { history } = entity; return ( - + - entity.undo()} - icon={} - /> + className="!p-0" + > + + - entity.redo()} - icon={} - /> + className="!p-0" + > + + - history?.reset()} - icon={} - /> + className="!p-0" + > + + - + ); }); diff --git a/web/libs/editor/src/components/BottomBar/__tests__/Controls.test.tsx b/web/libs/editor/src/components/BottomBar/__tests__/Controls.test.tsx index ffc835f6ada6..dd28df737f94 100644 --- a/web/libs/editor/src/components/BottomBar/__tests__/Controls.test.tsx +++ b/web/libs/editor/src/components/BottomBar/__tests__/Controls.test.tsx @@ -2,22 +2,32 @@ import { render, fireEvent } from "@testing-library/react"; import { Provider } from "mobx-react"; import { Controls } from "../Controls"; -jest.mock("@humansignal/ui", () => ({ - Tooltip: ({ children }: { children: React.ReactNode }) => { - return
    {children}
    ; - }, - Userpic: ({ children }: { children: React.ReactNode }) => { - return ( -
    - {children} -
    - ); - }, -})); +jest.mock("@humansignal/ui", () => { + const { forwardRef } = jest.requireActual("react"); + return { + Button: forwardRef(({ children, ...props }: { children: React.ReactNode }) => { + return ( + + ); + }), + Tooltip: ({ children }: { children: React.ReactNode }) => { + return
    {children}
    ; + }, + Userpic: ({ children }: { children: React.ReactNode }) => { + return ( +
    + {children} +
    + ); + }, + }; +}); const mockStore = { hasInterface: jest.fn(), isSubmitting: false, @@ -95,7 +105,8 @@ describe("Controls", () => { }); test("When skip button is clicked, if there is no currentComment and annotators doesn't need to leave a comment on skip, it must submit", async () => { - mockStore.hasInterface = (a: string) => a === "skip" ?? true; + mockStore.hasInterface = (a: string) => a === "skip"; + const { getByLabelText } = render( diff --git a/web/libs/editor/src/components/BottomBar/buttons.tsx b/web/libs/editor/src/components/BottomBar/buttons.tsx index ac75c82f489f..6c1c36b3dd09 100644 --- a/web/libs/editor/src/components/BottomBar/buttons.tsx +++ b/web/libs/editor/src/components/BottomBar/buttons.tsx @@ -7,8 +7,7 @@ import { inject, observer } from "mobx-react"; import type React from "react"; import { memo, type ReactElement } from "react"; -import { Button } from "../../common/Button/Button"; -import { Tooltip } from "@humansignal/ui"; +import { Tooltip, Button } from "@humansignal/ui"; type MixedInParams = { store: MSTStore; @@ -50,22 +49,20 @@ type AcceptButtonProps = { export const AcceptButton = memo( observer(({ disabled, history, store }: AcceptButtonProps) => { return ( - - - + selected?.submissionInProgress(); + await store.commentStore.commentFormSubmit(); + store.acceptAnnotation(); + }} + > + {history.canUndo ? "Fix + Accept" : "Accept"} + ); }), ); @@ -74,7 +71,8 @@ export const RejectButtonDefinition = { id: "reject", name: "reject", title: "Reject", - look: undefined, + variant: "negative", + look: "outlined", ariaLabel: "reject-annotation", tooltip: "Reject annotation: [ Ctrl+Space ]", // @todo we need this for types compatibility, but better to fix CustomButtonType @@ -94,26 +92,26 @@ type SkipButtonProps = { export const SkipButton = memo( observer(({ disabled, store, onSkipWithComment }: SkipButtonProps) => { return ( - - - + if (store.hasInterface("comments:skip") ?? true) { + onSkipWithComment(e, action); + } else { + selected?.submissionInProgress(); + await store.commentStore.commentFormSubmit(); + store.skipTask({}); + } + }} + > + Skip + ); }), ); @@ -121,22 +119,22 @@ export const SkipButton = memo( export const UnskipButton = memo( observer(({ disabled, store }: { disabled: boolean; store: MSTStore }) => { return ( - - - + selected?.submissionInProgress(); + await store.commentStore.commentFormSubmit(); + store.unskipTask(); + }} + > + Cancel skip + ); }), ); diff --git a/web/libs/editor/src/components/Comments/Comment/CommentItem.tsx b/web/libs/editor/src/components/Comments/Comment/CommentItem.tsx index bbc4573546b5..b8c0acb1dac6 100644 --- a/web/libs/editor/src/components/Comments/Comment/CommentItem.tsx +++ b/web/libs/editor/src/components/Comments/Comment/CommentItem.tsx @@ -3,7 +3,7 @@ import type React from "react"; import { type FC, useCallback, useContext, useMemo, useState } from "react"; import { Tooltip, Userpic } from "@humansignal/ui"; import { IconCheck, IconEllipsis } from "@humansignal/icons"; -import { Button } from "../../../common/Button/Button"; +import { Button } from "@humansignal/ui"; import { Dropdown } from "../../../common/Dropdown/Dropdown"; import { Menu } from "../../../common/Menu/Menu"; import { Space } from "../../../common/Space/Space"; @@ -203,10 +203,16 @@ export const CommentItem: FC = observer( Are you sure? - - @@ -271,7 +277,7 @@ export const CommentItem: FC = observer( } > - diff --git a/web/libs/editor/src/components/Comments/OldComment/CommentForm.tsx b/web/libs/editor/src/components/Comments/OldComment/CommentForm.tsx index 2c1a4b76c624..e41feaf48f3e 100644 --- a/web/libs/editor/src/components/Comments/OldComment/CommentForm.tsx +++ b/web/libs/editor/src/components/Comments/OldComment/CommentForm.tsx @@ -86,7 +86,7 @@ export const CommentForm: FC = observer( onBlur={clearTooltipMessage} /> - diff --git a/web/libs/editor/src/components/Comments/OldComment/CommentItem.tsx b/web/libs/editor/src/components/Comments/OldComment/CommentItem.tsx index 1ad8ab78a146..956a8ee4638e 100644 --- a/web/libs/editor/src/components/Comments/OldComment/CommentItem.tsx +++ b/web/libs/editor/src/components/Comments/OldComment/CommentItem.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react"; import { type FC, useState } from "react"; -import { Tooltip, Userpic } from "@humansignal/ui"; +import { Tooltip, Userpic, Button } from "@humansignal/ui"; import { IconCheck, IconEllipsis } from "@humansignal/icons"; import { Space } from "../../../common/Space/Space"; import { Dropdown } from "../../../common/Dropdown/Dropdown"; @@ -10,7 +10,6 @@ import { humanDateDiff, userDisplayName } from "../../../utils/utilities"; import { CommentFormBase } from "../CommentFormBase"; import "./CommentItem.scss"; -import { Button } from "../../../common/Button/Button"; interface Comment { comment: { @@ -115,10 +114,22 @@ export const CommentItem: FC = observer( Are you sure? - - @@ -168,7 +179,7 @@ export const CommentItem: FC = observer( } > - + , + ); + + buttons.push( + + + , + ); + } else if (annotation.skipped) { + buttons.push( + + Annotation skipped + , + ); + } else { + if (store.hasInterface("skip")) { + buttons.push( + + + , + ); + } + + if ((userGenerate && !sentUserGenerate) || (store.explore && !userGenerate && store.hasInterface("submit"))) { + const title = submitDisabled ? "Empty annotations denied in this project" : "Save results"; + // span is to display tooltip for disabled button + + buttons.push( + + + + + , + ); + } + + if ((userGenerate && sentUserGenerate) || (!userGenerate && store.hasInterface("update"))) { + buttons.push( + + + , + ); + } + } + + return {buttons}; + }), +); diff --git a/web/libs/editor/src/components/CurrentEntity/GroundTruth.jsx b/web/libs/editor/src/components/CurrentEntity/GroundTruth.jsx index d132f61d0976..a352493d1fe0 100644 --- a/web/libs/editor/src/components/CurrentEntity/GroundTruth.jsx +++ b/web/libs/editor/src/components/CurrentEntity/GroundTruth.jsx @@ -1,7 +1,6 @@ import { observer } from "mobx-react"; -import { Button } from "../../common/Button/Button"; import { IconStar, IconStarOutline } from "@humansignal/icons"; -import { Tooltip } from "@humansignal/ui"; +import { Button, Tooltip } from "@humansignal/ui"; import { BemWithSpecifiContext } from "../../utils/bem"; import { FF_DEV_3873, isFF } from "../../utils/feature-flags"; import "./GroundTruth.scss"; @@ -17,11 +16,10 @@ export const GroundTruth = observer(({ entity, disabled = false, size = "md" }) entity.type !== "prediction" && ( - { ev.preventDefault(); entity.setGroundTruth(!entity.ground_truth); @@ -32,7 +30,7 @@ export const GroundTruth = observer(({ entity, disabled = false, size = "md" }) tag={isFF(FF_DEV_3873) && !entity.ground_truth ? IconStarOutline : IconStar} mod={{ active: entity.ground_truth, dark: isFF(FF_DEV_3873) }} /> - + ) diff --git a/web/libs/editor/src/components/CurrentEntity/HistoryActions.jsx b/web/libs/editor/src/components/CurrentEntity/HistoryActions.jsx new file mode 100644 index 000000000000..96f8aec5e476 --- /dev/null +++ b/web/libs/editor/src/components/CurrentEntity/HistoryActions.jsx @@ -0,0 +1,45 @@ +import { IconRedo, IconRemove, IconUndo } from "@humansignal/icons"; +import { Button, Tooltip } from "@humansignal/ui"; +import { observer } from "mobx-react"; +import { Hotkey } from "../../core/Hotkey"; +import { Block } from "../../utils/bem"; +import "./HistoryActions.scss"; + +export const HistoryActions = observer(({ annotation }) => { + const { history } = annotation; + + return ( + + + - - + + +
    diff --git a/web/libs/editor/src/components/DraftPanel/DraftPanel.jsx b/web/libs/editor/src/components/DraftPanel/DraftPanel.jsx index f483b8145c94..b500fd479c59 100644 --- a/web/libs/editor/src/components/DraftPanel/DraftPanel.jsx +++ b/web/libs/editor/src/components/DraftPanel/DraftPanel.jsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { Tooltip } from "@humansignal/ui"; +import { Button, Tooltip } from "@humansignal/ui"; import Utils from "../../utils"; import { cn } from "../../utils/bem"; @@ -24,9 +24,16 @@ export const DraftPanel = observer(({ item }) => { alignment="top-left" title={item.draftSelected ? "switch to original result" : "switch to current draft"} > - + {saved} diff --git a/web/libs/editor/src/components/Entities/Entities.jsx b/web/libs/editor/src/components/Entities/Entities.jsx new file mode 100644 index 000000000000..79ca0113c8c2 --- /dev/null +++ b/web/libs/editor/src/components/Entities/Entities.jsx @@ -0,0 +1,108 @@ +import { observer } from "mobx-react"; + +import "./Entities.scss"; +import { RegionTree } from "./RegionTree"; +import { LabelList } from "./LabelList"; +import { SortMenu, SortMenuIcon } from "./SortMenu"; +import { Oneof } from "../../common/Oneof/Oneof"; +import { Space } from "../../common/Space/Space"; +import { Block, Elem } from "../../utils/bem"; +import { RadioGroup } from "../../common/RadioGroup/RadioGroup"; +import "./Entities.scss"; +import { confirm } from "../../common/Modal/Modal"; +import { IconInvisible, IconTrash, IconVisible } from "@humansignal/icons"; +import { Button, Tooltip } from "@humansignal/ui"; +import { Dropdown } from "../../common/Dropdown/Dropdown"; +import { cn } from "@humansignal/ui"; + +export default observer(({ regionStore, annotation }) => { + const { classifications, regions, view } = regionStore; + const count = regions.length + (view === "regions" ? classifications.length : 0); + const toggleVisibility = (e) => { + e.preventDefault(); + e.stopPropagation(); + regionStore.toggleVisibility(); + }; + + return ( + + + + { + regionStore.setView(e.target.value); + }} + > + + Regions{count ?  {count} : null} + + Labels + + + {annotation.isReadOnly() && ( + + + + )} + + + + {count ? ( + + + {view === "regions" && ( + } placement="bottomLeft"> + e.preventDefault()}> + + + {" "} + {`Sorted by ${regionStore.sort[0].toUpperCase()}${regionStore.sort.slice(1)}`} + + + )} + + + {regions.length > 0 ? ( + + ) : null} + + + + ) : null} + + + + {count ? : No Regions created yet} + + + {count ? : No Labeled Regions created yet} + + + + ); +}); diff --git a/web/libs/editor/src/components/Entities/LabelItem.jsx b/web/libs/editor/src/components/Entities/LabelItem.jsx new file mode 100644 index 000000000000..3ae26028c133 --- /dev/null +++ b/web/libs/editor/src/components/Entities/LabelItem.jsx @@ -0,0 +1,42 @@ +import { List } from "antd"; +import { observer } from "mobx-react"; +import { Button } from "@humansignal/ui"; +import { Block, Elem } from "../../utils/bem"; +import { Space } from "../../common/Space/Space"; +import { IconInvisible, IconVisible } from "@humansignal/icons"; +import { Label } from "../Label/Label"; +import { asVars } from "../../utils/styles"; +import "./LabelItem.scss"; + +export const LabelItem = observer(({ item, regions, regionStore }) => { + const color = item.background; + const vars = asVars({ color }); + + const isHidden = Object.values(regions).reduce((acc, item) => acc && item.hidden, true); + const count = Object.values(regions).length; + + return ( + + + + {!item.isNotLabel ? ( + + ) : ( + <>Not labeled + )} + {`${count} Region${count === 0 || count > 1 ? "s" : ""}`} + + : } + onClick={() => regionStore.setHiddenByLabel(!isHidden, item)} + mod={{ hidden: isHidden }} + /> + + + ); +}); diff --git a/web/libs/editor/src/components/Entities/RegionItem.jsx b/web/libs/editor/src/components/Entities/RegionItem.jsx new file mode 100644 index 000000000000..74be0eccb66d --- /dev/null +++ b/web/libs/editor/src/components/Entities/RegionItem.jsx @@ -0,0 +1,157 @@ +import { Badge, List } from "antd"; +import { observer } from "mobx-react"; +import { isAlive } from "mobx-state-tree"; +import { Button } from "@humansignal/ui"; +import { Node, NodeIcon } from "../Node/Node"; +import { IconCollapse, IconExpandTool, IconInvisible, IconSparks, IconVisible } from "@humansignal/icons"; +import styles from "./Entities.module.scss"; +import Utils from "../../utils"; + +import { Block, Elem } from "../../utils/bem"; +import { isDefined } from "../../utils/utilities"; +import "./RegionItem.scss"; +import { Space } from "../../common/Space/Space"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { asVars } from "../../utils/styles"; +import { PER_REGION_MODES } from "../../mixins/PerRegion"; +import Registry from "../../core/Registry"; +import chroma from "chroma-js"; + +const RegionItemDesc = observer(({ item, setDraggable }) => { + const [collapsed, setCollapsed] = useState(false); + const toggleCollapsed = useCallback((e) => { + setCollapsed((val) => !val); + e.preventDefault(); + e.stopPropagation(); + }, []); + const controls = item.perRegionDescControls || []; + + return ( + 0) }} + onMouseEnter={() => { + setDraggable?.(false); + }} + onMouseLeave={() => { + setDraggable?.(true); + }} + > + + {controls.map((tag, idx) => { + const View = Registry.getPerRegionView(tag.type, PER_REGION_MODES.REGION_LIST); + + return View ? ( + + ) : null; + })} + + + + ); +}); + +const RegionItemContent = observer(({ idx, item, setDraggable }) => { + const itemElRef = useRef(); + + useEffect(() => { + if (item.selected) { + const el = itemElRef.current; + + if (!el) return; + const scroll = el.scrollIntoViewIfNeeded || el.scrollIntoView; + + scroll.call(el); + } + }, [item.selected]); + return ( + + + {isDefined(idx) ? idx + 1 : ""} + + + + + + + + + + {item.origin === "prediction" && } + + + {item.isReadOnly() && } + + {item.score && ( + + {item.score.toFixed(2)} + + )} + + {item.hideable && ( + + )} + + + + + ); +}); + +export const RegionItem = observer(({ item, idx, flat, setDraggable, onClick }) => { + const getVars = useMemo(() => { + let vars; + + return () => { + if (!vars) { + const color = item.getOneColor(); + + vars = color ? asVars({ labelColor: color, labelBgColor: chroma(color).alpha(0.15) }) : null; + } + return vars; + }; + }, [isAlive(item) && item.getOneColor()]); + + if (!isAlive(item)) return null; + + const classnames = [ + styles.lstitem, + flat && styles.flat, + item.hidden === true && styles.hidden, + item.inSelection && styles.selected, + ].filter(Boolean); + + const vars = getVars(); + + return ( + { + onClick(e, item); + }} + onMouseOver={() => item.setHighlight(true)} + onMouseOut={() => item.setHighlight(false)} + style={vars} + aria-label="region" + > + + + ); +}); diff --git a/web/libs/editor/src/components/Entity/Entity.jsx b/web/libs/editor/src/components/Entity/Entity.jsx new file mode 100644 index 000000000000..a81bbfc5807c --- /dev/null +++ b/web/libs/editor/src/components/Entity/Entity.jsx @@ -0,0 +1,240 @@ +/** + * @deprecated It was only used in old interface without FF_1170 and FF_3873 + */ + +import React, { Fragment } from "react"; +import { observer } from "mobx-react"; +import { Badge, Form, Input, Typography } from "antd"; +import { CompressOutlined, DeleteOutlined, LinkOutlined, PlusOutlined } from "@ant-design/icons"; +import { CREATE_RELATION_MODE } from "../../stores/Annotation/LinkingModes"; + +import { NodeDebug, NodeMinimal } from "../Node/Node"; +import Hint from "../Hint/Hint"; +import styles from "./Entity.module.scss"; +import { IconWarning } from "@humansignal/icons"; +import { Tooltip, Button } from "@humansignal/ui"; +import { Tag } from "../../common/Tag/Tag"; +import { Space } from "../../common/Space/Space"; +import { Block, cn, Elem } from "../../utils/bem"; +import "./Entity.scss"; +import { PER_REGION_MODES } from "../../mixins/PerRegion"; +import { Hotkey } from "../../core/Hotkey"; + +const { Paragraph, Text } = Typography; + +const renderLabels = (element) => { + return element.selectedLabels?.length ? ( + + Labels:  + {element.selectedLabels.map((label) => { + const bgColor = label.background || "#000000"; + + return ( + + {label.value} + + ); + })} + + ) : null; +}; + +const renderResult = (result) => { + if (result.type.endsWith("labels")) { + return renderLabels(result); + } + if (result.type === "rating") { + return Rating: {result.mainValue}; + } + if ( + result.type === "textarea" && + !(result.from_name.perregion && result.from_name.displaymode === PER_REGION_MODES.REGION_LIST) + ) { + return ( + + Text: + + {result.mainValue.join("\n")} + + + ); + } + if (result.type === "choices") { + return Choices: {result.mainValue.join(", ")}; + } + + return null; +}; + +export default observer(({ store, annotation }) => { + const { highlightedNode: node, selectedRegions: nodes, selectionSize } = annotation; + const [editMode, setEditMode] = React.useState(false); + + const entityButtons = []; + const hasEditableNodes = !!nodes.find((node) => !node.isReadOnly()); + const hasEditableRegions = !!nodes.find((node) => !node.isReadOnly() && !node.classification); + + const Node = window.HTX_DEBUG ? NodeDebug : NodeMinimal; + + if (hasEditableRegions) { + entityButtons.push( + + + , + ); + + entityButtons.push( + + + , + ); + } + + entityButtons.push( + + + , + ); + + const entityStatesClassName = cn("entity-states").toClassName(); + const entityButtonsClassName = cn("entity-buttons").toClassName(); + + return ( + + + + {node ? ( + <> + (ID: {node.id}) + + ) : ( + `${selectionSize} Region${selectionSize > 1 ? "s are" : " is"} selected` + )} + + {!hasEditableNodes && } + +
    + {node?.score && ( + + + Score: {node.score} + + + )} + + {node?.meta?.text && ( + + Meta: {node.meta.text} +   + { + node.deleteMetaText(); + }} + /> + + )} + + {node?.results.map(renderResult)} +
    + + {node?.isDrawing && ( + + + Incomplete {node.type.replace("region", "")} + + )} + +
    + + {entityButtons} + + {hasEditableNodes && ( + + + + )} + +
    + + {editMode && ( + { + // `normInput` is undefined, but this component is not used in the new interface anyway + node.setMetaText(node.normInput); + setEditMode(false); + }} + > + { + const { value } = ev.target; + + node.setMetaText(value); + }} + className="mb-2" + placeholder="Meta Information" + /> + + + + + + )} +
    + ); +}); diff --git a/web/libs/editor/src/components/Filter/Filter.tsx b/web/libs/editor/src/components/Filter/Filter.tsx new file mode 100644 index 000000000000..c05bc3084e83 --- /dev/null +++ b/web/libs/editor/src/components/Filter/Filter.tsx @@ -0,0 +1,132 @@ +import { Block, Elem } from "../../utils/bem"; +import { Dropdown } from "../../common/Dropdown/Dropdown"; + +import { type FC, useCallback, useEffect, useMemo, useState } from "react"; +import { Button } from "@humansignal/ui"; +import { IconFilter } from "@humansignal/icons"; + +import "./Filter.scss"; +import type { FilterInterface, FilterListInterface } from "./FilterInterfaces"; +import { FilterRow } from "./FilterRow"; +import { FilterItems } from "./filter-util"; +import { FF_DEV_3873, isFF } from "../../utils/feature-flags"; + +export const Filter: FC = ({ availableFilters, filterData, onChange, animated = true }) => { + const [filterList, setFilterList] = useState([]); + const [active, setActive] = useState(false); + + useEffect(() => { + onChange(FilterItems(filterData, filterList)); + }, [filterData]); + + const addNewFilterListItem = useCallback(() => { + setFilterList((filterList) => [ + ...filterList, + { + field: availableFilters[0]?.label ?? "", + logic: "and", + operation: "", + value: "", + path: "", + }, + ]); + }, [setFilterList, availableFilters]); + + const onChangeRow = useCallback( + (index: number, { field, operation, value, path, logic }: Partial) => { + setFilterList((oldList) => { + const newList = [...oldList]; + + newList[index] = { + ...newList[index], + field: field ?? newList[index].field, + operation: operation ?? newList[index].operation, + logic: logic ?? newList[index].logic, + value: value ?? newList[index].value, + path: path ?? newList[index].path, + }; + + onChange(FilterItems(filterData, newList)); + + return newList; + }); + }, + [setFilterList, filterData], + ); + + const onDeleteRow = useCallback( + (index: number) => { + setFilterList((oldList) => { + const newList = [...oldList]; + + newList.splice(index, 1); + + if (newList[0]) { + newList[0].logic = "and"; + } + + onChange(FilterItems(filterData, newList)); + + return newList; + }); + }, + [setFilterList, filterData], + ); + + const renderFilterList = useMemo(() => { + return filterList.map(({ field, operation, logic, value }, index) => ( + + + + )); + }, [filterList, availableFilters, onDeleteRow, onChangeRow]); + + const renderFilter = useMemo(() => { + return ( + + {filterList.length > 0 ? renderFilterList : No filters applied} + + + ); + }, [filterList, renderFilterList, addNewFilterListItem]); + + const onToggle = useCallback((isOpen: boolean) => { + setActive(isOpen); + }, []); + + return ( + + + + + + + Filter + + {filterList.length > 0 && ( + + {filterList.length} + + )} + + + ); +}; diff --git a/web/libs/editor/src/components/HtxTextBox/HtxTextBox.jsx b/web/libs/editor/src/components/HtxTextBox/HtxTextBox.jsx index 1e9ef97fe97d..a8448be3fa7d 100644 --- a/web/libs/editor/src/components/HtxTextBox/HtxTextBox.jsx +++ b/web/libs/editor/src/components/HtxTextBox/HtxTextBox.jsx @@ -1,9 +1,8 @@ import React from "react"; import { Typography } from "antd"; import { EnterOutlined } from "@ant-design/icons"; -import { Button } from "../../common/Button/Button"; import { IconEdit, IconTrashAlt } from "@humansignal/icons"; -import { Tooltip } from "@humansignal/ui"; +import { Button, Tooltip } from "@humansignal/ui"; import styles from "./HtxTextBox.module.scss"; import throttle from "lodash.throttle"; @@ -182,31 +181,36 @@ export class HtxTextBox extends React.Component { {text} - {isEditable && onChange && ( - {store.setPrelabeling && ( )} @@ -86,6 +86,7 @@ export default observer(({ store }) => {
    {store.description && store.showingDescription && ( + ); }; diff --git a/web/libs/editor/src/components/SidePanels/Components/LockButton.tsx b/web/libs/editor/src/components/SidePanels/Components/LockButton.tsx index 988d2cfcc23c..5bbfcc65923b 100644 --- a/web/libs/editor/src/components/SidePanels/Components/LockButton.tsx +++ b/web/libs/editor/src/components/SidePanels/Components/LockButton.tsx @@ -1,9 +1,10 @@ import { observer } from "mobx-react"; -import type { FC } from "react"; +import { forwardRef, type FC } from "react"; import { IconLockLocked, IconLockUnlocked } from "@humansignal/icons"; -import type { ButtonProps } from "../../../common/Button/Button"; import { RegionControlButton } from "./RegionControlButton"; import { FF_DEV_3873, isFF } from "../../../utils/feature-flags"; +import type { ButtonProps } from "@humansignal/ui"; +import type { HotkeyList } from "libs/editor/src/core/Hotkey"; export const LockButton: FC<{ item: any; @@ -14,30 +15,45 @@ export const LockButton: FC<{ look?: ButtonProps["look"]; style?: ButtonProps["style"]; onClick: () => void; -}> = observer(({ item, annotation, hovered, locked, hotkey, look, style, onClick }) => { - if (!item) return null; - const isLocked = locked || item.isReadOnly() || annotation.isReadOnly(); - const isRegionReadonly = item.isReadOnly() && !locked; +}> = observer( + forwardRef(({ item, annotation, hovered, locked, hotkey, look, style, onClick }, ref) => { + if (!item) return null; + const isLocked = locked || item.isReadOnly() || annotation.isReadOnly(); + const isRegionReadonly = item.isReadOnly() && !locked; - if (isFF(FF_DEV_3873)) { - const styles = { - ...style, - display: item.isReadOnly() || locked ? undefined : "none", - }; + if (isFF(FF_DEV_3873)) { + const styles = { + ...style, + display: item.isReadOnly() || locked ? undefined : "none", + }; + + return ( + + {isLocked ? : } + + ); + } return ( - - {isLocked ? : } - + item && + (hovered || item.isReadOnly() || locked) && ( + + {isLocked ? : } + + ) ); - } - - return ( - item && - (hovered || item.isReadOnly() || locked) && ( - - {isLocked ? : } - - ) - ); -}); + }), +); diff --git a/web/libs/editor/src/components/SidePanels/Components/RegionContextMenu.tsx b/web/libs/editor/src/components/SidePanels/Components/RegionContextMenu.tsx index a405d7744e48..ee37f35655d1 100644 --- a/web/libs/editor/src/components/SidePanels/Components/RegionContextMenu.tsx +++ b/web/libs/editor/src/components/SidePanels/Components/RegionContextMenu.tsx @@ -2,9 +2,8 @@ import { observer } from "mobx-react"; import { useCallback, useMemo, useState, type FC } from "react"; import { useCopyText } from "@humansignal/core/lib/hooks/useCopyText"; import { IconLink, IconEllipsis } from "@humansignal/icons"; -import { ToastType, useToast } from "@humansignal/ui"; +import { Button, ToastType, useToast } from "@humansignal/ui"; import { ContextMenu, type ContextMenuAction, ContextMenuTrigger, type MenuActionOnClick } from "../../ContextMenu"; -import { Button } from "../../../common/Button/Button"; import { cn } from "../../../utils/bem"; export const RegionContextMenu: FC<{ item: any }> = observer(({ item }: { item: any }) => { @@ -52,8 +51,10 @@ export const RegionContextMenu: FC<{ item: any }> = observer(({ item }: { item: onToggle={(isOpen) => setOpen(isOpen)} > diff --git a/web/libs/editor/src/components/SidePanels/Components/RegionControlButton.tsx b/web/libs/editor/src/components/SidePanels/Components/RegionControlButton.tsx index 0a12f5109e27..e9b433c00b78 100644 --- a/web/libs/editor/src/components/SidePanels/Components/RegionControlButton.tsx +++ b/web/libs/editor/src/components/SidePanels/Components/RegionControlButton.tsx @@ -1,18 +1,29 @@ -import type { FC } from "react"; -import { Button, type ButtonProps } from "../../../common/Button/Button"; +import { forwardRef, type FC } from "react"; +import { Button, type ButtonProps } from "@humansignal/ui"; +import { WithHotkey } from "../../../common/Hotkey/WithHotkey"; +import type { HotkeyList } from "libs/editor/src/core/Hotkey"; -export const RegionControlButton: FC = ({ children, onClick, ...props }) => { +export const RegionControlButton: FC< + ButtonProps & { + hotkey: HotkeyList; + } +> = forwardRef(({ children, onClick, hotkey, ...props }, ref) => { return ( - + + + ); -}; +}); diff --git a/web/libs/editor/src/components/SidePanels/DetailsPanel/RegionItem.tsx b/web/libs/editor/src/components/SidePanels/DetailsPanel/RegionItem.tsx index c22e85bf8066..be6272e72db2 100644 --- a/web/libs/editor/src/components/SidePanels/DetailsPanel/RegionItem.tsx +++ b/web/libs/editor/src/components/SidePanels/DetailsPanel/RegionItem.tsx @@ -1,13 +1,14 @@ import chroma from "chroma-js"; import { observer } from "mobx-react"; -import { type FC, useMemo, useState } from "react"; +import { type FC, forwardRef, useMemo, useState } from "react"; import { IconRelationLink, IconPlus, IconTrash, IconWarning, IconEyeClosed, IconEyeOpened } from "@humansignal/icons"; -import { Button, type ButtonProps } from "../../../common/Button/Button"; +import { Button, type ButtonProps } from "@humansignal/ui"; import { CREATE_RELATION_MODE } from "../../../stores/Annotation/LinkingModes"; import { Block, Elem } from "../../../utils/bem"; import { NodeIcon } from "../../Node/Node"; import { LockButton } from "../Components/LockButton"; import { RegionLabels } from "./RegionLabels"; +import { WithHotkey } from "../../../common/Hotkey/WithHotkey"; interface RegionItemProps { region: any; @@ -96,33 +97,39 @@ const RegionAction: FC = observer(({ region, annotation, editMode, onEditMo const entityButtons: JSX.Element[] = []; entityButtons.push( - } - primary={annotation.isLinkingMode} - onClick={(_e: any, hotkey?: any) => { - // If this is triggered by a hotkey, defer to the global bound handler for relations to avoid contention. - if (hotkey) return; - if (annotation.isLinkingMode) { - annotation.stopLinkingMode(); - } else { - annotation.startLinkingMode(CREATE_RELATION_MODE, region); - } - }} - hotkey="region:relation" - aria-label="Create Relation" - />, + + { + // If this is triggered by a hotkey, defer to the global bound handler for relations to avoid contention. + if (hotkey) return; + if (annotation.isLinkingMode) { + annotation.stopLinkingMode(); + } else { + annotation.startLinkingMode(CREATE_RELATION_MODE, region); + } + }} + aria-label="Create Relation" + > + + + , ); entityButtons.push( - } - primary={editMode} - onClick={() => onEditModeChange(!editMode)} - hotkey="region:meta" - aria-label="Edit region's meta" - />, + + onEditModeChange(!editMode)} + aria-label="Edit region's meta" + > + + + , ); return ( @@ -138,32 +145,32 @@ const RegionAction: FC = observer(({ region, annotation, editMode, onEditMo locked={region?.locked} onClick={() => region.setLocked(!region.locked)} hotkey="region:lock" - look="alt" + aria-label={`${region.locked ? "Unlock" : "Lock"} selected region`} style={{ width: 36, height: 32 }} /> : } - onClick={region.toggleHidden} - displayedHotkey="region:visibility" aria-label={`${region.hidden ? "Show" : "Hide"} selected region`} - /> + onClick={region.toggleHidden} + > + {region.hidden ? : } + } onClick={() => annotation.deleteRegion(region)} - displayedHotkey="region:delete" - aria-label="Delete selected region" - /> + > + + ); }); -const RegionActionButton: FC = ({ children, ...props }) => { +const RegionActionButton: FC = forwardRef(({ children, ...props }, ref) => { return ( - ); -}; +}); diff --git a/web/libs/editor/src/components/SidePanels/DetailsPanel/Relations.tsx b/web/libs/editor/src/components/SidePanels/DetailsPanel/Relations.tsx index c3dbaaf418ec..d62e6c62d748 100644 --- a/web/libs/editor/src/components/SidePanels/DetailsPanel/Relations.tsx +++ b/web/libs/editor/src/components/SidePanels/DetailsPanel/Relations.tsx @@ -9,7 +9,7 @@ import { IconEyeClosed, IconEyeOpened, } from "@humansignal/icons"; -import { Button } from "../../../common/Button/Button"; +import { Button } from "@humansignal/ui"; import { Block, Elem } from "../../../utils/bem"; import { wrapArray } from "../../../utils/utilities"; import { RegionItem } from "./RegionItem"; diff --git a/web/libs/editor/src/components/SidePanels/DetailsPanel/RelationsControls.tsx b/web/libs/editor/src/components/SidePanels/DetailsPanel/RelationsControls.tsx index dee3029ae640..42da800b917b 100644 --- a/web/libs/editor/src/components/SidePanels/DetailsPanel/RelationsControls.tsx +++ b/web/libs/editor/src/components/SidePanels/DetailsPanel/RelationsControls.tsx @@ -1,7 +1,7 @@ import { type FC, useCallback } from "react"; import { observer } from "mobx-react"; import { Block, Elem } from "../../../utils/bem"; -import { Button } from "../../../common/Button/Button"; +import { Button } from "@humansignal/ui"; import "./RelationsControls.scss"; import { IconOutlinerEyeClosed, IconOutlinerEyeOpened, IconSortUp, IconSortDown } from "@humansignal/icons"; @@ -34,7 +34,8 @@ const ToggleRelationsVisibilityButton = observer> return ( ({ }, [value, optionsList, readableValue, direction, onChange]); // mods are already set in the button from type, so use it only in new UI - const extraStyles = isFF(FF_DEV_3873) ? { mod: { newUI: true } } : undefined; - const style = isFF(FF_DEV_3873) ? { padding: "0 12px 0 2px" } : {}; return ( @@ -275,22 +273,20 @@ const ToggleRegionsVisibilityButton = observer const isAllHidden = !isDisabled && regions.isAllHidden; return ( - - ) : ( - - ) - } tooltip={isAllHidden ? "Show all regions" : "Hide all regions"} - tooltipTheme="dark" - /> + > + {isAllHidden ? ( + + ) : ( + + )} + ); }); diff --git a/web/libs/editor/src/components/Taxonomy/Taxonomy.tsx b/web/libs/editor/src/components/Taxonomy/Taxonomy.tsx index 2b6f27400225..df82bfc60fad 100644 --- a/web/libs/editor/src/components/Taxonomy/Taxonomy.tsx +++ b/web/libs/editor/src/components/Taxonomy/Taxonomy.tsx @@ -435,7 +435,7 @@ const TaxonomyDropdown = ({ show, flatten, items, dropdownRef, isEditable }: Tax ) : isEditable ? (
    -
    diff --git a/web/libs/editor/src/components/Timeline/Controls.scss b/web/libs/editor/src/components/Timeline/Controls.scss index aa0a62016b64..3f18cb92c8a5 100644 --- a/web/libs/editor/src/components/Timeline/Controls.scss +++ b/web/libs/editor/src/components/Timeline/Controls.scss @@ -39,6 +39,7 @@ display: flex; max-width: 310px; justify-content: space-between; + gap: var(--spacing-tight) } diff --git a/web/libs/editor/src/components/Timeline/Controls.tsx b/web/libs/editor/src/components/Timeline/Controls.tsx index 9533c352fd14..af1ffa09d50d 100644 --- a/web/libs/editor/src/components/Timeline/Controls.tsx +++ b/web/libs/editor/src/components/Timeline/Controls.tsx @@ -1,4 +1,3 @@ -import { type FC, memo, type MouseEvent, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { IconBackward, IconChevronLeft, @@ -15,13 +14,18 @@ import { IconTimelinePause, IconTimelinePlay, } from "@humansignal/icons"; -import { Button, type ButtonProps } from "../../common/Button/Button"; -import { Space } from "../../common/Space/Space"; -import { Hotkey } from "../../core/Hotkey"; +import { Button, type ButtonProps, Space } from "@humansignal/ui"; +import { type FC, memo, type MouseEvent, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { WithHotkey } from "../../common/Hotkey/WithHotkey"; +import { Hotkey, type HotkeyList } from "../../core/Hotkey"; import { Block, Elem } from "../../utils/bem"; +import { FF_DEV_2715, isFF } from "../../utils/feature-flags"; import { isDefined } from "../../utils/utilities"; +import { TimeDurationControl } from "../TimeDurationControl/TimeDurationControl"; import { TimelineContext } from "./Context"; import "./Controls.scss"; +import { AudioControl } from "./Controls/AudioControl"; +import { ConfigControl } from "./Controls/ConfigControl"; import * as SideControls from "./SideControls"; import type { TimelineControlsFormatterOptions, @@ -31,10 +35,6 @@ import type { TimelineProps, TimelineStepFunction, } from "./Types"; -import { FF_DEV_2715, isFF } from "../../utils/feature-flags"; -import { AudioControl } from "./Controls/AudioControl"; -import { ConfigControl } from "./Controls/ConfigControl"; -import { TimeDurationControl } from "../TimeDurationControl/TimeDurationControl"; const positionFromTime = ({ time, fps }: TimelineControlsFormatterOptions) => { const roundedFps = Math.round(fps).toString(); @@ -174,7 +174,7 @@ export const Controls: FC = memo( {isFF(FF_DEV_2715) && mediaType === "audio" ? ( renderControls() ) : ( - + {props.controls && Object.entries(props.controls).map(([name, enabled]) => { if (enabled === false) return; @@ -195,13 +195,11 @@ export const Controls: FC = memo( ); })} {customControls?.left} - + )} - - - {extraControls} - - + + {extraControls} + {customControls?.leftCenter} = memo( onClick={stepHandlerWrapper(onStepBackward, settings.stepSize)} hotkey={settings?.stepAltBack} disabled={startReached} + aria-label="Hop backward" > {} @@ -220,6 +219,7 @@ export const Controls: FC = memo( onClick={stepHandlerWrapper(onStepBackward)} hotkey={settings?.stepBackHotkey} disabled={startReached} + aria-label="Step backward" > @@ -231,6 +231,7 @@ export const Controls: FC = memo( onClick={() => onRewind?.()} disabled={startReached} hotkey={settings?.skipToBeginning} + aria-label="Skip to start" > @@ -238,6 +239,7 @@ export const Controls: FC = memo( onClick={() => onRewind?.(altHopSize)} disabled={startReached} hotkey={settings?.hopBackward} + aria-label="Media rewind" > @@ -249,6 +251,7 @@ export const Controls: FC = memo( onClick={handlePlay} hotkey={settings?.playpauseHotkey} hotkeyScope={Hotkey.ALL_SCOPES} + aria-label="Play" > {playing ? : } @@ -260,15 +263,16 @@ export const Controls: FC = memo( onClick={stepHandlerWrapper(onStepForward)} hotkey={settings?.stepForwardHotkey} disabled={endReached} + aria-label="Step forward" > - {} {settings?.stepSize && !disableFrames && ( @@ -281,17 +285,23 @@ export const Controls: FC = memo( onClick={() => onForward?.(altHopSize)} disabled={endReached} hotkey={settings?.hopForward} + aria-label="Media fast forward" > - onForward?.()} disabled={endReached} hotkey={settings?.skipToEnd}> + onForward?.()} + disabled={endReached} + hotkey={settings?.skipToEnd} + > } /> {customControls?.rightCenter} - + {!disableFrames && allowViewCollapse && ( onToggleCollapsed?.(!collapsed)}> @@ -304,7 +314,7 @@ export const Controls: FC = memo( )} - + {isFF(FF_DEV_2715) && mediaType === "audio" ? ( @@ -339,11 +349,13 @@ export const Controls: FC = memo( }, ); -export const ControlButton: FC = ({ children, ...props }) => { +export const ControlButton: FC = ({ children, ...props }) => { return ( - + + + ); }; diff --git a/web/libs/editor/src/components/Timeline/Controls/AudioControl.tsx b/web/libs/editor/src/components/Timeline/Controls/AudioControl.tsx index 0c0c2d8486e8..21433deeb3a9 100644 --- a/web/libs/editor/src/components/Timeline/Controls/AudioControl.tsx +++ b/web/libs/editor/src/components/Timeline/Controls/AudioControl.tsx @@ -79,7 +79,7 @@ export const AudioControl: FC = ({ volume, onVolumeChange, on return ( ) => e.stopPropagation()}> - + {isMuted ? : } {audioModal && renderModal()} diff --git a/web/libs/editor/src/components/Tools/Basic.jsx b/web/libs/editor/src/components/Tools/Basic.jsx index e8acc6b3af2c..eb6f348f5456 100644 --- a/web/libs/editor/src/components/Tools/Basic.jsx +++ b/web/libs/editor/src/components/Tools/Basic.jsx @@ -1,6 +1,5 @@ import { Component } from "react"; -import { Button } from "antd"; -import { Tooltip } from "@humansignal/ui"; +import { Button, Tooltip } from "@humansignal/ui"; import { observer } from "mobx-react"; import styles from "./Styles.module.scss"; @@ -12,11 +11,12 @@ export default observer( @@ -36,7 +35,7 @@ export default observer( return ( - diff --git a/web/libs/editor/src/components/TopBar/Actions.jsx b/web/libs/editor/src/components/TopBar/Actions.jsx index 33f5b16a3cb3..e32a017591a0 100644 --- a/web/libs/editor/src/components/TopBar/Actions.jsx +++ b/web/libs/editor/src/components/TopBar/Actions.jsx @@ -1,4 +1,4 @@ -import { Button } from "../../common/Button/Button"; +import { Button } from "@humansignal/ui"; import { IconCopy, IconInfo, IconViewAll, IconTrash, IconSettings } from "@humansignal/icons"; import { Tooltip } from "@humansignal/ui"; import { Elem } from "../../utils/bem"; @@ -27,7 +27,7 @@ export const Actions = ({ store }) => { + )} diff --git a/web/libs/editor/src/components/TopBar/HistoryActions.jsx b/web/libs/editor/src/components/TopBar/HistoryActions.jsx index f05babe0e030..25177beb5f4b 100644 --- a/web/libs/editor/src/components/TopBar/HistoryActions.jsx +++ b/web/libs/editor/src/components/TopBar/HistoryActions.jsx @@ -1,7 +1,6 @@ import { observer } from "mobx-react"; -import { Button } from "../../common/Button/Button"; import { IconRedo, IconRemove, IconUndo } from "@humansignal/icons"; -import { Tooltip } from "@humansignal/ui"; +import { Button, Tooltip } from "@humansignal/ui"; import { Block, Elem } from "../../utils/bem"; import "./HistoryActions.scss"; @@ -11,33 +10,36 @@ export const EditingHistory = observer(({ entity }) => { return ( - entity.undo()} - icon={} - /> + > + + - entity.redo()} - icon={} - /> + > + + history?.reset()} diff --git a/web/libs/editor/src/components/TopBar/TopBar.jsx b/web/libs/editor/src/components/TopBar/TopBar.jsx index b4800a031017..9bf19f2ffb6c 100644 --- a/web/libs/editor/src/components/TopBar/TopBar.jsx +++ b/web/libs/editor/src/components/TopBar/TopBar.jsx @@ -1,8 +1,7 @@ import { observer } from "mobx-react"; -import { Button } from "../../common/Button/Button"; import { IconViewAll, IconPlus } from "@humansignal/icons"; -import { Tooltip } from "@humansignal/ui"; +import { Button, Tooltip } from "@humansignal/ui"; import { Block, Elem } from "../../utils/bem"; import { isSelfServe } from "../../utils/billing"; import { FF_BULK_ANNOTATION, FF_DEV_3873, isFF } from "../../utils/feature-flags"; @@ -31,44 +30,37 @@ export const TopBar = observer(({ store }) => { {store.hasInterface("annotations:view-all") && ( - - )} {store.hasInterface("annotations:add-new") && ( )} {!isViewAll && ( diff --git a/web/libs/editor/src/components/TopBar/TopBar.scss b/web/libs/editor/src/components/TopBar/TopBar.scss index 21bbcfaa3438..420b71e3d974 100644 --- a/web/libs/editor/src/components/TopBar/TopBar.scss +++ b/web/libs/editor/src/components/TopBar/TopBar.scss @@ -21,6 +21,7 @@ &__group { display: flex; align-items: stretch; + gap: var(--spacing-tight); } &__section { @@ -52,4 +53,4 @@ } } } -} \ No newline at end of file +} diff --git a/web/libs/editor/src/core/Hotkey.ts b/web/libs/editor/src/core/Hotkey.ts index 8fde130c1edb..1ddc7eaf8beb 100644 --- a/web/libs/editor/src/core/Hotkey.ts +++ b/web/libs/editor/src/core/Hotkey.ts @@ -422,7 +422,7 @@ Hotkey.setScope = (scope: string) => { */ Hotkey.Tooltip = inject("store")( observer(({ store, name, children, ...props }: any) => { - const hotkey = Hotkey.keymap[name as string]; + const hotkey = Hotkey.keymap[name as keyof typeof Hotkey.keymap]; const enabled = store.settings.enableTooltips && store.settings.enableHotkeys; if (isDefined(hotkey)) { @@ -478,7 +478,7 @@ Hotkey.Tooltip = inject("store")( */ Hotkey.Hint = inject("store")( observer(({ store, name }: any) => { - const hotkey = Hotkey.keymap[name]; + const hotkey = Hotkey.keymap[name as HotkeyList]; const enabled = store.settings.enableTooltips && store.settings.enableHotkeys; if (isDefined(hotkey) && enabled) { @@ -491,6 +491,8 @@ Hotkey.Hint = inject("store")( }), ); +export type HotkeyList = keyof typeof Hotkey.keymap; + export default { DEFAULT_SCOPE, INPUT_SCOPE, diff --git a/web/libs/editor/src/stores/CustomButton.ts b/web/libs/editor/src/stores/CustomButton.ts index 2e55cefca10f..eb8c80cfd019 100644 --- a/web/libs/editor/src/stores/CustomButton.ts +++ b/web/libs/editor/src/stores/CustomButton.ts @@ -13,9 +13,11 @@ export const CustomButton = types id: types.optional(types.identifier, guidGenerator), name: types.string, title: types.string, - look: types.maybe( - types.enumeration(["primary", "danger", "destructive", "alt", "outlined", "active", "disabled"] as const), + variant: types.maybe( + types.enumeration(["primary", "neutral", "positive", "negative", "warning", "inverted"] as const), ), + look: types.maybe(types.enumeration(["filled", "outlined", "string"])), + size: types.maybe(types.enumeration(["medium", "small", "smaller"])), tooltip: types.maybe(types.string), ariaLabel: types.maybe(types.string), disabled: types.maybe(types.boolean), diff --git a/web/libs/editor/src/stores/types.d.ts b/web/libs/editor/src/stores/types.d.ts index 4c6be3d1535e..64170db304e2 100644 --- a/web/libs/editor/src/stores/types.d.ts +++ b/web/libs/editor/src/stores/types.d.ts @@ -96,6 +96,7 @@ type MSTAnnotation = { names: Map; isLinkingMode: boolean; linkingMode: "create_relation" | "link_to_comment"; + isNonEditableDraft: boolean; submissionInProgress: () => void; }; @@ -142,7 +143,7 @@ type MSTCommentStore = { overlayComments: MSTComment[]; annotationId: string; annotation?: MSTAnnotation; - commentFormSubmit: () => void; + commentFormSubmit: () => Promise; setTooltipMessage: (message: string) => void; currentComment: any; addedCommentThisSession: boolean; diff --git a/web/libs/editor/src/tags/control/TextArea/TextArea.jsx b/web/libs/editor/src/tags/control/TextArea/TextArea.jsx index ffe296166750..41c6c094c575 100644 --- a/web/libs/editor/src/tags/control/TextArea/TextArea.jsx +++ b/web/libs/editor/src/tags/control/TextArea/TextArea.jsx @@ -1,5 +1,5 @@ import { createRef, useCallback } from "react"; -import Button from "antd/lib/button/index"; +import { Button } from "@humansignal/ui"; import Form from "antd/lib/form/index"; import Input from "antd/lib/input/index"; import { observer } from "mobx-react"; @@ -420,7 +420,7 @@ const HtxTextArea = observer(({ item }) => { )} {showAddButton && ( - diff --git a/web/libs/editor/src/tags/control/TextArea/TextAreaRegionView.jsx b/web/libs/editor/src/tags/control/TextArea/TextAreaRegionView.jsx index 99cdefba7137..6bd76a2fe7ce 100644 --- a/web/libs/editor/src/tags/control/TextArea/TextAreaRegionView.jsx +++ b/web/libs/editor/src/tags/control/TextArea/TextAreaRegionView.jsx @@ -78,11 +78,11 @@ const HtxTextAreaResultLine = forwardRef( {canDelete && !collapsed && !readOnly && ( } - size="small" - type="text" + leading={} onClick={() => { onDelete(idx); }} diff --git a/web/libs/editor/src/tags/object/Audio/Controls.jsx b/web/libs/editor/src/tags/object/Audio/Controls.jsx index 46a74a45599d..83f8e6642e8e 100644 --- a/web/libs/editor/src/tags/object/Audio/Controls.jsx +++ b/web/libs/editor/src/tags/object/Audio/Controls.jsx @@ -1,5 +1,5 @@ import { Fragment } from "react"; -import { Button } from "antd"; +import { Button } from "@humansignal/ui"; import { observer } from "mobx-react"; import { PauseCircleOutlined, PlayCircleOutlined } from "@ant-design/icons"; diff --git a/web/libs/editor/src/tags/object/Paragraphs/HtxParagraphs.jsx b/web/libs/editor/src/tags/object/Paragraphs/HtxParagraphs.jsx index be18db1fde6e..c0b1c467f082 100644 --- a/web/libs/editor/src/tags/object/Paragraphs/HtxParagraphs.jsx +++ b/web/libs/editor/src/tags/object/Paragraphs/HtxParagraphs.jsx @@ -487,6 +487,8 @@ class HtxParagraphsView extends Component { requestAnimationFrame(() => { const container = this.myRef.current; const mainContentView = document.querySelector(this.mainContentSelector); + + if (!mainContentView) return; const mainRect = mainContentView.getBoundingClientRect(); const visibleHeight = document.documentElement.clientHeight - mainRect.top; const annotationView = document.querySelector(this.mainViewAnnotationSelector); diff --git a/web/libs/editor/src/tags/object/Paragraphs/Phrases.jsx b/web/libs/editor/src/tags/object/Paragraphs/Phrases.jsx index 8b4160e51209..ca6b98bc095c 100644 --- a/web/libs/editor/src/tags/object/Paragraphs/Phrases.jsx +++ b/web/libs/editor/src/tags/object/Paragraphs/Phrases.jsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react"; import { getRoot } from "mobx-state-tree"; -import { Button } from "antd"; +import { Button } from "@humansignal/ui"; import { PauseCircleOutlined, PlayCircleOutlined } from "@ant-design/icons"; import styles from "./Paragraphs.module.scss"; import { FF_LSDV_E_278, isFF } from "../../../utils/feature-flags"; @@ -161,7 +161,7 @@ export const Phrases = observer(({ item, playingId, activeRef, setIsInViewport } > {isContentVisible && withAudio && !isNaN(v.start) && (
    diff --git a/web/libs/editor/tests/e2e/fragments/AtAudioView.js b/web/libs/editor/tests/e2e/fragments/AtAudioView.js index 45c7f3f357bf..1b4b9d917546 100644 --- a/web/libs/editor/tests/e2e/fragments/AtAudioView.js +++ b/web/libs/editor/tests/e2e/fragments/AtAudioView.js @@ -18,12 +18,9 @@ module.exports = { _hideTimelineButtonSelector: ".lsf-audio-config__buttons > .lsf-audio-config__menu-button:nth-child(1)", _hideWaveformButtonSelector: ".lsf-audio-config__buttons > .lsf-audio-config__menu-button:nth-child(2)", _audioElementSelector: '[data-testid="waveform-audio"]', - _seekBackwardButtonSelector: - ".lsf-audio-tag .lsf-timeline-controls__main-controls > .lsf-timeline-controls__group:nth-child(2) > button:nth-child(1)", - _playButtonSelector: - ".lsf-audio-tag .lsf-timeline-controls__main-controls > .lsf-timeline-controls__group:nth-child(2) > button:nth-child(2)", - _seekForwardButtonSelector: - ".lsf-audio-tag .lsf-timeline-controls__main-controls > .lsf-timeline-controls__group:nth-child(2) > button:nth-child(3)", + _seekBackwardButtonSelector: "button[aria-label='Seek backward']", + _playButtonSelector: "button[aria-label='Play']", + _seekForwardButtonSelector: "button[aria-label='Seek forward']", _errorSelector: '[data-testid="error:audio"]', _httpErrorSelector: '[data-testid="error:http"]', diff --git a/web/libs/editor/tests/e2e/fragments/AtVideoView.js b/web/libs/editor/tests/e2e/fragments/AtVideoView.js index 2dc5207b73f9..09f326078c97 100644 --- a/web/libs/editor/tests/e2e/fragments/AtVideoView.js +++ b/web/libs/editor/tests/e2e/fragments/AtVideoView.js @@ -16,10 +16,9 @@ module.exports = { _trackSelector: ".lsf-seeker__track", _indicatorSelector: ".lsf-seeker__indicator", _positionSelector: ".lsf-seeker__position", - _seekStepForwardSelector: ".lsf-timeline-controls__main-controls > div:nth-child(2) > button:nth-child(4)", - _seekStepBackwardSelector: ".lsf-timeline-controls__main-controls > div:nth-child(2) > button:nth-child(2)", - _playButtonSelector: - ".lsf-timeline-controls__main-controls > .lsf-timeline-controls__group:nth-child(2) > button:nth-child(2)", + _seekStepForwardSelector: 'button[aria-label="Step forward"]', + _seekStepBackwardSelector: 'button[aria-label="Step backward"]', + _playButtonSelector: 'button[aria-label="Play"]', locateRootSelector() { return locate(this._rootSelector); diff --git a/web/libs/editor/tests/e2e/tests/regression-tests/video-snapshot.test.js b/web/libs/editor/tests/e2e/tests/regression-tests/video-snapshot.test.js index a9cc80799edb..4f7e706d10cb 100644 --- a/web/libs/editor/tests/e2e/tests/regression-tests/video-snapshot.test.js +++ b/web/libs/editor/tests/e2e/tests/regression-tests/video-snapshot.test.js @@ -60,7 +60,7 @@ Scenario("Restoring video regions from snapshots", async ({ I, LabelStudio, AtOu I.say("delete region"); I.pressKey("Backspace"); I.say("undo action"); - I.click(".lsf-history-buttons__action[aria-label=Undo]"); + I.click("button[aria-label=Undo]"); const result = await LabelStudio.serialize(); assert.notStrictEqual(result[0].value.sequence.length, 0); diff --git a/web/libs/frontend-test/src/helpers/LSF/Relations.ts b/web/libs/frontend-test/src/helpers/LSF/Relations.ts index 6201d86e28ff..1c78fc6875ec 100644 --- a/web/libs/frontend-test/src/helpers/LSF/Relations.ts +++ b/web/libs/frontend-test/src/helpers/LSF/Relations.ts @@ -52,7 +52,7 @@ export const Relations = { this.hiddenRelations.should("have.length", count); }, toggleCreation() { - cy.get(".lsf-region-actions__group_align_left > :nth-child(1) > .lsf-button__icon").click(); + cy.get('button[aria-label="Create Relation"]').click(); }, toggleCreationWithHotkey() { // hotkey is alt + r diff --git a/web/libs/ui/src/assets/icons/interpolation-disabled.svg b/web/libs/ui/src/assets/icons/interpolation-disabled.svg index 326b51cb1366..85be91e5883c 100644 --- a/web/libs/ui/src/assets/icons/interpolation-disabled.svg +++ b/web/libs/ui/src/assets/icons/interpolation-disabled.svg @@ -1,6 +1,6 @@ - - + + diff --git a/web/libs/ui/src/assets/icons/keypoint-disabled.svg b/web/libs/ui/src/assets/icons/keypoint-disabled.svg index 8c546b437dab..a922c4d1317f 100644 --- a/web/libs/ui/src/assets/icons/keypoint-disabled.svg +++ b/web/libs/ui/src/assets/icons/keypoint-disabled.svg @@ -1,6 +1,6 @@ - + diff --git a/web/libs/ui/src/index.ts b/web/libs/ui/src/index.ts index f520acae5801..e85fd07d877a 100644 --- a/web/libs/ui/src/index.ts +++ b/web/libs/ui/src/index.ts @@ -1,19 +1,21 @@ -export * from "./lib/button/button"; export * from "./lib/Card/Card"; -export * from "./lib/checkbox/checkbox"; -export * from "./lib/code-block/code-block"; -export * from "./lib/enterprise-badge/enterprise-badge"; +export * from "./lib/InputFile/InputFile"; export * from "./lib/MultiStateToggle/MultiStateToggle"; export * from "./lib/ThemeToggle/ThemeToggle"; -export * from "./lib/InputFile/InputFile"; -export * from "./lib/label/label"; -export * from "./lib/toast/toast"; -export * from "./lib/toggle/toggle"; -export * from "./lib/select/select"; export * from "./lib/Tooltip/Tooltip"; export * from "./lib/Userpic/Userpic"; +export * from "./lib/badge/badge"; +export * from "./lib/button/button"; +export * from "./lib/checkbox/checkbox"; +export * from "./lib/code-block/code-block"; export * from "./lib/code-block/code-block"; export * from "./lib/code-editor/code-editor"; +export * from "./lib/enterprise-badge/enterprise-badge"; +export * from "./lib/label/label"; +export * from "./lib/select/select"; +export * from "./lib/skeleton/skeleton"; +export * from "./lib/toast/toast"; +export * from "./lib/toggle/toggle"; export * from "./assets/icons"; export * from "./lib/simple-card"; @@ -23,4 +25,7 @@ export * from "./lib/sparkles/sparkles"; export * from "./lib/popover/popover"; export * from "./lib/auto-sizer-table/auto-sizer-table"; +export * from "./utils/utils"; + +// TODO: Remove when DIA-2142 and DIA-2175 are delivered export * from "./shadcn"; diff --git a/web/libs/ui/src/lib/badge/badge.tsx b/web/libs/ui/src/lib/badge/badge.tsx new file mode 100644 index 000000000000..fa7f98c6ea9b --- /dev/null +++ b/web/libs/ui/src/lib/badge/badge.tsx @@ -0,0 +1 @@ +export { Badge } from "../../shad/components/ui/badge"; diff --git a/web/libs/ui/src/lib/button/button.module.scss b/web/libs/ui/src/lib/button/button.module.scss index 66b97065abdc..57746165ae96 100644 --- a/web/libs/ui/src/lib/button/button.module.scss +++ b/web/libs/ui/src/lib/button/button.module.scss @@ -39,10 +39,10 @@ &:disabled:not(.waiting):hover, &:disabled:not(.waiting):focus, &:disabled:not(.waiting):active { + --background-color: var(--color-neutral-surface); --background-color-hover: var(--color-neutral-surface); --background-color-active: var(--color-neutral-surface); --border-color-hover: var(--color-neutral-border); - --background-color: var(--color-neutral-surface); --border-color: var(--color-neutral-border); --text-color: var(--color-neutral-content-subtlest); @@ -149,7 +149,163 @@ --wait-color-opacity: 10%; } -/// VARIANT LOOK +.base.variant-gradient { + --background-color: var(--color-neutral-background); + --border-color: var(--color-accent-canteloupe-bold); + --focus-outline: var(--color-warning-focus-outline); + + @apply transition-all; + + background-image: linear-gradient( + 90deg, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 10%), + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 10%), + color-mix(in srgb, var(--color-accent-plum-base), transparent 10%) 100% + ); + + &:hover { + --background-color: var(--color-warning-emphasis-subtle,); + + background-image: linear-gradient( + 90deg, + var(--color-accent-canteloupe-base, #FFA663) 0%, + var(--color-accent-persimmon-base, #FF7557) 50%, + var(--color-accent-plum-base, #E37BD3) 100% + ); + } + + &.waiting { + background-image: linear-gradient( + 90deg, + var(--color-accent-canteloupe-base, #FFA663) 0%, + var(--color-accent-persimmon-base, #FF7557) 25%, + var(--color-accent-plum-base, #E37BD3) 50%, + var(--color-accent-persimmon-base, #FF7557) 75%, + var(--color-accent-canteloupe-base, #FFA663) 100% + ); + background-size: 200% 100%; + background-position: 0 0; + animation: none; + animation: gradient-button-waiting 2s linear infinite; + } + + &:active { + --background-color: var(--color-warning-emphasis-subtle,); + + background-image: linear-gradient( + 90deg, + var(--color-accent-canteloupe-dark, #664228) 0%, + var(--color-accent-persimmon-dark, #803B2C) 50%, + var(--color-accent-plum-dark, #723E6A) 100% + ); + } + + &:disabled { + background-image: none; + } + +} + +.base.variant-gradient.look-outlined:not(:disabled) { + --text-color: var(--color-accent-canteloupe-dark,); + --border-color: var(--color-warning-border-subtle); + + text-shadow: none; + background-image: linear-gradient( + 90deg, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 75%), + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 75%), + color-mix(in srgb, var(--color-accent-plum-base), transparent 75%) 100% + ); + + &:hover { + background-image: linear-gradient( + 90deg, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 80%), + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 80%), + color-mix(in srgb, var(--color-accent-plum-base), transparent 75%) 100% + ); + } + + &.waiting { + background-image: linear-gradient( + 90deg, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 80%) 0%, + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 80%) 25%, + color-mix(in srgb, var(--color-accent-plum-base), transparent 75%) 50%, + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 80%) 75%, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 80%) 100% + ); + background-size: 200% 100%; + } + + &:disabled { + --background-color: var(--color-neutral-surface); + --background-color-hover: var(--color-neutral-surface); + --background-color-active: var(--color-neutral-surface); + --border-color-hover: var(--color-neutral-border); + --border-color: var(--color-neutral-border); + --text-color: var(--color-neutral-content-subtlest); + + background-image: none; + } +} + +.base.variant-gradient.look-string { + &:not(:disabled) { + --text-color: transparent; + + background-image: none; + + & > span { + color: transparent; + background-clip: text; + background-image: linear-gradient( + 90deg, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 10%), + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 10%), + color-mix(in srgb, var(--color-accent-plum-base), transparent 10%) 100% + ); + } + + &.waiting { + border-color: transparent; + + & > span { + background-image: linear-gradient( + 90deg, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 10%), + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 10%), + color-mix(in srgb, var(--color-accent-plum-base), transparent 10%) 100%, + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 10%), + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 10%) + ); + background-size: 200% 100%; + animation: gradient-button-waiting 2s linear infinite; + } + } + + &:focus { + --border-color: var(--color-warning-border-subtle); + --focus-outline: var(--color-warning-focus-outline); + + box-shadow: var(--focus-shadow); + + & > span { + color: var(--color-accent-canteloupe-dark,); + } + + background-image: linear-gradient( + 90deg, + color-mix(in srgb, var(--color-accent-canteloupe-base), transparent 75%), + color-mix(in srgb, var(--color-accent-persimmon-base), transparent 75%), + color-mix(in srgb, var(--color-accent-plum-base), transparent 75%) 100% + ); + } + } +} + + /// VARIANT LOOK .look-outlined { &:not(:disabled), &.waiting { @@ -284,6 +440,12 @@ } } +@keyframes gradient-button-waiting { + to { + background-position: -200%; + } +} + /// ALIGN .align-default { & > span { diff --git a/web/libs/ui/src/lib/button/button.tsx b/web/libs/ui/src/lib/button/button.tsx index 2a064dfd5940..3a92ab8188a7 100644 --- a/web/libs/ui/src/lib/button/button.tsx +++ b/web/libs/ui/src/lib/button/button.tsx @@ -11,6 +11,7 @@ const variants = { positive: styles["variant-positive"], warning: styles["variant-warning"], inverted: styles["variant-neutral-interted"], + gradient: styles["variant-gradient"], }; const looks = { diff --git a/web/libs/ui/src/lib/skeleton/skeleton.tsx b/web/libs/ui/src/lib/skeleton/skeleton.tsx new file mode 100644 index 000000000000..07862160cb87 --- /dev/null +++ b/web/libs/ui/src/lib/skeleton/skeleton.tsx @@ -0,0 +1 @@ +export { Skeleton } from "../../shad/components/ui/skeleton"; diff --git a/web/libs/ui/src/lib/space/space.module.scss b/web/libs/ui/src/lib/space/space.module.scss index 10496138ba17..f9af7d952cc8 100644 --- a/web/libs/ui/src/lib/space/space.module.scss +++ b/web/libs/ui/src/lib/space/space.module.scss @@ -1,10 +1,9 @@ .space { display: grid; - grid-gap: 16px; - grid-auto-flow: column; align-items: center; + grid-auto-flow: column; grid-auto-columns: max-content; - + grid-gap: var(--spacing-tight); } .directionHorizontal { @@ -38,9 +37,9 @@ } .sizeLarge { - grid-gap: 32px; + grid-gap: var(--spacing-wider); } .sizeSmall { - grid-gap: 12px; + grid-gap: var(--spacing-tight); } diff --git a/web/libs/ui/src/lib/toast/toast.tsx b/web/libs/ui/src/lib/toast/toast.tsx index 4ef7964f051b..6934bf743d10 100644 --- a/web/libs/ui/src/lib/toast/toast.tsx +++ b/web/libs/ui/src/lib/toast/toast.tsx @@ -3,6 +3,8 @@ import * as ToastPrimitive from "@radix-ui/react-toast"; import styles from "./toast.module.scss"; import clsx from "clsx"; import { IconCross } from "../../assets/icons"; +import { Button } from "../button/button"; +import { cn } from "@humansignal/shad/utils"; export type ToastViewportProps = ToastPrimitive.ToastViewportProps & any; export interface ToastProps extends Omit { @@ -84,10 +86,16 @@ export interface ToastActionProps extends ToastPrimitive.ToastActionProps { onClose?: () => void; } export const ToastAction: FC = ({ children, onClose, altText, ...props }) => ( - - + ); export type ToastShowArgs = { diff --git a/web/libs/ui/src/shadcn.ts b/web/libs/ui/src/shadcn.ts index 9ffadbd12ad3..deee408ad46a 100644 --- a/web/libs/ui/src/shadcn.ts +++ b/web/libs/ui/src/shadcn.ts @@ -1,3 +1,4 @@ /// Raw shadcn components for re-export export { Badge, type BadgeProps, badgeVariants } from "./shad/components/ui/badge"; export { Skeleton } from "./shad/components/ui/skeleton"; +export { cn } from "./utils/utils"; diff --git a/web/libs/ui/src/tailwind.config.js b/web/libs/ui/src/tailwind.config.js index c113a0ed6c13..efd2e7196e81 100644 --- a/web/libs/ui/src/tailwind.config.js +++ b/web/libs/ui/src/tailwind.config.js @@ -2,15 +2,7 @@ import tokens from "./tokens/tokens"; /** @type {import('tailwindcss').Config} */ module.exports = { - content: [ - "./apps/**/*.{js,jsx,ts,tsx}", - "./libs/app-common/src/**/*.{js,jsx,ts,tsx}", - "./libs/core/src/**/*.{js,jsx,ts,tsx}", - "./libs/editor/src/**/*.{js,jsx,ts,tsx}", - "./libs/datamanager/src/**/*.{js,jsx,ts,tsx}", - "./libs/ui/src/**/*.{js,jsx,ts,tsx}", - "./libs/storybook/**/*.{js,jsx,ts,tsx}", - ], + content: ["./apps/**/*.{js,jsx,ts,tsx}", "./libs/**/*.{js,jsx,ts,tsx}"], theme: { extend: { colors: { diff --git a/web/libs/ui/src/tailwind.css b/web/libs/ui/src/tailwind.css index 6ed4ae50541e..a2da45efbe71 100644 --- a/web/libs/ui/src/tailwind.css +++ b/web/libs/ui/src/tailwind.css @@ -87,6 +87,10 @@ a:hover { @layer utilities { .text-shadow-button { - text-shadow: 0 1px 1px rgb(var(--black-raw) / 15%); + text-shadow: 0 1px 1px rgb(var(--color-neutral-shadow-raw) / 15%); + } + + .pointer-events-all { + pointer-events: all; } } diff --git a/web/package.json b/web/package.json index 63234b9b3ffc..c9ac740e60df 100644 --- a/web/package.json +++ b/web/package.json @@ -220,6 +220,7 @@ "ts-node": "10.9.1", "tslib": "^2.3.0", "typescript": "4.8.3", + "typescript-plugin-css-modules": "^5.1.0", "url-loader": "^4.1.1", "webpack": "^5.94.0", "webpack-cli": "^5.0.1", diff --git a/web/tsconfig.base.json b/web/tsconfig.base.json index de7c29ad8b15..97771382eb3f 100644 --- a/web/tsconfig.base.json +++ b/web/tsconfig.base.json @@ -31,7 +31,14 @@ "@humansignal/ui": ["libs/ui/src/index.ts"], "@humansignal/ui/*": ["libs/ui/src/*"] }, - "types": ["vite-plugin-svgr/client"] + "types": ["vite-plugin-svgr/client"], + "plugins": [{ + "name": "typescript-plugin-css-modules", + "options": { + "customMatcher": "\\.module\\.scss$", + "classnameTransform": "asIs" + } + }] }, "exclude": ["node_modules", "tmp"] } diff --git a/web/yarn.lock b/web/yarn.lock index 7c7c81242238..1d6bf9491269 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.1.tgz#2447a230bfe072c1659e6815129c03cf170710e3" integrity sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ== +"@adobe/css-tools@~4.3.1": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" + integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== + "@alloc/quick-lru@^5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" @@ -6615,6 +6620,20 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== +"@types/postcss-modules-local-by-default@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.2.tgz#8fee7513dd1558d74713d817c183a33a6dc583f9" + integrity sha512-CtYCcD+L+trB3reJPny+bKWKMzPfxEyQpKIwit7kErnOexf5/faaGpkFy4I5AwbV4hp1sk7/aTg0tt0B67VkLQ== + dependencies: + postcss "^8.0.0" + +"@types/postcss-modules-scope@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/postcss-modules-scope/-/postcss-modules-scope-3.0.4.tgz#f82d15ec9023c924b531a49e8087b32646233f41" + integrity sha512-//ygSisVq9kVI0sqx3UPLzWIMCmtSVrzdljtuaAEJtGoGnpjBikZ2sXO5MpH9SnWX9HRfXxHifDAXcQjupWnIQ== + dependencies: + postcss "^8.0.0" + "@types/prettier@^2.1.5": version "2.7.3" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" @@ -10806,6 +10825,11 @@ dotenv@^16.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz#3cb611ce5a63002dbabf7c281bc331f69d28f03f" integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ== +dotenv@^16.4.2: + version "16.5.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692" + integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== + dotenv@^16.4.5, dotenv@~16.4.5: version "16.4.7" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" @@ -14588,6 +14612,23 @@ less@4.1.3: needle "^3.1.0" source-map "~0.6.0" +less@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/less/-/less-4.3.0.tgz#ef0cfc260a9ca8079ed8d0e3512bda8a12c82f2a" + integrity sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^3.1.0" + source-map "~0.6.0" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -14668,7 +14709,7 @@ lightningcss@^1.29.1: lightningcss-win32-arm64-msvc "1.29.1" lightningcss-win32-x64-msvc "1.29.1" -lilconfig@^2.0.3, lilconfig@^2.1.0: +lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== @@ -16527,6 +16568,14 @@ postcss-js@^4.0.1: dependencies: camelcase-css "^2.0.1" +postcss-load-config@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== + dependencies: + lilconfig "^2.0.5" + yaml "^1.10.2" + postcss-load-config@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" @@ -16677,6 +16726,15 @@ postcss-modules-local-by-default@^4.0.0, postcss-modules-local-by-default@^4.0.3 postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" +postcss-modules-local-by-default@^4.0.4: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz#d150f43837831dae25e4085596e84f6f5d6ec368" + integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.1.0" + postcss-modules-local-by-default@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f" @@ -16693,6 +16751,13 @@ postcss-modules-scope@^3.0.0: dependencies: postcss-selector-parser "^6.0.4" +postcss-modules-scope@^3.1.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c" + integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA== + dependencies: + postcss-selector-parser "^7.0.0" + postcss-modules-scope@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5" @@ -16916,6 +16981,14 @@ postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262" + integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-svgo@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" @@ -16951,6 +17024,15 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== +postcss@^8.0.0, postcss@^8.4.35: + version "8.5.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" + integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5, postcss@^8.4.14, postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.33, postcss@^8.4.41: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" @@ -18234,6 +18316,11 @@ requizzle@^0.2.3: dependencies: lodash "^4.17.21" +reserved-words@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1" + integrity sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw== + resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" @@ -18461,7 +18548,18 @@ sass@^1.55.0: optionalDependencies: "@parcel/watcher" "^2.4.1" -sax@>=0.6.0, sax@^1.2.4: +sass@^1.70.0: + version "1.87.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.87.0.tgz#8cceb36fa63fb48a8d5d7f2f4c13b49c524b723e" + integrity sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw== + dependencies: + chokidar "^4.0.0" + immutable "^5.0.2" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" + +sax@>=0.6.0, sax@^1.2.4, sax@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== @@ -18927,7 +19025,7 @@ sort-keys@^1.0.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-js@^1.2.1: +source-map-js@^1.0.2, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -19458,6 +19556,17 @@ stylus@^0.59.0: sax "~1.2.4" source-map "^0.7.3" +stylus@^0.62.0: + version "0.62.0" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.62.0.tgz#648a020e2bf90ed87587ab9c2f012757e977bb5d" + integrity sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg== + dependencies: + "@adobe/css-tools" "~4.3.1" + debug "^4.3.2" + glob "^7.1.6" + sax "~1.3.0" + source-map "^0.7.3" + sucrase@^3.35.0: version "3.35.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" @@ -20145,6 +20254,28 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript-plugin-css-modules@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/typescript-plugin-css-modules/-/typescript-plugin-css-modules-5.1.0.tgz#faa0ceffe8a8ffcbbc2f77ed637a64464195044a" + integrity sha512-6h+sLBa4l+XYSTn/31vZHd/1c3SvAbLpobY6FxDiUOHJQG1eD9Gh3eCs12+Eqc+TCOAdxcO+zAPvUq0jBfdciw== + dependencies: + "@types/postcss-modules-local-by-default" "^4.0.2" + "@types/postcss-modules-scope" "^3.0.4" + dotenv "^16.4.2" + icss-utils "^5.1.0" + less "^4.2.0" + lodash.camelcase "^4.3.0" + postcss "^8.4.35" + postcss-load-config "^3.1.4" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.4" + postcss-modules-scope "^3.1.1" + reserved-words "^0.1.2" + sass "^1.70.0" + source-map-js "^1.0.2" + stylus "^0.62.0" + tsconfig-paths "^4.2.0" + typescript@4.8.3: version "4.8.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.3.tgz#d59344522c4bc464a65a730ac695007fdb66dd88"