Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,37 @@ const props = defineProps({
type: Boolean,
default: false,
},
compactSideGroups: {
type: Boolean,
default: false,
},
});

const currentItem = ref(null);
const { isHighContrastMode } = useHighContrastMode();
// Matches media query from SuperDoc.vue
const isMobile = window.matchMedia('(max-width: 768px)').matches;
const styleMap = {
left: {
minWidth: '120px',
justifyContent: 'flex-start',
},
right: {
minWidth: '120px',
justifyContent: 'flex-end',
},
default: {

const getPositionStyle = computed(() => {
if (props.position === 'left') {
return {
minWidth: props.compactSideGroups ? 'auto' : '120px',
justifyContent: 'flex-start',
};
}

if (props.position === 'right') {
return {
minWidth: props.compactSideGroups ? 'auto' : '120px',
justifyContent: 'flex-end',
};
}

return {
// Only grow if not on a mobile device
flexGrow: isMobile ? 0 : 1,
justifyContent: 'center',
},
};

const getPositionStyle = computed(() => {
return styleMap[props.position] || styleMap.default;
};
});

const isButton = (item) => item.type === 'button';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { ref, getCurrentInstance, onMounted, onDeactivated, nextTick, computed } from 'vue';
import { throttle } from './helpers.js';
import ButtonGroup from './ButtonGroup.vue';
import { RESPONSIVE_BREAKPOINTS } from './constants.js';

/**
* The default font-family to use for toolbar UI surfaces when no custom font is configured.
Expand All @@ -14,6 +15,8 @@ const { proxy } = getCurrentInstance();
const emit = defineEmits(['command', 'toggle', 'select']);

let toolbarKey = ref(1);
const compactSideGroups = ref(false);
let containerResizeObserver = null;

/**
* Computed property that determines the font-family to use for toolbar UI surfaces.
Expand All @@ -40,14 +43,27 @@ const getFilteredItems = (position) => {
return proxy.$toolbar.getToolbarItemByGroup(position).filter((item) => !excludeButtonsList.includes(item.name.value));
};

const updateCompactSideGroups = () => {
Comment thread
artem-harbour marked this conversation as resolved.
compactSideGroups.value = proxy.$toolbar.getAvailableWidth() <= RESPONSIVE_BREAKPOINTS.lg;
};

onMounted(() => {
window.addEventListener('resize', onResizeThrottled);
window.addEventListener('keydown', onKeyDown);
if (typeof ResizeObserver !== 'undefined' && proxy.$toolbar.toolbarContainer) {
containerResizeObserver = new ResizeObserver(() => {
onResizeThrottled();
});
containerResizeObserver.observe(proxy.$toolbar.toolbarContainer);
}
updateCompactSideGroups();
});
Comment thread
artem-harbour marked this conversation as resolved.

onDeactivated(() => {
window.removeEventListener('resize', onResizeThrottled);
window.removeEventListener('keydown', onKeyDown);
containerResizeObserver?.disconnect();
containerResizeObserver = null;
});

const onKeyDown = async (e) => {
Expand All @@ -66,6 +82,7 @@ const onKeyDown = async (e) => {

const onWindowResized = async () => {
await proxy.$toolbar.onToolbarResize();
updateCompactSideGroups();
toolbarKey.value += 1;
};
const onResizeThrottled = throttle(onWindowResized, 300);
Expand Down Expand Up @@ -107,6 +124,7 @@ const handleToolbarMousedown = (e) => {
tabindex="0"
v-if="showLeftSide"
:toolbar-items="getFilteredItems('left')"
:compact-side-groups="compactSideGroups"
:ui-font-family="uiFontFamily"
position="left"
@command="handleCommand"
Expand All @@ -117,6 +135,7 @@ const handleToolbarMousedown = (e) => {
tabindex="0"
:toolbar-items="getFilteredItems('center')"
:overflow-items="proxy.$toolbar.overflowItems"
:compact-side-groups="compactSideGroups"
:ui-font-family="uiFontFamily"
position="center"
@command="handleCommand"
Expand All @@ -126,6 +145,7 @@ const handleToolbarMousedown = (e) => {
tabindex="0"
v-if="showRightSide"
:toolbar-items="getFilteredItems('right')"
:compact-side-groups="compactSideGroups"
:ui-font-family="uiFontFamily"
position="right"
@command="handleCommand"
Expand All @@ -148,12 +168,6 @@ const handleToolbarMousedown = (e) => {
z-index: var(--sd-ui-toolbar-z-index, 10);
}

@media (max-width: 1280px) {
.superdoc-toolbar-group-side {
min-width: auto !important;
}
}

@media (max-width: 768px) {
.superdoc-toolbar {
padding: 4px 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,21 +291,19 @@ const caretIcon = computed(() => {
height: 10px;
}

@media (max-width: 1280px) {
.toolbar-item--doc-mode .button-label {
display: none;
}
.toolbar-item--doc-mode-compact .button-label {
display: none;
}

.toolbar-item--doc-mode .toolbar-icon {
margin-right: 5px;
}
.toolbar-item--doc-mode-compact .toolbar-icon {
margin-right: 5px;
}

.toolbar-item--linked-styles {
width: auto !important;
}
.toolbar-item--linked-styles-compact {
width: auto !important;
}

.toolbar-item--linked-styles .button-label {
display: none;
}
.toolbar-item--linked-styles-compact .button-label {
display: none;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ export const TOOLBAR_FONT_SIZES = [
{ label: '96', key: '96pt', props: { 'data-item': 'btn-fontSize-option' } },
];

export const RESPONSIVE_BREAKPOINTS = {
sm: 768,
md: 1024,
lg: 1280,
xl: 1410,
};

export const HEADLESS_ITEM_MAP = {
undo: 'undo',
redo: 'redo',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { scrollToElement } from './scroll-helpers.js';

import checkIconSvg from '@superdoc/common/icons/check.svg?raw';
import SearchInput from './SearchInput.vue';
import { TOOLBAR_FONTS, TOOLBAR_FONT_SIZES } from './constants.js';
import { RESPONSIVE_BREAKPOINTS, TOOLBAR_FONTS, TOOLBAR_FONT_SIZES } from './constants.js';
import { getQuickFormatList } from '@extensions/linked-styles/index.js';

const closeDropdown = (dropdown) => {
Expand Down Expand Up @@ -995,18 +995,34 @@ export const makeDefaultItems = ({
}),
});

// Responsive toolbar calculations
const breakpoints = {
sm: 768,
md: 1024,
lg: 1280,
xl: 1410,
};
// Responsive toolbar calculations.
// `availableWidth` comes from SuperToolbar and represents either:
// - container width when `responsiveToContainer: true`
// - viewport/document width when `responsiveToContainer: false`

// Extra headroom to prevent toolbar jitter at the XL edge;
// with `<=` this effectively shifts overflow by 21px.
const XL_OVERFLOW_SAFETY_BUFFER = 20;
Comment thread
artem-harbour marked this conversation as resolved.
const stickyItemsWidth = 120;
const toolbarPadding = 32;

const itemsToHideXL = ['linkedStyles', 'clearFormatting', 'copyFormat', 'ruler'];
const itemsToHideSM = ['zoom', 'fontFamily', 'fontSize', 'redo'];
const shouldUseLgCompactStyles = availableWidth <= RESPONSIVE_BREAKPOINTS.lg;

if (shouldUseLgCompactStyles) {
documentMode.attributes.value = {
...documentMode.attributes.value,
className: `${documentMode.attributes.value.className} toolbar-item--doc-mode-compact`,
};
}

if (shouldUseLgCompactStyles) {
linkedStyles.attributes.value = {
...linkedStyles.attributes.value,
className: `${linkedStyles.attributes.value.className} toolbar-item--linked-styles-compact`,
};
}

let toolbarItems = [
undo,
Expand Down Expand Up @@ -1053,7 +1069,7 @@ export const makeDefaultItems = ({
}

// Hide separators on small screens
if (availableWidth <= breakpoints.md && hideButtons) {
if (availableWidth <= RESPONSIVE_BREAKPOINTS.md && hideButtons) {
toolbarItems = toolbarItems.filter((item) => item.type !== 'separator');
}

Expand Down Expand Up @@ -1088,7 +1104,11 @@ export const makeDefaultItems = ({
toolbarItems.forEach((item) => {
const itemWidth = controlSizes.get(item.name.value) || controlSizes.get('default');

if (availableWidth < breakpoints.xl && itemsToHideXL.includes(item.name.value) && hideButtons) {
if (
availableWidth <= RESPONSIVE_BREAKPOINTS.xl + XL_OVERFLOW_SAFETY_BUFFER &&
itemsToHideXL.includes(item.name.value) &&
hideButtons
) {
overflowItems.push(item);
if (item.name.value === 'linkedStyles') {
const linkedStylesIdx = toolbarItems.findIndex((item) => item.name.value === 'linkedStyles');
Expand All @@ -1097,7 +1117,7 @@ export const makeDefaultItems = ({
return;
}

if (availableWidth < breakpoints.sm && itemsToHideSM.includes(item.name.value) && hideButtons) {
if (availableWidth < RESPONSIVE_BREAKPOINTS.sm && itemsToHideSM.includes(item.name.value) && hideButtons) {
overflowItems.push(item);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,16 @@ export class SuperToolbar extends EventEmitter {
return this.toolbarItems.find((item) => item.name.value === name);
}

/**
* Get the width used for responsive toolbar decisions.
* @returns {number} Available width in pixels
*/
getAvailableWidth() {
const documentWidth = document.documentElement.clientWidth; // take into account the scrollbar
const containerWidth = this.toolbarContainer?.offsetWidth ?? 0;
return this.config.responsiveToContainer ? containerWidth : documentWidth;
}

/**
* Create toolbar items based on configuration
* @private
Expand All @@ -397,9 +407,7 @@ export class SuperToolbar extends EventEmitter {
* @returns {void}
*/
#makeToolbarItems({ superToolbar, icons, texts, fonts, hideButtons, isDev = false } = {}) {
const documentWidth = document.documentElement.clientWidth; // take into account the scrollbar
const containerWidth = this.toolbarContainer?.offsetWidth ?? 0;
const availableWidth = this.config.responsiveToContainer ? containerWidth : documentWidth;
const availableWidth = this.getAvailableWidth();

const { defaultItems, overflowItems } = makeDefaultItems({
superToolbar,
Expand Down
2 changes: 1 addition & 1 deletion packages/superdoc/src/dev/components/SuperdocDev.vue
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ const init = async () => {
// },
// fonts: null,
// hideButtons: false,
// responsiveToContainer: true,
responsiveToContainer: true,
Comment thread
artem-harbour marked this conversation as resolved.
excludeItems: [], // ['italic', 'bold'],
// texts: {},
},
Expand Down
Loading