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')} + - - {t('auto_compile')} + setAutoCompile(false)}> + + {t('off')} + - setAutoCompile(true)}> - - {t('on')} - + {t('compile_mode')} - setAutoCompile(false)}> - - {t('off')} - + setDraft(false)}> + + {t('normal')} + - {t('compile_mode')} + setDraft(true)}> + + {t('fast')} [draft] + - setDraft(false)}> - - {t('normal')} - + Syntax Checks - setDraft(true)}> - - {t('fast')} [draft] - + setStopOnValidationError(true)}> + + {t('stop_on_validation_error')} + - Syntax Checks + setStopOnValidationError(false)}> + + {t('ignore_validation_errors')} + - setStopOnValidationError(true)}> - - {t('stop_on_validation_error')} - + {t('compile_error_handling')} - setStopOnValidationError(false)}> - - {t('ignore_validation_errors')} - + + + {t('stop_on_first_error')} + - {t('compile_error_handling')} + + + {t('try_to_compile_despite_errors')} + - - - {t('stop_on_first_error')} - + - - - {t('try_to_compile_despite_errors')} - + stopCompile()} + disabled={!compiling} + aria-disabled={!compiling} + > + {t('stop_compile')} + - - - stopCompile()} - disabled={!compiling} - aria-disabled={!compiling} - > - {t('stop_compile')} - - - recompileFromScratch()} - disabled={compiling} - aria-disabled={compiling} - > - {t('recompile_from_scratch')} - - - + 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'