Skip to content

Commit

Permalink
Merge pull request #3260 from serlo/bigger-blank-button-alternative
Browse files Browse the repository at this point in the history
feat(plugin-blanks): prominent blank button alternative
  • Loading branch information
elbotho authored Jan 11, 2024
2 parents 15ab12b + d1c0ae8 commit 80de7e3
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 51 deletions.
4 changes: 3 additions & 1 deletion apps/web/src/data/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ export const loggedInData = {
mathFormula: 'Math formula (%ctrlOrCmd% + M)',
code: 'Code (%ctrlOrCmd% + ⇧ + `)',
blank: 'Blank',
createBlank: 'Create Blank',
removeBlank: 'Remove Blank',
bold: 'Bold (%ctrlOrCmd% + B)',
italic: 'Italic (%ctrlOrCmd% + I)',
noItemsFound: 'No items found',
Expand Down Expand Up @@ -903,7 +905,7 @@ export const loggedInData = {
},
textAreaExercise: {
title: 'Text Box Exercise',
description: 'A big text box for long answers. No feedback.'
description: 'A big text box for long answers. No feedback.',
},
scMcExercise: {
title: 'SC/MC Exercise',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
faListOl,
faListUl,
faSquareRootVariable,
faXmark,
} from '@fortawesome/free-solid-svg-icons'
import { onKeyDown as slateListsOnKeyDown } from '@prezly/slate-lists'
import { FaIcon } from '@serlo/frontend/src/components/fa-icon'
Expand Down Expand Up @@ -227,12 +226,27 @@ function createToolbarControls(
ctrlKey: string
): ControlButton[] {
const allFormattingOptions = [
// Blank (For Fill in the Blank Exercises)
{
name: TextEditorFormattingOption.textBlank,
title: textStrings.createBlank,
activeTitle: textStrings.removeBlank,
isActive: isBlankActive,
onClick: toggleBlank,
group: 'blank',
renderIcon: () => (
<span className="relative -top-0.25 mx-1 mb-1 rounded-lg border-2 border-current px-1.5 py-0.25 text-[13px] font-bold">
{textStrings.blank}
</span>
),
},
// Bold
{
name: TextEditorFormattingOption.richTextBold,
title: textStrings.bold,
isActive: isBoldActive,
onClick: toggleBoldMark,
group: 'default',
renderIcon: () => <EditorSvgIcon pathData={editorBold} />,
},
// Italic
Expand All @@ -241,6 +255,7 @@ function createToolbarControls(
title: textStrings.italic,
isActive: isItalicActive,
onClick: toggleItalicMark,
group: 'default',
renderIcon: () => <EditorSvgIcon pathData={editorItalic} />,
},
// Link
Expand All @@ -249,6 +264,7 @@ function createToolbarControls(
title: textStrings.link,
isActive: isLinkActive,
onClick: toggleLink,
group: 'default',
renderIcon: () => <EditorSvgIcon pathData={editorLink} />,
},
// Headings
Expand All @@ -257,8 +273,8 @@ function createToolbarControls(
title: textStrings.headings,
closeMenuTitle: textStrings.closeSubMenu,
isActive: isAnyHeadingActive,
group: 'default',
renderIcon: () => <EditorSvgIcon pathData={editorText} />,
renderCloseMenuIcon: () => <FaIcon icon={faXmark} />,
subMenuButtons: ([1, 2, 3] as const).map((level) => ({
name: TextEditorFormattingOption.headings,
title: `${textStrings.heading} ${level}`,
Expand All @@ -275,12 +291,12 @@ function createToolbarControls(
title: textStrings.colors,
closeMenuTitle: textStrings.closeSubMenu,
isActive: () => false,
group: 'default',
renderIcon: (editor: SlateEditor) => {
const colorIndex = getColorIndex(editor)
const color = colorIndex ? textColors[colorIndex].value : 'black'
return <ColorTextIcon color={color} />
},
renderCloseMenuIcon: () => <FaIcon icon={faXmark} />,
subMenuButtons: [
{
name: TextEditorFormattingOption.colors,
Expand Down Expand Up @@ -315,6 +331,7 @@ function createToolbarControls(
title: textStrings.orderedList,
isActive: isSelectionWithinOrderedList,
onClick: toggleOrderedList,
group: 'lists',
renderIcon: () => <FaIcon className="h-[15px]" icon={faListOl} />,
},
// Unordered list
Expand All @@ -323,6 +340,7 @@ function createToolbarControls(
title: textStrings.unorderedList,
isActive: isSelectionWithinUnorderedList,
onClick: toggleUnorderedList,
group: 'lists',
renderIcon: () => <FaIcon className="h-[15px]" icon={faListUl} />,
},
// Math
Expand All @@ -331,6 +349,7 @@ function createToolbarControls(
title: textStrings.mathFormula,
isActive: isMathActive,
onClick: toggleMath,
group: 'default',
renderIcon: () => (
<FaIcon className="h-[15px]" icon={faSquareRootVariable} />
),
Expand All @@ -341,28 +360,17 @@ function createToolbarControls(
title: textStrings.code,
isActive: isCodeActive,
onClick: toggleCode,
group: 'default',
renderIcon: () => <FaIcon className="h-[15px]" icon={faCode} />,
},
// Blank (For Fill in the Blank Exercises)
{
name: TextEditorFormattingOption.textBlank,
title: textStrings.blank,
isActive: isBlankActive,
onClick: toggleBlank,
renderIcon: () => (
<span className="relative -top-0.5 rounded-lg border-2 border-current px-1 text-[10px] font-bold">
_
</span>
),
},
].map((option) => {
return {
...option,
title: option.title.replace('%ctrlOrCmd%', isMac ? '⌘' : ctrlKey),
}
})

return allFormattingOptions.filter((option) =>
formattingOptions.includes(TextEditorFormattingOption[option.name])
)
return allFormattingOptions.filter(({ name }) =>
formattingOptions.includes(TextEditorFormattingOption[name])
) as ControlButton[]
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function PluginToolbarTextControlButton({
active
? 'bg-editor-primary-200 text-almost-black shadow-menu hover:text-black'
: '#b6b6b6 hover:text-editor-primary',
'b-0 m-1 h-6 w-6 cursor-pointer rounded p-0 outline-none',
'b-0 m-1 h-6 w-auto min-w-[1.5rem] cursor-pointer rounded p-0 outline-none',
'serlo-tooltip-trigger'
)}
data-qa={`plugin-toolbar-button-${
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useState } from 'react'
import { faXmark } from '@fortawesome/free-solid-svg-icons'
import { Fragment, useState } from 'react'
import { Editor as SlateEditor } from 'slate'

import { PluginToolbarTextControlButton } from './plugin-toolbar-text-control-button'
import type { NestedControlButton, ControlButton } from './types'
import { FaIcon } from '@/components/fa-icon'

export interface PluginToolbarTextControlsProps {
controls: ControlButton[]
Expand All @@ -29,28 +31,41 @@ export function PluginToolbarTextControls({

const mathActive = controls.find(isMath)?.isActive(editor)
const blankActive = controls.find(isBlank)?.isActive(editor)
const isSpecialMode = mathActive || blankActive

if (typeof subMenu !== 'number') {
return (
<>
{controls.map((control, index) => {
if (mathActive && !isMath(control)) return null
if (blankActive && !isBlank(control)) return null

const next = controls.at(index + 1)
const showSeparator =
!isSpecialMode && !!next && next.group !== control.group

return (
<PluginToolbarTextControlButton
active={control.isActive(editor)}
tooltipText={control.title}
onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
isNestedControlButton(control)
? setSubMenu(index)
: control.onClick(editor)
}}
key={index}
>
{control.renderIcon(editor)}
</PluginToolbarTextControlButton>
<Fragment key={control.title}>
<PluginToolbarTextControlButton
active={control.isActive(editor)}
tooltipText={
control.isActive(editor) &&
Object.hasOwn(control, 'activeTitle')
? control.activeTitle
: control.title
}
onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
isNestedControlButton(control)
? setSubMenu(index)
: control.onClick(editor)
}}
>
{control.renderIcon(editor)}
</PluginToolbarTextControlButton>
{showSeparator ? <span className="opacity-30"> | </span> : null}
</Fragment>
)
})}
</>
Expand All @@ -66,7 +81,7 @@ export function PluginToolbarTextControls({
return false
},
renderIcon() {
return activeControl.renderCloseMenuIcon()
return <FaIcon icon={faXmark} />
},
onClick() {
setSubMenu(undefined)
Expand All @@ -77,20 +92,22 @@ export function PluginToolbarTextControls({

return (
<>
{subMenuControls.map((control, index) => (
<PluginToolbarTextControlButton
active={control.isActive(editor)}
tooltipText={control.title}
onMouseDown={(event) => {
event.preventDefault()
control.onClick(editor)
setSubMenu(undefined)
}}
key={index}
>
{control.renderIcon(editor)}
</PluginToolbarTextControlButton>
))}
{subMenuControls.map((control, index) => {
return (
<PluginToolbarTextControlButton
active={control.isActive(editor)}
tooltipText={control.title}
onMouseDown={(event) => {
event.preventDefault()
control.onClick(editor)
setSubMenu(undefined)
}}
key={index}
>
{control.renderIcon(editor)}
</PluginToolbarTextControlButton>
)
})}
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,27 @@ export enum TextEditorFormattingOption {
richTextBold = 'richTextBold',
richTextItalic = 'richTextItalic',
textBlank = 'textBlank',
separator = 'separator',
}

export type ControlButton = ActionControlButton | NestedControlButton

interface ActionControlButton {
name: TextEditorFormattingOption
title: string
activeTitle?: string
isActive(editor: SlateEditor): boolean
group: 'blank' | 'default'
onClick(editor: SlateEditor): void
renderIcon(editor: SlateEditor): React.ReactNode
}

export interface NestedControlButton {
name: TextEditorFormattingOption
title: string
closeMenuTitle: string
subMenuButtons: ActionControlButton[]
isActive(editor: SlateEditor): boolean
group: undefined
renderIcon(editor: SlateEditor): React.ReactNode
renderCloseMenuIcon(): React.ReactNode
}

0 comments on commit 80de7e3

Please sign in to comment.