Skip to content

Commit

Permalink
feat: toolbar enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdehaven committed Jan 2, 2024
1 parent 8a5c468 commit c69e591
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 40 deletions.
11 changes: 10 additions & 1 deletion sandbox/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
@save="contentSaved"
@update:model-value="contentUpdated"
>
<!-- <template #actions="{ save }">
<!-- <template #toolbar-right>
<button>
Custom button
</button>
</template> -->
<!-- <template #editor-actions="{ save }">
<button @click="save">
Toolbar
</button>
Expand Down Expand Up @@ -77,4 +82,8 @@ onBeforeMount(async () => {
.sandbox-container {
padding: var(--kui-space-0, $kui-space-0) var(--kui-space-70, $kui-space-70);
}
button {
white-space: nowrap;
}
</style>
12 changes: 12 additions & 0 deletions src/assets/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,15 @@
box-shadow: 0px 0px 0px 2px rgba(0, 68, 244, 0.2);
}
}

@mixin sr-only {
border-width: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
69 changes: 63 additions & 6 deletions src/components/MarkdownUi.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,32 @@
class="kong-ui-public-markdown-ui"
:class="[`mode-${currentMode}`, { 'fullscreen': isFullscreen }]"
>
<div
v-if="editable && currentMode !== 'read'"
class="toolbar-overlay"
:class="{ 'left': !arrivedState.left }"
/>
<div
v-if="editable && currentMode !== 'read'"
class="toolbar-overlay"
:class="{ 'right': !arrivedState.right }"
/>
<MarkdownToolbar
v-if="editable && currentMode !== 'read'"
ref="toolbar"
@change-mode="(mode: MarkdownMode) => currentMode = mode"
@format-selection="formatSelection"
@insert-template="insertTemplate"
@toggle-fullscreen="toggleFullscreen"
@toggle-html-preview="toggleHtmlPreview"
>
<template #actions>
<template #toolbar-right>
<slot name="toolbar-right" />
</template>
<template #editor-actions>
<slot
:cancel="cancel"
name="actions"
name="editor-actions"
:save="save"
>
<template
Expand Down Expand Up @@ -110,9 +124,9 @@ import MarkdownToolbar from '@/components/toolbar/MarkdownToolbar.vue'
import MarkdownContent from '@/components/MarkdownContent.vue'
import ToolbarButton from '@/components/toolbar/ToolbarButton.vue'
import composables from '@/composables'
import { TEXTAREA_ID_INJECTION_KEY, MODE_INJECTION_KEY, EDITABLE_INJECTION_KEY, FULLSCREEN_INJECTION_KEY, HTML_PREVIEW_INJECTION_KEY, THEME_INJECTION_KEY } from '@/injection-keys'
import { UNIQUE_ID_INJECTION_KEY, TEXTAREA_ID_INJECTION_KEY, MODE_INJECTION_KEY, EDITABLE_INJECTION_KEY, FULLSCREEN_INJECTION_KEY, HTML_PREVIEW_INJECTION_KEY, THEME_INJECTION_KEY } from '@/injection-keys'
import { EDITOR_DEBOUNCE_TIMEOUT, TOOLBAR_HEIGHT, NEW_LINE_CHARACTER } from '@/constants'
import { useMediaQuery } from '@vueuse/core'
import { useMediaQuery, useScroll } from '@vueuse/core'
import { v4 as uuidv4 } from 'uuid'
import type { MarkdownMode, InlineFormat, MarkdownTemplate, TextAreaInputEvent } from '@/types'
import formatHtml from 'html-format'
Expand Down Expand Up @@ -190,6 +204,7 @@ const scrollableClass = computed((): string => `scrollable-${uniqueId}`)
const tabSize = computed((): number => props.tabSize)
// Provide values to child components
provide(UNIQUE_ID_INJECTION_KEY, computed((): string => uniqueId))
provide(TEXTAREA_ID_INJECTION_KEY, computed((): string => textareaId.value))
provide(MODE_INJECTION_KEY, computed((): MarkdownMode => currentMode.value))
provide(EDITABLE_INJECTION_KEY, computed((): boolean => props.editable))
Expand Down Expand Up @@ -430,6 +445,10 @@ watch(() => props.theme, async (theme: 'light' | 'dark') => {
}
})
const toolbar = ref<HTMLElement | null>(null)
// Track the scroll position of the toolbar to show/hide the `.toolbar-overlay`
const { arrivedState } = useScroll(toolbar)
// Initialize keyboard shortcuts; they will only fire in edit mode when the textarea is active
composables.useKeyboardShortcuts(textareaId.value, rawMarkdown, tabSize, emulateInputEvent)
Expand Down Expand Up @@ -477,8 +496,37 @@ const markdownEditorMaxHeight = computed((): string => `${props.editorMaxHeight}
gap: var(--kui-space-50, $kui-space-50);
margin-bottom: var(--kui-space-70, $kui-space-70);
padding-bottom: var(--kui-space-50, $kui-space-50);
position: relative;
width: 100%;
.toolbar-overlay {
bottom: 0;
content: '';
display: none;
height: calc(v-bind('TOOLBAR_HEIGHT') + 1px);
pointer-events: none;
position: absolute;
top: 0;
width: 20px;
// Show an overlay transparency while the toolbar is scrollable
@media (max-width: ($kui-breakpoint-phablet - 1px)) {
display: block;
}
&.left {
background: linear-gradient(to right, rgba(#000, 0.1), rgba(#000, 0));
border-top-left-radius: var(--kui-border-radius-40, $kui-border-radius-40);
left: 0;
}
&.right {
background: linear-gradient(to right, rgba(#000, 0), rgba(#000, 0.1));
border-top-right-radius: var(--kui-border-radius-40, $kui-border-radius-40);
right: 0;
}
}
.markdown-panes {
box-sizing: border-box;
display: flex;
Expand Down Expand Up @@ -509,8 +557,7 @@ const markdownEditorMaxHeight = computed((): string => `${props.editorMaxHeight}
// Fullscreen mode only available when editing
&.fullscreen {
border-bottom-left-radius: var(--kui-border-radius-0, $kui-border-radius-0);
border-bottom-right-radius: var(--kui-border-radius-0, $kui-border-radius-0);
border-radius: var(--kui-border-radius-0, $kui-border-radius-0);
bottom: 0;
height: 100%;
left: 0;
Expand All @@ -526,6 +573,16 @@ const markdownEditorMaxHeight = computed((): string => `${props.editorMaxHeight}
padding-bottom: var(--kui-space-50, $kui-space-50);
}
.toolbar-overlay {
&.left {
border-top-left-radius: var(--kui-border-radius-0, $kui-border-radius-0);
}
&.right {
border-top-right-radius: var(--kui-border-radius-0, $kui-border-radius-0);
}
}
:deep(.markdown-ui-toolbar) {
border-top-left-radius: var(--kui-border-radius-0, $kui-border-radius-0);
border-top-right-radius: var(--kui-border-radius-0, $kui-border-radius-0);
Expand Down
102 changes: 69 additions & 33 deletions src/components/toolbar/MarkdownToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,47 @@
<div
v-if="editable && mode !== 'read'"
class="button-group"
role="radiogroup"
>
<button
:class="{ 'active': mode === 'edit' }"
<label
:id="`editor-mode-options-${uniqueId}`"
class="sr-only"
>Select the editor mode</label>
<ToolbarButton
:aria-checked="mode === 'edit'"
:aria-labelledby="`editor-mode-options-${uniqueId}`"
:class="['edit', { 'active': mode === 'edit' }]"
:disabled="mode === 'edit'"
:tabindex="0"
:icon="false"
role="radio"
type="button"
@click.prevent="changeMode('edit')"
>
Edit
</button>
<button
</ToolbarButton>
<ToolbarButton
:aria-checked="mode === 'split'"
:aria-labelledby="`editor-mode-options-${uniqueId}`"
class="mode-split-button"
:class="{ 'active': mode === 'split' }"
:tabindex="0"
:class="['split', { 'active': mode === 'split' }]"
:icon="false"
role="radio"
type="button"
@click.prevent="changeMode('split')"
>
Split
</button>
<button
:class="{ 'active': mode === 'preview' }"
:tabindex="0"
</ToolbarButton>
<ToolbarButton
:aria-checked="mode === 'preview'"
:aria-labelledby="`editor-mode-options-${uniqueId}`"
:class="['preview', { 'active': mode === 'preview' }]"
:icon="false"
role="radio"
type="button"
@click.prevent="changeMode('preview')"
>
Preview
</button>
</ToolbarButton>
</div>

<template v-if="editable && !['preview', 'read'].includes(mode)">
Expand All @@ -52,7 +66,6 @@
<ToolbarButton
:aria-label="option.label"
:data-testid="`format-option-${option.action}`"
:tabindex="0"
@click.prevent="emit('format-selection', option.action)"
>
<component
Expand All @@ -76,7 +89,6 @@
<ToolbarButton
:aria-label="option.label"
:data-testid="`template-option-${option.action}`"
:tabindex="0"
@click.prevent="emit('insert-template', option.action)"
>
<component
Expand All @@ -103,7 +115,6 @@
<ToolbarButton
:aria-label="fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'"
data-testid="toggle-fullscreen"
:tabindex="0"
@click.prevent="toggleFullscreen"
>
<component
Expand All @@ -124,7 +135,6 @@
<ToolbarButton
:aria-label="htmlPreview ? 'Toggle markdown Preview' : 'Toggle HTML Preview'"
data-testid="toggle-html-preview"
:tabindex="0"
@click.prevent="toggleHtmlPreview"
>
<component
Expand All @@ -136,18 +146,24 @@
</InfoTooltip>
</div>
<div
class="actions"
data-testid="actions"
class="toolbar-right"
data-testid="slot-toolbar-right"
>
<slot name="toolbar-right" />
</div>
<div
class="editor-actions"
data-testid="slot-editor-actions"
>
<slot name="actions" />
<slot name="editor-actions" />
</div>
</div>
</template>

<script setup lang="ts">
import { inject, ref, onMounted, watch } from 'vue'
import type { Ref } from 'vue'
import { MODE_INJECTION_KEY, EDITABLE_INJECTION_KEY, FULLSCREEN_INJECTION_KEY, HTML_PREVIEW_INJECTION_KEY } from '@/injection-keys'
import { MODE_INJECTION_KEY, EDITABLE_INJECTION_KEY, FULLSCREEN_INJECTION_KEY, HTML_PREVIEW_INJECTION_KEY, UNIQUE_ID_INJECTION_KEY } from '@/injection-keys'
import { useMediaQuery } from '@vueuse/core'
import { TOOLBAR_HEIGHT } from '@/constants'
import { KUI_BREAKPOINT_PHABLET, KUI_ICON_SIZE_40 } from '@kong/design-tokens'
Expand All @@ -156,7 +172,9 @@ import ToolbarButton from '@/components/toolbar/ToolbarButton.vue'
import InfoTooltip from '@/components/toolbar/InfoTooltip.vue'
import TooltipShortcut from '@/components/toolbar/TooltipShortcut.vue'
import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, /* SubscriptIcon, SuperscriptIcon, MarkIcon, */ CodeIcon, CodeblockIcon, TableIcon, TasklistIcon, ListUnorderedIcon, ListOrderedIcon, MarkdownIcon, HtmlIcon, BlockquoteIcon, ExpandIcon, CollapseIcon } from '@kong/icons'
import { v4 as uuidv4 } from 'uuid'
const uniqueId: Ref<String> = inject(UNIQUE_ID_INJECTION_KEY, ref(uuidv4()))
const mode: Ref<MarkdownMode> = inject(MODE_INJECTION_KEY, ref('read'))
const editable: Ref<boolean> = inject(EDITABLE_INJECTION_KEY, ref(false))
const fullscreen: Ref<boolean> = inject(FULLSCREEN_INJECTION_KEY, ref(false))
Expand Down Expand Up @@ -233,6 +251,8 @@ onMounted(() => {
</script>

<style lang="scss" scoped>
@import "../../assets/mixins";
.markdown-ui-toolbar {
align-items: center;
background-color: var(--kui-color-background-neutral-weakest, $kui-color-background-neutral-weakest);
Expand All @@ -258,28 +278,35 @@ onMounted(() => {
}
}
// TODO: Replace with tabs
.button-group {
align-items: center;
display: flex;
button {
border: 0;
border-right: 1px solid $kui-color-border;
cursor: pointer;
padding: var(--kui-space-20, $kui-space-20) var(--kui-space-30, $kui-space-30);
:deep(.toolbar-button) {
border: var(--kui-border-width-10, $kui-border-width-10) solid var(--kui-color-border, $kui-color-border);
&:disabled {
cursor: not-allowed;
&.edit {
border-bottom-right-radius: $kui-border-radius-0;
border-top-right-radius: $kui-border-radius-0;
}
&:last-of-type {
&.split {
border-left: 0;
border-radius: $kui-border-radius-0;
border-right: 0;
}
&.active {
background: $kui-color-background-primary;
color: $kui-color-text-inverse;
&.preview {
border-bottom-left-radius: $kui-border-radius-0;
border-top-left-radius: $kui-border-radius-0;
}
&.active,
&.active:hover {
background: var(--kui-color-background-primary, $kui-color-background-primary);
border-color: var(--kui-color-border-primary, $kui-color-border-primary);
color: var(--kui-color-text-inverse, $kui-color-text-inverse);
cursor: pointer;
}
}
}
Expand All @@ -293,12 +320,17 @@ onMounted(() => {
}
.toolbar-left,
.actions {
.toolbar-right,
.editor-actions {
align-items: center;
display: flex;
gap: var(--kui-space-20, $kui-space-20);
}
.toolbar-right {
margin-left: auto;
}
.toolbar-divider {
background-color: var(--kui-color-background-disabled, $kui-color-background-disabled);
height: 16px;
Expand All @@ -310,4 +342,8 @@ onMounted(() => {
pointer-events: none;
}
}
.sr-only {
@include sr-only;
}
</style>
1 change: 1 addition & 0 deletions src/components/toolbar/ToolbarButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<button
class="toolbar-button"
:class="[{ 'has-text': !icon }, appearance]"
tabindex="0"
type="button"
>
<slot name="default" />
Expand Down
1 change: 1 addition & 0 deletions src/injection-keys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// NOTE: Do not export these injection keys from the package
export const UNIQUE_ID_INJECTION_KEY = Symbol('The unique id that prefixes all identifiers within a single instance of the component')
export const TEXTAREA_ID_INJECTION_KEY = Symbol('Textarea unique id')
export const MODE_INJECTION_KEY = Symbol('Markdown component mode')
export const EDITABLE_INJECTION_KEY = Symbol('Is the markdown component editable')
Expand Down

0 comments on commit c69e591

Please sign in to comment.