Align QWord shell with Office ribbon

This commit is contained in:
Курнат Андрей
2026-06-02 07:00:42 +03:00
parent 68e0678ca6
commit ffa377d931
4 changed files with 96 additions and 23 deletions
+16
View File
@@ -0,0 +1,16 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { AppShell } from "./AppShell";
describe("AppShell", () => {
it("не показывает общую ленту навигации между приложениями", () => {
const view = render(
<AppShell activeApp="word" status={{ savedLabel: "Сохранено локально" }}>
<div>Рабочая область QWord</div>
</AppShell>
);
expect(screen.getByText("Рабочая область QWord")).toBeInTheDocument();
expect(view.container.querySelector(".ribbon")).not.toBeInTheDocument();
});
});
-10
View File
@@ -57,16 +57,6 @@ export function AppShell({ activeApp, status, children }: AppShellProps) {
</div> </div>
</header> </header>
<section className="ribbon" aria-label="Лента команд">
<div className="ribbon-tabs">
{["Файл", "Главная", "Вставка", "Рецензирование", "Вид", "Экспорт"].map((tab, index) => (
<button key={tab} className={index === 1 ? "is-active" : ""} type="button">
{tab}
</button>
))}
</div>
</section>
<div className="surface-host">{children}</div> <div className="surface-host">{children}</div>
<footer className="statusbar"> <footer className="statusbar">
+8
View File
@@ -134,6 +134,7 @@ const styleGalleryOptions = [
{ value: "h6", label: "Заголовок 6" }, { value: "h6", label: "Заголовок 6" },
{ value: "blockquote", label: "Цитата" } { value: "blockquote", label: "Цитата" }
]; ];
const wordRibbonTabs = ["Файл", "Главная", "Вставка", "Конструктор", "Макет", "Ссылки", "Рассылки", "Рецензирование", "Вид", "Справка"];
function findInPage(query: string) { function findInPage(query: string) {
return (window as PageSearchWindow).find?.(query) ?? false; return (window as PageSearchWindow).find?.(query) ?? false;
@@ -375,6 +376,13 @@ export function QWord({ document, onChange }: QWordProps) {
onChange={(event) => void insertImage(event.target.files?.[0])} onChange={(event) => void insertImage(event.target.files?.[0])}
aria-label="Вставить изображение" aria-label="Вставить изображение"
/> />
<div className="word-ribbon-tabs" role="tablist" aria-label="Вкладки QWord">
{wordRibbonTabs.map((tab, index) => (
<button key={tab} className={index === 1 ? "is-active" : ""} type="button" role="tab" aria-selected={index === 1}>
{tab}
</button>
))}
</div>
<div className="word-ribbon" aria-label="Лента команд QWord"> <div className="word-ribbon" aria-label="Лента команд QWord">
<section className="word-ribbon-group word-file-group" aria-label="Файл"> <section className="word-ribbon-group word-file-group" aria-label="Файл">
<div className="word-file-actions"> <div className="word-file-actions">
+72 -13
View File
@@ -87,7 +87,7 @@ button {
.workspace { .workspace {
display: grid; display: grid;
grid-template-rows: 34px 36px minmax(0, 1fr) 28px; grid-template-rows: 34px minmax(0, 1fr) 28px;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
height: 100%; height: 100%;
@@ -329,18 +329,53 @@ button {
.word-module-toolbar { .word-module-toolbar {
display: block; display: block;
min-height: 118px; min-height: 154px;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
} }
.word-ribbon-tabs {
display: flex;
align-items: center;
min-width: 100%;
height: 36px;
overflow-x: auto;
overflow-y: hidden;
border-bottom: 1px solid #d6dde8;
background: #ffffff;
}
.word-ribbon-tabs button {
flex: 0 0 auto;
height: 36px;
padding: 0 13px;
border: 0;
border-bottom: 2px solid transparent;
background: transparent;
color: #2f343c;
cursor: pointer;
font-size: 13px;
}
.word-ribbon-tabs button.is-active {
border-bottom-color: #2f5f9f;
color: #1f4f8a;
font-weight: 700;
}
.word-ribbon-tabs button:hover {
background: #edf4fb;
}
.word-ribbon { .word-ribbon {
display: flex; display: flex;
align-items: stretch; align-items: stretch;
width: 100%; width: 100%;
min-width: 100%; min-width: 100%;
height: 118px;
min-height: 118px; min-height: 118px;
overflow: hidden; overflow-x: auto;
overflow-y: hidden;
background: #ffffff; background: #ffffff;
} }
@@ -349,6 +384,7 @@ button {
grid-template-rows: minmax(0, 1fr) 18px; grid-template-rows: minmax(0, 1fr) 18px;
align-items: stretch; align-items: stretch;
gap: 4px; gap: 4px;
height: 118px;
min-height: 118px; min-height: 118px;
min-width: 0; min-width: 0;
padding: 8px 10px 4px; padding: 8px 10px 4px;
@@ -459,10 +495,17 @@ button {
} }
.word-font-group { .word-font-group {
flex: 1 1 266px; flex: 0 0 266px;
grid-template-rows: 28px 28px minmax(0, 1fr) 18px;
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
.word-ribbon select {
height: 28px;
border-radius: 3px;
font-size: 12px;
}
.word-font-select { .word-font-select {
min-width: 0; min-width: 0;
flex: 1 1 170px; flex: 1 1 170px;
@@ -476,16 +519,18 @@ button {
.word-format-tools { .word-format-tools {
width: fit-content; width: fit-content;
max-width: 100%; max-width: 100%;
margin-top: 6px; margin-top: 0;
} }
.word-color-row { .word-color-row {
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 6px; gap: 4px;
margin-top: 0;
overflow: hidden;
} }
.word-color-row .text-color-tools { .word-color-row .text-color-tools {
min-height: 26px; min-height: 22px;
padding-left: 0; padding-left: 0;
border-left: 0; border-left: 0;
} }
@@ -497,6 +542,7 @@ button {
.word-paragraph-group { .word-paragraph-group {
flex: 0 0 170px; flex: 0 0 170px;
grid-template-rows: 28px 28px minmax(0, 1fr) 18px;
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
@@ -510,20 +556,22 @@ button {
} }
.word-styles-group { .word-styles-group {
flex: 0 1 210px; flex: 0 0 360px;
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
.word-style-gallery { .word-style-gallery {
display: grid; display: flex;
grid-template-columns: repeat(2, minmax(74px, 1fr)); align-items: stretch;
gap: 6px; gap: 6px;
min-width: 0; min-width: 0;
overflow: hidden;
} }
.word-style-gallery button { .word-style-gallery button {
display: grid; display: grid;
align-content: center; align-content: center;
flex: 0 0 82px;
min-height: 64px; min-height: 64px;
padding: 6px 8px; padding: 6px 8px;
border: 1px solid #d6dde8; border: 1px solid #d6dde8;
@@ -563,6 +611,17 @@ button {
font-size: 11px; font-size: 11px;
} }
.word-ribbon .segmented-tools button {
width: 30px;
height: 28px;
}
.word-ribbon .compact-command {
height: 28px;
padding: 0 8px;
font-size: 12px;
}
.hidden-file-input { .hidden-file-input {
display: none; display: none;
} }
@@ -809,7 +868,7 @@ button {
.page-ruler { .page-ruler {
display: grid; display: grid;
grid-template-columns: repeat(9, 1fr); grid-template-columns: repeat(9, 1fr);
width: clamp(794px, calc(100% - 80px), 1000px); width: var(--qword-page-width, 794px);
height: 26px; height: 26px;
margin: 12px auto 0; margin: 12px auto 0;
color: #7a8797; color: #7a8797;
@@ -845,12 +904,12 @@ button {
.paper-frame { .paper-frame {
position: relative; position: relative;
width: clamp(794px, calc(100% - 80px), 1000px); width: var(--qword-page-width, 794px);
margin: 0 auto 38px; margin: 0 auto 38px;
} }
.paper { .paper {
--qword-page-height: 1180px; --qword-page-height: 1123px;
min-height: var(--qword-page-height); min-height: var(--qword-page-height);
width: 100%; width: 100%;
margin: 0; margin: 0;