diff --git a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.js b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.js
new file mode 100644
index 0000000000..a9997206ef
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button-wrapper.js
@@ -0,0 +1,23 @@
+import { memo } from 'react'
+import PropTypes from 'prop-types'
+import { useLayoutContext } from '../../../shared/context/layout-context'
+import DetachCompileButton from './detach-compile-button'
+
+function DetachCompileButtonWrapper() {
+ const { detachRole, detachIsLinked } = useLayoutContext(
+ layoutContextPropTypes
+ )
+
+ if (detachRole !== 'detacher' || !detachIsLinked) {
+ return null
+ }
+
+ return
+}
+
+const layoutContextPropTypes = {
+ detachRole: PropTypes.string,
+ detachIsLinked: PropTypes.bool,
+}
+
+export default memo(DetachCompileButtonWrapper)
diff --git a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.js b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.js
deleted file mode 100644
index e0e52913cf..0000000000
--- a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { memo } from 'react'
-import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import { useLayoutContext } from '../../../shared/context/layout-context'
-import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
-import PdfCompileButtonInner from './pdf-compile-button-inner'
-
-export function DetachCompileButton() {
- const { compiling, hasChanges, startCompile } = useCompileContext()
-
- return (
-
- )
-}
-
-export function DetachCompileButtonWrapper() {
- const { detachRole, detachIsLinked } = useLayoutContext(
- layoutContextPropTypes
- )
-
- if (detachRole !== 'detacher' || !detachIsLinked) {
- return null
- }
-
- return
-}
-
-const layoutContextPropTypes = {
- detachRole: PropTypes.string,
- detachIsLinked: PropTypes.bool,
-}
-
-export default memo(DetachCompileButtonWrapper)
diff --git a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx
new file mode 100644
index 0000000000..39c89929b5
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx
@@ -0,0 +1,50 @@
+import { useTranslation } from 'react-i18next'
+import { memo } from 'react'
+import { Button } from 'react-bootstrap'
+import classNames from 'classnames'
+import Icon from '../../../shared/components/icon'
+import { useDetachCompileContext } from '../../../shared/context/detach-compile-context'
+import Tooltip from '../../../shared/components/tooltip'
+
+const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
+
+function DetachCompileButton() {
+ const { t } = useTranslation()
+ const { compiling, startCompile, hasChanges } = useDetachCompileContext()
+
+ const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
+ const tooltipElement = (
+ <>
+ {t('recompile_pdf')}{' '}
+ ({modifierKey} + Enter)
+ >
+ )
+
+ return (
+
+
+
+
+
+ )
+}
+
+export default memo(DetachCompileButton)
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button-inner.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button-inner.tsx
deleted file mode 100644
index cf4b47762e..0000000000
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button-inner.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Button } from 'react-bootstrap'
-import Tooltip from '../../../shared/components/tooltip'
-import Icon from '../../../shared/components/icon'
-import { useTranslation } from 'react-i18next'
-import { memo } from 'react'
-
-const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
-
-type PdfCompileButtonInnerProps = {
- startCompile: () => void
- compiling: boolean
-}
-
-function PdfCompileButtonInner({
- startCompile,
- compiling,
-}: PdfCompileButtonInnerProps) {
- const { t } = useTranslation()
-
- const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
-
- return (
-
- {t('recompile_pdf')}{' '}
- ({modifierKey} + Enter)
- >
- }
- tooltipProps={{ className: 'keyboard-tooltip' }}
- overlayProps={{ delayShow: 500 }}
- >
-
-
- )
-}
-
-export default memo(PdfCompileButtonInner)
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js b/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js
index 6b32fef078..f447fcfbb1 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js
@@ -1,12 +1,12 @@
-import { Dropdown, MenuItem } from 'react-bootstrap'
-import Icon from '../../../shared/components/icon'
-import ControlledDropdown from '../../../shared/components/controlled-dropdown'
import { useTranslation } from 'react-i18next'
import { memo } from 'react'
-import classnames from 'classnames'
+import classNames from 'classnames'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
-import PdfCompileButtonInner from './pdf-compile-button-inner'
+import SplitMenu from '../../../shared/components/split-menu'
+import Icon from '../../../shared/components/icon'
+
+const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
function PdfCompileButton() {
const {
@@ -30,100 +30,115 @@ function PdfCompileButton() {
const { t } = useTranslation()
+ const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
+ const tooltipElement = (
+ <>
+ {t('recompile_pdf')}{' '}
+ ({modifierKey} + Enter)
+ >
+ )
+
+ const dropdownToggleClassName = classNames({
+ 'detach-compile-button-animate': animateCompileDropdownArrow,
+ 'btn-striped-animated': hasChanges,
+ })
+
+ const buttonClassName = classNames({
+ 'btn-striped-animated': hasChanges,
+ })
+
return (
- startCompile(),
+ text: compileButtonLabel,
+ className: buttonClassName,
+ }}
+ dropdownToggle={{
+ 'aria-label': t('toggle_compile_options_menu'),
+ handleAnimationEnd: () => setAnimateCompileDropdownArrow(false),
+ className: dropdownToggleClassName,
+ }}
+ dropdown={{
+ id: 'pdf-recompile-dropdown',
+ }}
>
-
+ {t('auto_compile')}
- {
- setAnimateCompileDropdownArrow(false)
- }}
- />
+ setAutoCompile(true)}>
+
+ {t('on')}
+
-
-
+ setAutoCompile(false)}>
+
+ {t('off')}
+
-
+ {t('compile_mode')}
-
+ setDraft(false)}>
+
+ {t('normal')}
+
-
+ setDraft(true)}>
+
+ {t('fast')} [draft]
+
-
+ Syntax Checks
-
+ setStopOnValidationError(true)}>
+
+ {t('stop_on_validation_error')}
+
-
+ setStopOnValidationError(false)}>
+
+ {t('ignore_validation_errors')}
+
-
+ {t('compile_error_handling')}
-
+
+
+ {t('stop_on_first_error')}
+
-
+
+
+ {t('try_to_compile_despite_errors')}
+
-
+
-
+ stopCompile()}
+ disabled={!compiling}
+ aria-disabled={!compiling}
+ >
+ {t('stop_compile')}
+
-
-
-
-
-
-
-
+ recompileFromScratch()}
+ disabled={compiling}
+ aria-disabled={compiling}
+ >
+ {t('recompile_from_scratch')}
+
+
)
}
diff --git a/services/web/frontend/js/ide/editor/controllers/CompileButton.js b/services/web/frontend/js/ide/editor/controllers/CompileButton.js
index 25390f3fd6..abaa460ad5 100644
--- a/services/web/frontend/js/ide/editor/controllers/CompileButton.js
+++ b/services/web/frontend/js/ide/editor/controllers/CompileButton.js
@@ -1,7 +1,7 @@
import App from '../../../base'
import { react2angular } from 'react2angular'
import { rootContext } from '../../../shared/context/root-context'
-import DetachCompileButtonWrapper from '../../../features/pdf-preview/components/detach-compile-button'
+import DetachCompileButtonWrapper from '../../../features/pdf-preview/components/detach-compile-button-wrapper'
App.component(
'editorCompileButton',
diff --git a/services/web/frontend/js/shared/components/icon.tsx b/services/web/frontend/js/shared/components/icon.tsx
index aa8e5e1b7d..176590b834 100644
--- a/services/web/frontend/js/shared/components/icon.tsx
+++ b/services/web/frontend/js/shared/components/icon.tsx
@@ -8,7 +8,7 @@ type IconOwnProps = {
accessibilityLabel?: string
}
-type IconProps = IconOwnProps &
+export type IconProps = IconOwnProps &
Omit, keyof IconOwnProps>
function Icon({
diff --git a/services/web/frontend/js/shared/components/split-menu.tsx b/services/web/frontend/js/shared/components/split-menu.tsx
new file mode 100644
index 0000000000..5dd9e690b6
--- /dev/null
+++ b/services/web/frontend/js/shared/components/split-menu.tsx
@@ -0,0 +1,146 @@
+import { Button, Dropdown, MenuItem } from 'react-bootstrap'
+import type {
+ ButtonProps,
+ MenuItemProps,
+ DropdownButtonProps,
+ DropdownProps,
+} from 'react-bootstrap'
+import type { PropsWithChildren } from 'react'
+import classNames from 'classnames'
+import Tooltip, { type TooltipProps } from './tooltip'
+import Icon, { type IconProps } from './icon'
+import type { BsSize, BsStyle } from '../../../../types/bootstrap'
+
+type SplitMenuBsStyle = Extract
+
+type SplitMenuBsSize = Extract
+
+type SplitMenuButtonProps = {
+ tooltip?: TooltipProps
+ bsStyle?: SplitMenuBsStyle
+ text: string
+ icon?: IconProps
+} & Pick
+
+type SplitMenuDropdownToggleProps = {
+ handleAnimationEnd?: () => void
+} & Pick
+
+type SplitMenuDropdownProps = Pick
+
+type SplitMenuProps = PropsWithChildren<{
+ bsStyle: SplitMenuBsStyle
+ bsSize?: SplitMenuBsSize
+ button: Omit
+ dropdown: SplitMenuDropdownProps
+ dropdownToggle?: SplitMenuDropdownToggleProps
+ disabled?: boolean
+}>
+
+function SplitMenu({
+ bsStyle,
+ bsSize = 'md',
+ button,
+ dropdown,
+ dropdownToggle,
+ disabled = false,
+ children,
+}: SplitMenuProps) {
+ const { tooltip, icon, ...buttonProps } = button
+
+ const splitMenuClassName = classNames('split-menu', {
+ [`btn-${bsSize}`]: true,
+ })
+
+ const dropdownToggleClassName = classNames(
+ 'split-menu-dropdown-toggle',
+ dropdownToggle?.className
+ )
+
+ return (
+
+
+ {icon ? (
+
+ ) : null}
+ {buttonProps.text}
+
+
+
+
+ {children}
+
+
+ )
+}
+
+function SplitMenuButton({
+ onClick,
+ disabled,
+ tooltip,
+ bsStyle,
+ children,
+ className,
+ ...props
+}: PropsWithChildren>) {
+ const buttonClassName = classNames('split-menu-button', className)
+
+ if (tooltip) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+ )
+}
+
+function SplitMenuItem(props: MenuItemProps) {
+ return
+}
+
+SplitMenu.Item = SplitMenuItem
+
+export default SplitMenu
diff --git a/services/web/frontend/js/shared/components/tooltip.tsx b/services/web/frontend/js/shared/components/tooltip.tsx
index 2b10620ab3..ad61955927 100644
--- a/services/web/frontend/js/shared/components/tooltip.tsx
+++ b/services/web/frontend/js/shared/components/tooltip.tsx
@@ -9,12 +9,20 @@ type OverlayProps = Omit & {
shouldUpdatePosition?: boolean // Not officially documented https://stackoverflow.com/a/43138470
}
-const Tooltip: FC<{
+export type TooltipProps = {
description: ReactNode
id: string
overlayProps?: OverlayProps
tooltipProps?: BSTooltip.TooltipProps
-}> = ({ id, description, children, tooltipProps, overlayProps }) => {
+}
+
+const Tooltip: FC = ({
+ id,
+ description,
+ children,
+ tooltipProps,
+ overlayProps,
+}) => {
return (
{
+ return (
+
+ tes
+
+ )
+}
+
+export const PrimaryWithTooltip = () => {
+ return (
+
+ tes
+
+ )
+}
+
+export const Disabled = () => {
+ return (
+
+
Primary
+
+ tes
+
+
+ Secondary
+
+ tes
+
+
+ Danger
+
+ tes
+
+
+ )
+}
+
+export const DifferentSizeAndStyle = () => {
+ return (
+
+
Default (medium)
+
+
+ tes
+
+
+ tes
+
+
+ tes
+
+
+
+
Small
+
+
+ tes
+
+
+ tes
+
+
+ tes
+
+
+
+
Extra Small
+
+
+ tes
+
+
+ tes
+
+
+ tes
+
+
+
+ )
+}
+
+export default {
+ title: 'Shared / Components / Split Menu',
+ component: SplitMenu,
+ args: {
+ source: 'storybook',
+ },
+}
diff --git a/services/web/frontend/stylesheets/_style_includes.less b/services/web/frontend/stylesheets/_style_includes.less
index c483e5f77a..257e28233d 100644
--- a/services/web/frontend/stylesheets/_style_includes.less
+++ b/services/web/frontend/stylesheets/_style_includes.less
@@ -60,6 +60,7 @@
@import 'components/expand-collapse.less';
@import 'components/beta-badges.less';
@import 'components/divider.less';
+@import 'components/split-menu.less';
// Components w/ JavaScript
@import 'components/modals.less';
diff --git a/services/web/frontend/stylesheets/app/editor.less b/services/web/frontend/stylesheets/app/editor.less
index 4621344db1..688391a666 100644
--- a/services/web/frontend/stylesheets/app/editor.less
+++ b/services/web/frontend/stylesheets/app/editor.less
@@ -17,6 +17,7 @@
@import './editor/outline.less';
@import './editor/logs.less';
@import './editor/dictionary.less';
+@import './editor/compile-button.less';
@ui-layout-toggler-def-height: 50px;
@ui-resizer-size: 7px;
@@ -154,21 +155,6 @@
overflow: hidden;
position: relative;
z-index: 10; // Prevent track changes showing over toolbar
-
- .btn-recompile-group {
- margin-right: -5px;
- border-radius: @btn-border-radius-base 0 0 @btn-border-radius-base;
- margin-left: 6px;
- &.btn-recompile-group-has-changes {
- // prettier-ignore
- #gradient > .striped(@color: rgba(255, 255, 255, 0.2), @angle: -45deg);
- background-size: @stripe-width @stripe-width;
- .animation(pdf-toolbar-stripes 2s linear infinite);
- }
- .btn-recompile {
- border-radius: @btn-border-radius-base 0 0 @btn-border-radius-base;
- }
- }
}
.loading-screen {
diff --git a/services/web/frontend/stylesheets/app/editor/compile-button.less b/services/web/frontend/stylesheets/app/editor/compile-button.less
new file mode 100644
index 0000000000..2e60739752
--- /dev/null
+++ b/services/web/frontend/stylesheets/app/editor/compile-button.less
@@ -0,0 +1,102 @@
+@stripe-width: 20px;
+@keyframes pdf-toolbar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: @stripe-width 0;
+ }
+}
+
+.detach-compile-button-container {
+ border-radius: @btn-border-radius-base 0 0 @btn-border-radius-base;
+ margin-left: 6px;
+}
+
+.detach-compile-button-container when (@is-new-css = false) {
+ margin-right: -5px;
+}
+
+// because 2px border on :active state
+.detach-compile-button-container when (@is-new-css = true) {
+ margin-right: -3px;
+}
+
+.btn-striped-animated {
+ // prettier-ignore
+ #gradient > .striped(@color: rgba(255, 255, 255, 0.2), @angle: -45deg);
+ background-size: @stripe-width @stripe-width;
+ .animation(pdf-toolbar-stripes 2s linear infinite);
+}
+
+.detach-compile-button when (@is-new-css = false) {
+ &[disabled],
+ &[disabled].active,
+ &[disabled]:hover,
+ &[disabled]:focus {
+ background-color: mix(@btn-primary-bg, @toolbar-alt-bg-color, 65%);
+ .opacity(1);
+ }
+}
+
+.detach-compile-button {
+ height: 28px;
+ padding-top: 0;
+ padding-bottom: 0;
+
+ &.detach-compile-button-disabled {
+ &,
+ &:hover {
+ color: @white;
+ background-color: @ol-green;
+ }
+ }
+}
+
+.detach-compile-button when (@is-new-css = true) {
+ border: none;
+}
+
+.detach-compile-button-label {
+ margin-left: @line-height-computed / 4;
+}
+
+@keyframes compile-button-flash {
+ from,
+ to {
+ background: rgba(0, 0, 0, 0);
+ }
+
+ 25%,
+ 75% {
+ background: rgba(0, 0, 0, 0.2);
+ }
+}
+
+@keyframes compile-button-bounce {
+ from,
+ 50%,
+ to {
+ transform: translateY(0);
+ }
+
+ 25%,
+ 75% {
+ transform: translateY(2px);
+ }
+}
+
+.detach-compile-button-animate {
+ animation-duration: 1.2s;
+ animation-fill-mode: both;
+ animation-timing-function: ease-in-out;
+ animation-name: compile-button-flash;
+}
+
+.detach-compile-button-animate .caret {
+ animation-duration: 0.6s;
+ animation-delay: 0.4s;
+ animation-fill-mode: both;
+ animation-timing-function: cubic-bezier(0.76, 0, 0.24, 1);
+ animation-name: compile-button-bounce;
+}
diff --git a/services/web/frontend/stylesheets/app/editor/pdf.less b/services/web/frontend/stylesheets/app/editor/pdf.less
index 25b0a31616..7aa9f84c84 100644
--- a/services/web/frontend/stylesheets/app/editor/pdf.less
+++ b/services/web/frontend/stylesheets/app/editor/pdf.less
@@ -1,24 +1,8 @@
-@stripe-width: 20px;
-@keyframes pdf-toolbar-stripes {
- from {
- background-position: 0 0;
- }
- to {
- background-position: @stripe-width 0;
- }
-}
-
.pdf .toolbar.toolbar-pdf {
.toolbar-small-mixin;
.toolbar-alt-mixin;
padding-right: 5px;
margin-left: 0;
- &.changes-to-autocompile {
- // prettier-ignore
- #gradient > .striped(@color: rgba(255, 255, 255, 0.1), @angle: -45deg);
- background-size: @stripe-width @stripe-width;
- .animation(pdf-toolbar-stripes 2s linear infinite);
- }
.auto-compile-status {
color: white;
margin-right: (@line-height-computed / 2);
@@ -44,6 +28,10 @@
flex: 1 1 100%;
}
+.toolbar-pdf-left when (@is-new-css = true) {
+ margin-left: 2px;
+}
+
.toolbar-pdf-right {
flex: 1 0 auto;
}
@@ -67,7 +55,7 @@
}
.toolbar-pdf-hybrid {
- .btn:not(.btn-recompile):not(.btn-orphan):not(.detach-synctex-control):not(.switch-to-editor-btn) {
+ .btn:not(.detach-compile-button):not(.btn-orphan):not(.detach-synctex-control):not(.switch-to-editor-btn):not(.split-menu-dropdown-toggle):not(.split-menu-button) {
display: inline-block;
color: @toolbar-btn-color;
background-color: transparent;
@@ -121,10 +109,6 @@
}
}
}
-
- .btn-recompile {
- padding-top: 2px;
- }
}
.pdf {
@@ -152,89 +136,6 @@
}
}
-.btn-recompile-group {
- align-self: stretch;
- margin-right: 6px;
- border-radius: 0 @btn-border-radius-base @btn-border-radius-base 0;
- background-color: @btn-primary-bg;
- &.btn-recompile-group-has-changes {
- // prettier-ignore
- #gradient > .striped(@color: rgba(255, 255, 255, 0.2), @angle: -45deg);
- background-size: @stripe-width @stripe-width;
- .animation(pdf-toolbar-stripes 2s linear infinite);
- }
-}
-
-.btn-recompile {
- height: 100%;
- // .btn-primary;
- color: #fff;
- background-color: transparent;
- padding-top: 3px;
- padding-bottom: 3px;
- &:first-child {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
-}
-
-.btn-recompile when (@is-new-css = false) {
- &[disabled],
- &[disabled].active,
- &[disabled]:hover,
- &[disabled]:focus {
- background-color: mix(@btn-primary-bg, @toolbar-alt-bg-color, 65%);
- .opacity(1);
- }
-}
-.btn-recompile when (@is-new-css = true) {
- border: none;
-}
-
-.btn-recompile-label {
- margin-left: @line-height-computed / 4;
-}
-
-@keyframes compile-button-flash {
- from,
- to {
- background: rgba(0, 0, 0, 0);
- }
-
- 25%,
- 75% {
- background: rgba(0, 0, 0, 0.2);
- }
-}
-
-@keyframes compile-button-bounce {
- from,
- 50%,
- to {
- transform: translateY(0);
- }
-
- 25%,
- 75% {
- transform: translateY(2px);
- }
-}
-
-.btn-recompile-animate {
- animation-duration: 1.2s;
- animation-fill-mode: both;
- animation-timing-function: ease-in-out;
- animation-name: compile-button-flash;
-}
-
-.btn-recompile-animate .caret {
- animation-duration: 0.6s;
- animation-delay: 0.4s;
- animation-fill-mode: both;
- animation-timing-function: cubic-bezier(0.76, 0, 0.24, 1);
- animation-name: compile-button-bounce;
-}
-
.toolbar-text {
padding-left: @padding-xs;
}
diff --git a/services/web/frontend/stylesheets/app/editor/toolbar.less b/services/web/frontend/stylesheets/app/editor/toolbar.less
index afe5bb2024..c7211e94d6 100644
--- a/services/web/frontend/stylesheets/app/editor/toolbar.less
+++ b/services/web/frontend/stylesheets/app/editor/toolbar.less
@@ -150,14 +150,6 @@
z-index: 1;
}
- &.toolbar-small {
- .toolbar-small-mixin;
- }
-
- &.toolbar-tall {
- .toolbar-small-mixin;
- }
-
&.toolbar-alt {
.toolbar-alt-mixin;
}
diff --git a/services/web/frontend/stylesheets/components/button-groups.less b/services/web/frontend/stylesheets/components/button-groups.less
index ed95d18a0c..0fa5b3b457 100755
--- a/services/web/frontend/stylesheets/components/button-groups.less
+++ b/services/web/frontend/stylesheets/components/button-groups.less
@@ -242,16 +242,3 @@
display: flex;
flex-wrap: nowrap;
}
-
-// allow hiding toolbar content at various breakpoints
-.toolbar-large .toolbar-hide-large {
- display: none !important;
-}
-
-.toolbar-medium .toolbar-hide-medium {
- display: none !important;
-}
-
-.toolbar-small .toolbar-hide-small {
- display: none !important;
-}
diff --git a/services/web/frontend/stylesheets/components/split-menu.less b/services/web/frontend/stylesheets/components/split-menu.less
new file mode 100644
index 0000000000..6835421540
--- /dev/null
+++ b/services/web/frontend/stylesheets/components/split-menu.less
@@ -0,0 +1,91 @@
+.split-menu {
+ display: flex;
+
+ &.btn-md,
+ &.btn-sm,
+ &.btn-xs {
+ padding: 0;
+ }
+
+ &.btn-md {
+ & > .split-menu-button,
+ & > .split-menu-dropdown-toggle {
+ height: 36px;
+ }
+ }
+
+ &.btn-sm {
+ & > .split-menu-button,
+ & > .split-menu-dropdown-toggle {
+ height: 32px;
+ }
+ }
+
+ &.btn-xs {
+ & > .split-menu-button,
+ & > .split-menu-dropdown-toggle {
+ height: 28px;
+ }
+ }
+
+ &.btn-primary {
+ background-color: @ol-green;
+ }
+
+ .split-menu-icon {
+ margin-right: @line-height-computed / 4;
+ }
+
+ .split-menu-button {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ height: 100%;
+ color: @white;
+ padding-top: 0;
+ padding-bottom: 0;
+
+ &.btn-primary {
+ border-right: 1px solid @green-10;
+ }
+
+ // on new css, btn-secondary already has a border
+ &.btn-secondary when (@is-new-css = false) {
+ border-right: 1px solid @neutral-10;
+ }
+
+ &.btn-danger {
+ border-right: 1px solid @red-10;
+ }
+
+ &[disabled] when (@is-new-css = false) {
+ opacity: 1;
+ }
+ }
+
+ .split-menu-button when (@is-new-css = true) {
+ // workaround for for the blue 2x border on the new css
+ // if z-index rule is not added, the border will overlap under the `split-menu-dropdown-toggle` since margin between both component is only 1px
+ z-index: 1;
+ }
+
+ .split-menu-dropdown {
+ float: none;
+ display: flex;
+ flex-wrap: nowrap;
+ align-self: stretch;
+ margin-right: 6px;
+
+ .split-menu-dropdown-toggle {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+ padding: 0 8px;
+
+ // on new css, btn-secondary has a border
+ // since the border between both buttons already been defined in the `split-menu-button`
+ // we will remove the rule from the dropdown toggle
+ &.btn-secondary when (@is-new-css = true) {
+ border-left: none;
+ }
+ }
+ }
+}
diff --git a/services/web/frontend/stylesheets/core/mixins.less b/services/web/frontend/stylesheets/core/mixins.less
index cc30a2931e..4664eea056 100755
--- a/services/web/frontend/stylesheets/core/mixins.less
+++ b/services/web/frontend/stylesheets/core/mixins.less
@@ -595,10 +595,13 @@
}
&[data-ol-loading='true'] {
- // use the default state colors when in a loading state
- color: @btn-bordered-color!important;
- background-color: @btn-bordered-background-color!important;
- border-color: @btn-bordered-border-color!important;
+ &,
+ &:hover {
+ // use the default state colors when in a loading state
+ color: @btn-bordered-color!important;
+ background-color: @btn-bordered-background-color!important;
+ border-color: @btn-bordered-border-color!important;
+ }
}
}
@@ -651,10 +654,13 @@
}
&[data-ol-loading='true'] {
- // use the default state colors when in a loading state
- color: @btn-borderless-color!important;
- background-color: @btn-borderless-background-color!important;
- border-color: @btn-borderless-border-color!important;
+ &,
+ &:hover {
+ // use the default state colors when in a loading state
+ color: @btn-borderless-color!important;
+ background-color: @btn-borderless-background-color!important;
+ border-color: @btn-borderless-border-color;
+ }
}
}
diff --git a/services/web/frontend/stylesheets/core/variables.less b/services/web/frontend/stylesheets/core/variables.less
index 3cf68360e2..a4614a79b9 100644
--- a/services/web/frontend/stylesheets/core/variables.less
+++ b/services/web/frontend/stylesheets/core/variables.less
@@ -11,6 +11,8 @@
@white: #ffffff;
@neutral-20: #e7e9ee;
@neutral-90: #1b222c;
+@neutral-40: #afb5c0;
+@neutral-10: #f4f5f6;
// Styleguide colors
@ol-blue-gray-0: #f4f5f8;
diff --git a/services/web/frontend/stylesheets/main-style.less b/services/web/frontend/stylesheets/main-style.less
index e46fab18a6..966aea71d2 100644
--- a/services/web/frontend/stylesheets/main-style.less
+++ b/services/web/frontend/stylesheets/main-style.less
@@ -71,6 +71,7 @@
@import 'components/divider.less';
@import 'components/input-switch.less';
@import 'components/container.less';
+@import 'components/split-menu.less';
// Components w/ JavaScript
@import 'components/modals.less';
diff --git a/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx b/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
index be8caadb34..5ecd3dc374 100644
--- a/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
+++ b/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
@@ -1,9 +1,9 @@
import { EditorProviders } from '../../helpers/editor-providers'
-import DetachCompileButton from '../../../../frontend/js/features/pdf-preview/components/detach-compile-button'
+import DetachCompileButtonWrapper from '../../../../frontend/js/features/pdf-preview/components/detach-compile-button-wrapper'
import { mockScope } from './scope'
import { testDetachChannel } from '../../helpers/detach-channel'
-describe('', function () {
+describe('', function () {
beforeEach(function () {
cy.interceptCompile()
cy.interceptEvents()
@@ -22,7 +22,7 @@ describe('', function () {
cy.mount(
-
+
)
@@ -38,7 +38,7 @@ describe('', function () {
cy.mount(
-
+
)
@@ -61,7 +61,7 @@ describe('', function () {
cy.mount(
-
+
)
diff --git a/services/web/test/frontend/components/shared/split-menu.spec.tsx b/services/web/test/frontend/components/shared/split-menu.spec.tsx
new file mode 100644
index 0000000000..44a72be1af
--- /dev/null
+++ b/services/web/test/frontend/components/shared/split-menu.spec.tsx
@@ -0,0 +1,67 @@
+import SplitMenu from '../../../../frontend/js/shared/components/split-menu'
+
+describe('SplitMenu', function () {
+ it('renders primary variant', function () {
+ cy.mount(
+
+ Item 1
+ Item 2
+ Item 3
+
+ )
+
+ cy.get('button.split-menu-button').contains('Button Text')
+ cy.get('button.split-menu-button').should('have.class', 'btn-primary')
+ cy.get('button.split-menu-dropdown-toggle').should(
+ 'have.class',
+ 'btn-primary'
+ )
+ cy.get('li').should('have.length', 3)
+ cy.get('li').contains('Item 1')
+ cy.get('li').contains('Item 2')
+ cy.get('li').contains('Item 3')
+
+ cy.get('ul.dropdown-menu').should('not.be.visible')
+ cy.get('button.split-menu-dropdown-toggle').click()
+ cy.get('ul.dropdown-menu').should('be.visible')
+ })
+
+ it('with custom classNames', function () {
+ cy.mount(
+
+ Item 1
+
+ )
+
+ cy.get('button.split-menu-button').should(
+ 'have.class',
+ 'split-menu-class-1'
+ )
+ cy.get('div.split-menu-dropdown').should('have.class', 'split-menu-class-2')
+ cy.get('button.split-menu-dropdown-toggle').should(
+ 'have.class',
+ 'split-menu-class-3'
+ )
+ })
+})
diff --git a/services/web/types/bootstrap.ts b/services/web/types/bootstrap.ts
new file mode 100644
index 0000000000..38f1c8085e
--- /dev/null
+++ b/services/web/types/bootstrap.ts
@@ -0,0 +1,11 @@
+export type BsStyle =
+ | 'primary'
+ | 'secondary'
+ | 'danger'
+ | 'info'
+ | 'default'
+ | 'link'
+ | 'warning'
+ | 'success'
+
+export type BsSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'