@beyondwork/docx-react-component 1.0.52 → 1.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +31 -40
- package/src/api/public-types.ts +67 -7
- package/src/io/chart-preview-resolver.ts +41 -0
- package/src/io/docx-session.ts +217 -23
- package/src/runtime/collab/checkpoint-store.ts +1 -1
- package/src/runtime/collab/event-types.ts +4 -0
- package/src/runtime/collab/runtime-collab-sync.ts +88 -8
- package/src/runtime/document-runtime.ts +182 -9
- package/src/runtime/layout/inert-layout-facet.ts +1 -0
- package/src/runtime/layout/layout-engine-version.ts +97 -2
- package/src/runtime/layout/layout-invalidation.ts +150 -30
- package/src/runtime/layout/page-graph.ts +19 -0
- package/src/runtime/layout/paginated-layout-engine.ts +128 -19
- package/src/runtime/layout/project-block-fragments.ts +27 -0
- package/src/runtime/layout/public-facet.ts +70 -1
- package/src/runtime/prerender/cache-envelope.ts +30 -0
- package/src/runtime/prerender/customxml-cache.ts +17 -3
- package/src/runtime/prerender/prerender-document.ts +17 -1
- package/src/runtime/render/render-frame-diff.ts +38 -2
- package/src/runtime/render/render-kernel.ts +67 -19
- package/src/runtime/surface-projection.ts +28 -0
- package/src/runtime/table-schema.ts +27 -0
- package/src/runtime/table-style-resolver.ts +51 -0
- package/src/ui/WordReviewEditor.tsx +6 -3
- package/src/ui/editor-runtime-boundary.ts +39 -2
- package/src/ui/headless/comment-decoration-model.ts +60 -5
- package/src/ui/headless/revision-decoration-model.ts +94 -6
- package/src/ui/shared/revision-filters.ts +16 -6
- package/src/ui-tailwind/chart/ChartSurface.tsx +236 -0
- package/src/ui-tailwind/chart/layout/axis-layout.ts +17 -9
- package/src/ui-tailwind/chart/layout/legend-layout.ts +231 -0
- package/src/ui-tailwind/chart/layout/plot-area.ts +152 -59
- package/src/ui-tailwind/chart/layout/title-layout.ts +184 -0
- package/src/ui-tailwind/chart/render/area.tsx +277 -0
- package/src/ui-tailwind/chart/render/bar-column.tsx +356 -0
- package/src/ui-tailwind/chart/render/bubble.tsx +134 -0
- package/src/ui-tailwind/chart/render/combo.tsx +85 -0
- package/src/ui-tailwind/chart/render/data-labels.tsx +513 -0
- package/src/ui-tailwind/chart/render/font-metrics.ts +298 -0
- package/src/ui-tailwind/chart/render/gridlines.ts +228 -0
- package/src/ui-tailwind/chart/render/line.tsx +363 -0
- package/src/ui-tailwind/chart/render/number-format.ts +120 -16
- package/src/ui-tailwind/chart/render/pie.tsx +275 -0
- package/src/ui-tailwind/chart/render/progressive-render.ts +103 -0
- package/src/ui-tailwind/chart/render/scatter.tsx +228 -0
- package/src/ui-tailwind/chart/render/smooth-curve.ts +101 -0
- package/src/ui-tailwind/chart/render/svg-primitives.ts +378 -0
- package/src/ui-tailwind/chart/render/unsupported.tsx +126 -0
- package/src/ui-tailwind/chrome/collab-audience-chip.tsx +11 -0
- package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +44 -18
- package/src/ui-tailwind/chrome/collab-presence-strip.tsx +68 -7
- package/src/ui-tailwind/chrome/collab-role-chip.tsx +21 -2
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +20 -3
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +102 -37
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +358 -0
- package/src/ui-tailwind/chrome/tw-comment-preview.tsx +108 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +227 -0
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +136 -0
- package/src/ui-tailwind/chrome/tw-empty-state.tsx +76 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +30 -16
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +23 -4
- package/src/ui-tailwind/chrome/tw-paste-drop-toast.tsx +113 -0
- package/src/ui-tailwind/chrome/tw-revision-hover-preview.tsx +150 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +2 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +38 -2
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +15 -3
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +32 -20
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +68 -0
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +10 -10
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +26 -5
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +29 -22
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +72 -10
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +33 -18
- package/src/ui-tailwind/chrome-overlay/tw-table-continuation-header.tsx +94 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +1 -0
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +20 -7
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +54 -0
- package/src/ui-tailwind/editor-surface/scroll-anchor.ts +93 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +107 -3
- package/src/ui-tailwind/index.ts +11 -0
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +2 -2
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +274 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +15 -2
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +15 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +19 -147
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +83 -32
- package/src/ui-tailwind/review/tw-health-panel.tsx +174 -109
- package/src/ui-tailwind/review/tw-rail-card.tsx +9 -1
- package/src/ui-tailwind/review/tw-review-rail.tsx +36 -42
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +189 -101
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +11 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +114 -46
- package/src/ui-tailwind/theme/chart-palette-adapter.ts +57 -0
- package/src/ui-tailwind/theme/editor-theme.css +275 -46
- package/src/ui-tailwind/theme/tokens.css +345 -0
- package/src/ui-tailwind/theme/tokens.ts +313 -0
- package/src/ui-tailwind/theme/use-density.ts +60 -0
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +14 -1
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +73 -32
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +49 -9
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +178 -14
- package/src/ui-tailwind/tw-review-workspace.tsx +39 -6
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +0 -85
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Density mode hook — reads and writes `document.documentElement.dataset.density`
|
|
3
|
+
* and persists the preference in `localStorage["wre.density"]`.
|
|
4
|
+
*
|
|
5
|
+
* Components that use spacing gaps / paddings should multiply by
|
|
6
|
+
* `var(--space-density-multiplier)` in their CSS; this hook owns the
|
|
7
|
+
* attribute that drives the CSS override blocks in `tokens.css`.
|
|
8
|
+
*
|
|
9
|
+
* No component consumer lands in Lane 6a — Lane 6b.U5 / 6c.U9 own the
|
|
10
|
+
* opt-in plumbing. This module ships the hook so downstream lanes can
|
|
11
|
+
* import without a dependency on 6a being in-progress.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useCallback, useEffect, useState } from "react";
|
|
15
|
+
|
|
16
|
+
export type DensityMode = "compact" | "standard" | "comfortable";
|
|
17
|
+
|
|
18
|
+
const STORAGE_KEY = "wre.density";
|
|
19
|
+
const DEFAULT_DENSITY: DensityMode = "standard";
|
|
20
|
+
|
|
21
|
+
function readPersistedDensity(): DensityMode {
|
|
22
|
+
try {
|
|
23
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
24
|
+
if (stored === "compact" || stored === "standard" || stored === "comfortable") {
|
|
25
|
+
return stored;
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// localStorage unavailable (SSR, sandboxed iframe, etc.)
|
|
29
|
+
}
|
|
30
|
+
return DEFAULT_DENSITY;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function applyDensity(mode: DensityMode): void {
|
|
34
|
+
document.documentElement.dataset.density = mode;
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem(STORAGE_KEY, mode);
|
|
37
|
+
} catch {
|
|
38
|
+
// ignore
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useDensity(): {
|
|
43
|
+
density: DensityMode;
|
|
44
|
+
setDensity: (mode: DensityMode) => void;
|
|
45
|
+
} {
|
|
46
|
+
const [density, setDensityState] = useState<DensityMode>(DEFAULT_DENSITY);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const initial = readPersistedDensity();
|
|
50
|
+
applyDensity(initial);
|
|
51
|
+
setDensityState(initial);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const setDensity = useCallback((mode: DensityMode) => {
|
|
55
|
+
applyDensity(mode);
|
|
56
|
+
setDensityState(mode);
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
return { density, setDensity };
|
|
60
|
+
}
|
|
@@ -52,7 +52,20 @@ import { preserveEditorSelectionMouseDown } from "../../ui/headless/preserve-edi
|
|
|
52
52
|
import { ROLE_ACTION_SETS } from "../chrome/role-action-sets";
|
|
53
53
|
import { TwScopePostureMenu } from "./tw-scope-posture-menu";
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Toolbar-local alias for the markup-display union. L6d.N2 widens
|
|
57
|
+
* this to match the `MarkupDisplay` surface in
|
|
58
|
+
* `comment-decoration-model.ts`, which now accepts Word's 4-mode
|
|
59
|
+
* grammar plus the legacy triple.
|
|
60
|
+
*/
|
|
61
|
+
export type MarkupDisplayMode =
|
|
62
|
+
| "all-markup"
|
|
63
|
+
| "simple-markup"
|
|
64
|
+
| "no-markup"
|
|
65
|
+
| "original"
|
|
66
|
+
| "clean"
|
|
67
|
+
| "simple"
|
|
68
|
+
| "all";
|
|
56
69
|
|
|
57
70
|
export interface WorkflowWorkItemSnapshot {
|
|
58
71
|
workItemId: string;
|
|
@@ -4,15 +4,20 @@ import * as Tabs from "@radix-ui/react-tabs";
|
|
|
4
4
|
/**
|
|
5
5
|
* TwShellHeader — the top "app chrome" bar above the document canvas.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Designsystem §6.1 — three subregions on a single 48-px row:
|
|
8
8
|
* ┌─────────────────────────────────────────────────────────────────────┐
|
|
9
|
-
* │
|
|
9
|
+
* │ LEFT │ CENTER │ RIGHT │
|
|
10
|
+
* │ brand + slot │ mode tabs (always-on) │ icon actions + CTA │
|
|
10
11
|
* └─────────────────────────────────────────────────────────────────────┘
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Layout is CSS-grid `grid-cols-[1fr_auto_1fr]` so the center zone is
|
|
14
|
+
* always visually centred regardless of left / right slot width. The
|
|
15
|
+
* 4-mode switcher (edit / review / workflow / more) renders
|
|
16
|
+
* unconditionally — Lane 6b §6b.S1 flips it from opt-in to always-on.
|
|
17
|
+
*
|
|
18
|
+
* All colors, shadows, radius, and motion bind to the Lane 6a token
|
|
19
|
+
* substrate (`var(--color-*)` / `var(--shadow-*)` / `var(--radius-*)` /
|
|
20
|
+
* `var(--motion-*)`) — no hex literals or legacy Tailwind palette names.
|
|
16
21
|
*/
|
|
17
22
|
|
|
18
23
|
export type ShellHeaderMode = "edit" | "review" | "workflow" | "more";
|
|
@@ -40,6 +45,10 @@ export interface ShellHeaderIconAction {
|
|
|
40
45
|
|
|
41
46
|
export interface TwShellHeaderProps {
|
|
42
47
|
brand?: ReactNode;
|
|
48
|
+
/**
|
|
49
|
+
* Mode tab options. When omitted, a default 4-mode set is rendered so
|
|
50
|
+
* the center subregion always has tabs per designsystem §6.1.
|
|
51
|
+
*/
|
|
43
52
|
modes?: readonly ShellHeaderModeOption[];
|
|
44
53
|
activeMode?: ShellHeaderMode;
|
|
45
54
|
onModeChange?: (mode: ShellHeaderMode) => void;
|
|
@@ -50,23 +59,33 @@ export interface TwShellHeaderProps {
|
|
|
50
59
|
className?: string;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Default 4-mode set — designsystem §6.1. Exported so hosts can extend
|
|
64
|
+
* / relabel without reconstructing the whole list.
|
|
65
|
+
*/
|
|
66
|
+
export const DEFAULT_SHELL_HEADER_MODES: readonly ShellHeaderModeOption[] = [
|
|
67
|
+
{ id: "edit", label: "Edit" },
|
|
68
|
+
{ id: "review", label: "Review" },
|
|
69
|
+
{ id: "workflow", label: "Workflow" },
|
|
70
|
+
{ id: "more", label: "More" },
|
|
71
|
+
];
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
props.brand ||
|
|
59
|
-
(props.modes && props.modes.length > 0) ||
|
|
60
|
-
(props.iconActions && props.iconActions.length > 0) ||
|
|
61
|
-
props.primaryAction;
|
|
73
|
+
const focusRingClass =
|
|
74
|
+
"focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]";
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
export function TwShellHeader(props: TwShellHeaderProps): React.ReactElement {
|
|
77
|
+
const modes =
|
|
78
|
+
props.modes && props.modes.length > 0
|
|
79
|
+
? props.modes
|
|
80
|
+
: DEFAULT_SHELL_HEADER_MODES;
|
|
81
|
+
const activeMode: ShellHeaderMode = props.activeMode ?? modes[0]!.id;
|
|
66
82
|
|
|
67
83
|
const className = [
|
|
68
|
-
"
|
|
69
|
-
|
|
84
|
+
"grid h-12 shrink-0 grid-cols-[1fr_auto_1fr] items-center gap-2 px-4",
|
|
85
|
+
"bg-[var(--color-bg-chrome)]/92 backdrop-blur-sm",
|
|
86
|
+
props.isScrolled
|
|
87
|
+
? "border-b border-[var(--color-border-subtle)]"
|
|
88
|
+
: "border-b border-transparent",
|
|
70
89
|
"transition-colors duration-[var(--motion-fast)]",
|
|
71
90
|
props.className,
|
|
72
91
|
]
|
|
@@ -75,10 +94,15 @@ export function TwShellHeader(props: TwShellHeaderProps) {
|
|
|
75
94
|
|
|
76
95
|
return (
|
|
77
96
|
<header className={className} data-testid="tw-shell-header">
|
|
78
|
-
|
|
97
|
+
{/* LEFT: brand + host-supplied slot */}
|
|
98
|
+
<div
|
|
99
|
+
className="flex min-w-0 items-center gap-3"
|
|
100
|
+
data-region="left"
|
|
101
|
+
data-testid="tw-shell-header__region-left"
|
|
102
|
+
>
|
|
79
103
|
{props.brand ? (
|
|
80
104
|
<div
|
|
81
|
-
className="font-[family-name:var(--font-legal-serif)] text-[15px] font-semibold text-primary
|
|
105
|
+
className="truncate font-[family-name:var(--font-legal-serif)] text-[15px] font-semibold text-[var(--color-text-primary)]"
|
|
82
106
|
data-testid="tw-shell-header__brand"
|
|
83
107
|
>
|
|
84
108
|
{props.brand}
|
|
@@ -86,9 +110,14 @@ export function TwShellHeader(props: TwShellHeaderProps) {
|
|
|
86
110
|
) : null}
|
|
87
111
|
</div>
|
|
88
112
|
|
|
89
|
-
{
|
|
113
|
+
{/* CENTER: mode tabs (always on) */}
|
|
114
|
+
<div
|
|
115
|
+
className="flex items-center justify-center"
|
|
116
|
+
data-region="center"
|
|
117
|
+
data-testid="tw-shell-header__region-center"
|
|
118
|
+
>
|
|
90
119
|
<Tabs.Root
|
|
91
|
-
value={
|
|
120
|
+
value={activeMode}
|
|
92
121
|
onValueChange={(v: string) =>
|
|
93
122
|
props.onModeChange?.(v as ShellHeaderMode)
|
|
94
123
|
}
|
|
@@ -97,7 +126,7 @@ export function TwShellHeader(props: TwShellHeaderProps) {
|
|
|
97
126
|
aria-label="Workspace modes"
|
|
98
127
|
className="flex items-center gap-1"
|
|
99
128
|
>
|
|
100
|
-
{
|
|
129
|
+
{modes.map((mode) => (
|
|
101
130
|
<Tabs.Trigger
|
|
102
131
|
key={mode.id}
|
|
103
132
|
value={mode.id}
|
|
@@ -110,17 +139,26 @@ export function TwShellHeader(props: TwShellHeaderProps) {
|
|
|
110
139
|
))}
|
|
111
140
|
</Tabs.List>
|
|
112
141
|
</Tabs.Root>
|
|
113
|
-
|
|
114
|
-
<div aria-hidden="true" />
|
|
115
|
-
)}
|
|
142
|
+
</div>
|
|
116
143
|
|
|
117
|
-
|
|
144
|
+
{/* RIGHT: icon actions + primary CTA */}
|
|
145
|
+
<div
|
|
146
|
+
className="flex items-center justify-end gap-1"
|
|
147
|
+
data-region="right"
|
|
148
|
+
data-testid="tw-shell-header__region-right"
|
|
149
|
+
>
|
|
118
150
|
{props.iconActions?.map((action) => {
|
|
119
151
|
const commonProps = {
|
|
120
152
|
key: action.id,
|
|
121
153
|
"aria-label": action.label,
|
|
122
154
|
title: action.label,
|
|
123
|
-
className:
|
|
155
|
+
className: [
|
|
156
|
+
"inline-flex h-8 w-8 items-center justify-center rounded-[var(--radius-sm)]",
|
|
157
|
+
"text-[var(--color-text-secondary)]",
|
|
158
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
159
|
+
"hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-primary)]",
|
|
160
|
+
focusRingClass,
|
|
161
|
+
].join(" "),
|
|
124
162
|
} as const;
|
|
125
163
|
|
|
126
164
|
if (action.href) {
|
|
@@ -145,10 +183,13 @@ export function TwShellHeader(props: TwShellHeaderProps) {
|
|
|
145
183
|
onClick={props.primaryAction.onClick}
|
|
146
184
|
data-tone={props.primaryAction.tone ?? "accent"}
|
|
147
185
|
className={[
|
|
148
|
-
"ml-2 inline-flex h-8 items-center rounded-sm px-3
|
|
186
|
+
"ml-2 inline-flex h-8 items-center rounded-[var(--radius-sm)] px-3",
|
|
187
|
+
"text-xs font-semibold",
|
|
188
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
189
|
+
"disabled:opacity-40",
|
|
149
190
|
props.primaryAction.tone === "neutral"
|
|
150
|
-
? "bg-
|
|
151
|
-
: "bg-accent text-
|
|
191
|
+
? "bg-[var(--color-bg-muted)] text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)]"
|
|
192
|
+
: "bg-[var(--color-accent-primary)] text-[var(--color-text-on-accent)] hover:bg-[var(--color-accent-primary-hover)]",
|
|
152
193
|
focusRingClass,
|
|
153
194
|
].join(" ")}
|
|
154
195
|
data-testid="tw-shell-header__primary-action"
|
|
@@ -7,13 +7,31 @@ export interface TwToolbarIconButtonProps {
|
|
|
7
7
|
icon: React.ComponentType<{ className?: string }>;
|
|
8
8
|
label: string;
|
|
9
9
|
disabled?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Active / pressed state. Designsystem §6.2 + Lane 6b §6b.S2: active
|
|
12
|
+
* buttons paint an accent-soft tint with an accent border ring — NOT a
|
|
13
|
+
* filled accent CTA. The filled-CTA grammar is reserved for the shell
|
|
14
|
+
* header primary action only.
|
|
15
|
+
*/
|
|
10
16
|
active?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Emphasis variant — used for "stand-out" toolbar items (e.g. a pinned
|
|
19
|
+
* AI action). Paints the accent glyph color at rest; still tints on
|
|
20
|
+
* hover and on active.
|
|
21
|
+
*/
|
|
11
22
|
emphasis?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Lane 6b §6b.U6 — optional keyboard-shortcut hint rendered as a small
|
|
25
|
+
* `<kbd>` chip to the right of the label inside the tooltip. Use
|
|
26
|
+
* platform-agnostic symbols (⌘, ⇧, ⌥, ⌃) — callers format for macOS
|
|
27
|
+
* vs. Windows however they like.
|
|
28
|
+
*/
|
|
29
|
+
shortcut?: string;
|
|
12
30
|
onClick?: () => void;
|
|
13
31
|
}
|
|
14
32
|
|
|
15
33
|
const focusRingClass =
|
|
16
|
-
"focus-visible:outline-none focus-visible:
|
|
34
|
+
"focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]";
|
|
17
35
|
|
|
18
36
|
export function TwToolbarIconButton(props: TwToolbarIconButtonProps) {
|
|
19
37
|
return (
|
|
@@ -22,17 +40,22 @@ export function TwToolbarIconButton(props: TwToolbarIconButtonProps) {
|
|
|
22
40
|
<button
|
|
23
41
|
type="button"
|
|
24
42
|
aria-label={props.label}
|
|
43
|
+
aria-pressed={props.active ?? undefined}
|
|
44
|
+
data-active={props.active ? "true" : undefined}
|
|
25
45
|
disabled={props.disabled}
|
|
26
46
|
onMouseDown={preserveEditorSelectionMouseDown}
|
|
27
47
|
onClick={props.onClick}
|
|
28
48
|
className={[
|
|
29
|
-
"inline-flex h-6 w-6 items-center justify-center rounded-
|
|
49
|
+
"inline-flex h-6 w-6 items-center justify-center rounded-[var(--radius-sm)]",
|
|
50
|
+
"border border-transparent outline-none",
|
|
51
|
+
"transition-colors duration-[var(--motion-fast)]",
|
|
30
52
|
"disabled:opacity-30 disabled:cursor-not-allowed",
|
|
31
|
-
props.
|
|
32
|
-
?
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
props.active
|
|
54
|
+
? // Active = underline-tint grammar: accent-soft fill, accent-primary glyph, accent border ring.
|
|
55
|
+
"bg-[var(--color-accent-soft)] text-[var(--color-accent-primary)] border-[var(--color-border-accent)]"
|
|
56
|
+
: props.emphasis
|
|
57
|
+
? "text-[var(--color-accent-primary)] hover:bg-[var(--color-bg-hover)] hover:border-[var(--color-border-subtle)]"
|
|
58
|
+
: "text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-primary)] hover:border-[var(--color-border-subtle)]",
|
|
36
59
|
focusRingClass,
|
|
37
60
|
].join(" ")}
|
|
38
61
|
>
|
|
@@ -41,10 +64,27 @@ export function TwToolbarIconButton(props: TwToolbarIconButtonProps) {
|
|
|
41
64
|
</Tooltip.Trigger>
|
|
42
65
|
<Tooltip.Portal>
|
|
43
66
|
<Tooltip.Content
|
|
44
|
-
className=
|
|
67
|
+
className={[
|
|
68
|
+
"inline-flex items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1 text-xs z-50",
|
|
69
|
+
"bg-[var(--color-text-primary)] text-[var(--color-text-inverse)]",
|
|
70
|
+
"shadow-[var(--shadow-soft)]",
|
|
71
|
+
].join(" ")}
|
|
45
72
|
sideOffset={6}
|
|
46
73
|
>
|
|
47
|
-
{props.label}
|
|
74
|
+
<span>{props.label}</span>
|
|
75
|
+
{props.shortcut ? (
|
|
76
|
+
<kbd
|
|
77
|
+
className={[
|
|
78
|
+
"inline-flex items-center rounded-[var(--radius-sm)]",
|
|
79
|
+
"px-1 py-0.5 font-sans text-[10px] font-medium",
|
|
80
|
+
"border border-[var(--color-border-subtle)]/40",
|
|
81
|
+
"bg-[var(--color-bg-overlay)] text-[var(--color-text-inverse)]/80",
|
|
82
|
+
].join(" ")}
|
|
83
|
+
data-testid="tw-toolbar-icon-button__shortcut"
|
|
84
|
+
>
|
|
85
|
+
{props.shortcut}
|
|
86
|
+
</kbd>
|
|
87
|
+
) : null}
|
|
48
88
|
</Tooltip.Content>
|
|
49
89
|
</Tooltip.Portal>
|
|
50
90
|
</Tooltip.Root>
|