@casualoffice/sheets 0.9.0 → 0.10.0
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/dist/api-BI2VLYQ6.d.cts +62 -0
- package/dist/api-BI2VLYQ6.d.ts +62 -0
- package/dist/chrome.cjs +2569 -0
- package/dist/chrome.cjs.map +1 -0
- package/dist/chrome.d.cts +96 -0
- package/dist/chrome.d.ts +96 -0
- package/dist/chrome.js +2556 -0
- package/dist/chrome.js.map +1 -0
- package/dist/collab.cjs +770 -0
- package/dist/collab.cjs.map +1 -0
- package/dist/collab.d.cts +248 -0
- package/dist/collab.d.ts +248 -0
- package/dist/collab.js +737 -0
- package/dist/collab.js.map +1 -0
- package/dist/embed/embed-runtime.js +128 -128
- package/dist/embed.cjs +166 -0
- package/dist/embed.cjs.map +1 -1
- package/dist/embed.d.cts +78 -3
- package/dist/embed.d.ts +78 -3
- package/dist/embed.js +166 -0
- package/dist/embed.js.map +1 -1
- package/dist/index.cjs +262 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +271 -168
- package/dist/index.js.map +1 -1
- package/dist/{protocol--KyBQUjU.d.cts → protocol-Cq4Cdoyi.d.cts} +19 -2
- package/dist/{protocol-cEzy7S0i.d.ts → protocol-DqDaG2yG.d.ts} +19 -2
- package/dist/sheets.cjs +102 -16
- package/dist/sheets.cjs.map +1 -1
- package/dist/sheets.d.cts +49 -63
- package/dist/sheets.d.ts +49 -63
- package/dist/sheets.js +111 -19
- package/dist/sheets.js.map +1 -1
- package/package.json +28 -3
- package/src/chrome/AutoSumPicker.tsx +176 -0
- package/src/chrome/BordersPicker.tsx +171 -0
- package/src/chrome/ChromeBottom.tsx +18 -0
- package/src/chrome/ChromeTop.tsx +21 -0
- package/src/chrome/ColorPicker.tsx +220 -0
- package/src/chrome/FindReplace.tsx +370 -0
- package/src/chrome/FormulaBar.tsx +378 -0
- package/src/chrome/Icon.tsx +43 -0
- package/src/chrome/MenuBar.tsx +336 -0
- package/src/chrome/NameBox.tsx +347 -0
- package/src/chrome/SheetTabs.tsx +346 -0
- package/src/chrome/StatusBar.tsx +232 -0
- package/src/chrome/Toolbar.tsx +401 -0
- package/src/chrome/fonts.ts +42 -0
- package/src/chrome/index.ts +24 -0
- package/src/collab/attachCollab.ts +151 -0
- package/src/collab/bridge-helpers.ts +97 -0
- package/src/collab/bridge.ts +885 -0
- package/src/collab/bridge.unit.test.ts +160 -0
- package/src/collab/index.ts +38 -0
- package/src/collab/replay-retry.ts +137 -0
- package/src/collab/replay-retry.unit.test.ts +223 -0
- package/src/collab/ws-url.ts +20 -0
- package/src/collab/ws-url.unit.test.ts +35 -0
- package/src/embed/EmbedHostTransport.ts +16 -1
- package/src/embed/EmbedTransport.ts +16 -0
- package/src/embed/EmbedTransport.unit.test.ts +88 -2
- package/src/embed/index.ts +7 -0
- package/src/embed/protocol.ts +34 -0
- package/src/embed-runtime/index.tsx +20 -0
- package/src/sheets/CasualSheets.tsx +204 -33
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BordersPicker — border-position dropdown for `<CasualSheets chrome>`.
|
|
3
|
+
*
|
|
4
|
+
* One toolbar button that opens a popover of Excel-style border options (All,
|
|
5
|
+
* Outside, Inside, Top, Bottom, Left, Right, None). Picking one dispatches
|
|
6
|
+
* `sheet.command.set-border-position` with the matching `BorderType` against the
|
|
7
|
+
* active selection, using Univer's current border style/colour (thin black by
|
|
8
|
+
* default). Drives the editor purely through `CasualSheetsAPI.executeCommand`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useRef, useState, type CSSProperties } from 'react';
|
|
12
|
+
import type { CasualSheetsAPI } from '../sheets/api';
|
|
13
|
+
import { Icon } from './Icon';
|
|
14
|
+
|
|
15
|
+
// BorderType is a string enum in @univerjs/core ('all' | 'outside' | …); use the
|
|
16
|
+
// literal values directly so this component needs no Univer value import.
|
|
17
|
+
const OPTIONS: { value: string; icon: string; label: string }[] = [
|
|
18
|
+
{ value: 'all', icon: 'border_all', label: 'All borders' },
|
|
19
|
+
{ value: 'outside', icon: 'border_outer', label: 'Outside borders' },
|
|
20
|
+
{ value: 'inside', icon: 'border_inner', label: 'Inside borders' },
|
|
21
|
+
{ value: 'top', icon: 'border_top', label: 'Top border' },
|
|
22
|
+
{ value: 'bottom', icon: 'border_bottom', label: 'Bottom border' },
|
|
23
|
+
{ value: 'left', icon: 'border_left', label: 'Left border' },
|
|
24
|
+
{ value: 'right', icon: 'border_right', label: 'Right border' },
|
|
25
|
+
{ value: 'none', icon: 'border_clear', label: 'No border' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const ROW_STYLE: CSSProperties = {
|
|
29
|
+
display: 'inline-flex',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
position: 'relative',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const BTN_STYLE: CSSProperties = {
|
|
35
|
+
width: 30,
|
|
36
|
+
height: 30,
|
|
37
|
+
display: 'inline-flex',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
justifyContent: 'center',
|
|
40
|
+
border: 'none',
|
|
41
|
+
borderRadius: 6,
|
|
42
|
+
background: 'transparent',
|
|
43
|
+
cursor: 'pointer',
|
|
44
|
+
color: 'var(--cs-chrome-fg, #201f1e)',
|
|
45
|
+
padding: 0,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const POPOVER_STYLE: CSSProperties = {
|
|
49
|
+
position: 'absolute',
|
|
50
|
+
top: '100%',
|
|
51
|
+
left: 0,
|
|
52
|
+
marginTop: 4,
|
|
53
|
+
zIndex: 1000,
|
|
54
|
+
padding: 4,
|
|
55
|
+
minWidth: 168,
|
|
56
|
+
borderRadius: 8,
|
|
57
|
+
border: '1px solid var(--cs-chrome-border, #e6e9ee)',
|
|
58
|
+
background: 'var(--cs-chrome-input-bg, #fff)',
|
|
59
|
+
boxShadow: '0 6px 20px rgba(0,0,0,0.18)',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const ITEM_STYLE: CSSProperties = {
|
|
63
|
+
display: 'flex',
|
|
64
|
+
alignItems: 'center',
|
|
65
|
+
gap: 8,
|
|
66
|
+
width: '100%',
|
|
67
|
+
height: 28,
|
|
68
|
+
padding: '0 8px',
|
|
69
|
+
border: 'none',
|
|
70
|
+
borderRadius: 6,
|
|
71
|
+
background: 'transparent',
|
|
72
|
+
color: 'var(--cs-chrome-fg, #201f1e)',
|
|
73
|
+
font: 'inherit',
|
|
74
|
+
fontSize: 13,
|
|
75
|
+
cursor: 'pointer',
|
|
76
|
+
textAlign: 'left',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export interface BordersPickerProps {
|
|
80
|
+
/** Live API, or `null` until the editor is ready. */
|
|
81
|
+
api: CasualSheetsAPI | null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function BordersPicker({ api }: BordersPickerProps) {
|
|
85
|
+
const [open, setOpen] = useState(false);
|
|
86
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
87
|
+
|
|
88
|
+
// Close on Escape and on any pointerdown outside the picker root.
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!open) return;
|
|
91
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
92
|
+
if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);
|
|
93
|
+
};
|
|
94
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
95
|
+
if (e.key === 'Escape') setOpen(false);
|
|
96
|
+
};
|
|
97
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
98
|
+
document.addEventListener('keydown', onKeyDown);
|
|
99
|
+
return () => {
|
|
100
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
101
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
102
|
+
};
|
|
103
|
+
}, [open]);
|
|
104
|
+
|
|
105
|
+
const pick = (value: string) => {
|
|
106
|
+
void api?.executeCommand('sheet.command.set-border-position', { value });
|
|
107
|
+
setOpen(false);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const on = open;
|
|
111
|
+
const baseBg = on ? 'var(--cs-chrome-active, #e6f3f7)' : 'transparent';
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div ref={rootRef} style={ROW_STYLE} data-testid="cs-borders">
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
title="Borders"
|
|
118
|
+
aria-label="Borders"
|
|
119
|
+
aria-haspopup="true"
|
|
120
|
+
aria-expanded={on}
|
|
121
|
+
data-testid="cs-borders-button"
|
|
122
|
+
disabled={!api}
|
|
123
|
+
style={{
|
|
124
|
+
...BTN_STYLE,
|
|
125
|
+
background: baseBg,
|
|
126
|
+
color: on ? 'var(--cs-chrome-active-fg, #0e7490)' : BTN_STYLE.color,
|
|
127
|
+
}}
|
|
128
|
+
// mousedown (not click) so the grid's selection isn't lost first.
|
|
129
|
+
onMouseDown={(e) => {
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
setOpen((v) => !v);
|
|
132
|
+
}}
|
|
133
|
+
onMouseEnter={(e) => {
|
|
134
|
+
if (!on) e.currentTarget.style.background = 'var(--cs-chrome-hover, rgba(0,0,0,0.06))';
|
|
135
|
+
}}
|
|
136
|
+
onMouseLeave={(e) => {
|
|
137
|
+
e.currentTarget.style.background = baseBg;
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
<Icon name="border_all" size={20} />
|
|
141
|
+
</button>
|
|
142
|
+
{open && (
|
|
143
|
+
<div style={POPOVER_STYLE} data-testid="cs-borders-popover" role="menu">
|
|
144
|
+
{OPTIONS.map((o) => (
|
|
145
|
+
<button
|
|
146
|
+
key={o.value}
|
|
147
|
+
type="button"
|
|
148
|
+
role="menuitem"
|
|
149
|
+
data-border={o.value}
|
|
150
|
+
data-testid={`cs-border-${o.value}`}
|
|
151
|
+
style={ITEM_STYLE}
|
|
152
|
+
onMouseEnter={(e) => {
|
|
153
|
+
e.currentTarget.style.background = 'var(--cs-chrome-hover, rgba(0,0,0,0.06))';
|
|
154
|
+
}}
|
|
155
|
+
onMouseLeave={(e) => {
|
|
156
|
+
e.currentTarget.style.background = 'transparent';
|
|
157
|
+
}}
|
|
158
|
+
onMouseDown={(e) => {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
pick(o.value);
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
<Icon name={o.icon} size={18} />
|
|
164
|
+
{o.label}
|
|
165
|
+
</button>
|
|
166
|
+
))}
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChromeBottom — the chrome BELOW the grid (sheet tabs + status bar) plus the
|
|
3
|
+
* Find & Replace overlay. Lazy-imported with ChromeTop (see ChromeTop).
|
|
4
|
+
*/
|
|
5
|
+
import { SheetTabs } from './SheetTabs';
|
|
6
|
+
import { StatusBar } from './StatusBar';
|
|
7
|
+
import { FindReplace } from './FindReplace';
|
|
8
|
+
import type { CasualSheetsAPI } from '../sheets/api';
|
|
9
|
+
|
|
10
|
+
export function ChromeBottom({ api }: { api: CasualSheetsAPI | null }) {
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<SheetTabs api={api} />
|
|
14
|
+
<StatusBar api={api} />
|
|
15
|
+
<FindReplace api={api} />
|
|
16
|
+
</>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChromeTop — the chrome ABOVE the grid (menu bar + toolbar + formula bar).
|
|
3
|
+
* Lives in the `@casualoffice/sheets/chrome` entry, lazy-imported by
|
|
4
|
+
* `<CasualSheets>` only when `chrome !== 'none'` so bare-grid consumers (the
|
|
5
|
+
* default; the apps/web host renders chrome="none" + its own shell) never bundle
|
|
6
|
+
* the chrome JS.
|
|
7
|
+
*/
|
|
8
|
+
import { MenuBar } from './MenuBar';
|
|
9
|
+
import { Toolbar } from './Toolbar';
|
|
10
|
+
import { FormulaBar } from './FormulaBar';
|
|
11
|
+
import type { CasualSheetsAPI } from '../sheets/api';
|
|
12
|
+
|
|
13
|
+
export function ChromeTop({ api }: { api: CasualSheetsAPI | null }) {
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<MenuBar api={api} />
|
|
17
|
+
<Toolbar api={api} />
|
|
18
|
+
<FormulaBar api={api} />
|
|
19
|
+
</>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColorPicker — text-colour + fill-colour swatch pickers for `<CasualSheets chrome>`.
|
|
3
|
+
*
|
|
4
|
+
* Two icon buttons (text colour, fill colour). Clicking one toggles a swatch
|
|
5
|
+
* popover; picking a swatch dispatches the matching Univer command on the active
|
|
6
|
+
* selection and closes the popover. A "None" entry resets the colour.
|
|
7
|
+
*
|
|
8
|
+
* Drives the editor purely through `CasualSheetsAPI.executeCommand`. Commands:
|
|
9
|
+
* text colour : `sheet.command.set-range-text-color` { value: '#rrggbb' | null }
|
|
10
|
+
* fill colour : `sheet.command.set-background-color` { value: '#rrggbb' }
|
|
11
|
+
* fill reset : `sheet.command.reset-background-color` (no params)
|
|
12
|
+
* (`set-background-color` rejects a null value, so reset uses its own command.)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useEffect, useRef, useState, type CSSProperties } from 'react';
|
|
16
|
+
import type { CasualSheetsAPI } from '../sheets/api';
|
|
17
|
+
import { Icon } from './Icon';
|
|
18
|
+
|
|
19
|
+
type Kind = 'text' | 'fill';
|
|
20
|
+
|
|
21
|
+
// A tasteful 14-swatch palette: neutrals + a spread of hues, teal = brand.
|
|
22
|
+
const SWATCHES: { hex: string; label: string }[] = [
|
|
23
|
+
{ hex: '#000000', label: 'Black' },
|
|
24
|
+
{ hex: '#5f6368', label: 'Dark gray' },
|
|
25
|
+
{ hex: '#9aa0a6', label: 'Gray' },
|
|
26
|
+
{ hex: '#d9dce0', label: 'Light gray' },
|
|
27
|
+
{ hex: '#ffffff', label: 'White' },
|
|
28
|
+
{ hex: '#d13438', label: 'Red' },
|
|
29
|
+
{ hex: '#e8710a', label: 'Orange' },
|
|
30
|
+
{ hex: '#f2c811', label: 'Yellow' },
|
|
31
|
+
{ hex: '#107c10', label: 'Green' },
|
|
32
|
+
{ hex: '#0e7490', label: 'Teal' },
|
|
33
|
+
{ hex: '#1a73e8', label: 'Blue' },
|
|
34
|
+
{ hex: '#5b2a86', label: 'Purple' },
|
|
35
|
+
{ hex: '#c2185b', label: 'Magenta' },
|
|
36
|
+
{ hex: '#795548', label: 'Brown' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const ROW_STYLE: CSSProperties = {
|
|
40
|
+
display: 'inline-flex',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
position: 'relative',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const BTN_STYLE: CSSProperties = {
|
|
46
|
+
width: 30,
|
|
47
|
+
height: 30,
|
|
48
|
+
display: 'inline-flex',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
border: 'none',
|
|
52
|
+
borderRadius: 6,
|
|
53
|
+
background: 'transparent',
|
|
54
|
+
cursor: 'pointer',
|
|
55
|
+
color: 'var(--cs-chrome-fg, #201f1e)',
|
|
56
|
+
padding: 0,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const POPOVER_STYLE: CSSProperties = {
|
|
60
|
+
position: 'absolute',
|
|
61
|
+
top: '100%',
|
|
62
|
+
left: 0,
|
|
63
|
+
marginTop: 4,
|
|
64
|
+
zIndex: 1000,
|
|
65
|
+
padding: 8,
|
|
66
|
+
borderRadius: 8,
|
|
67
|
+
border: '1px solid var(--cs-chrome-border, #e6e9ee)',
|
|
68
|
+
background: 'var(--cs-chrome-input-bg, #fff)',
|
|
69
|
+
boxShadow: '0 6px 20px rgba(0,0,0,0.18)',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const GRID_STYLE: CSSProperties = {
|
|
73
|
+
display: 'grid',
|
|
74
|
+
gridTemplateColumns: 'repeat(7, 22px)',
|
|
75
|
+
gap: 6,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const SWATCH_STYLE: CSSProperties = {
|
|
79
|
+
width: 22,
|
|
80
|
+
height: 22,
|
|
81
|
+
borderRadius: 4,
|
|
82
|
+
border: '1px solid var(--cs-chrome-border, rgba(0,0,0,0.18))',
|
|
83
|
+
cursor: 'pointer',
|
|
84
|
+
padding: 0,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const NONE_STYLE: CSSProperties = {
|
|
88
|
+
marginTop: 8,
|
|
89
|
+
width: '100%',
|
|
90
|
+
height: 26,
|
|
91
|
+
display: 'inline-flex',
|
|
92
|
+
alignItems: 'center',
|
|
93
|
+
justifyContent: 'center',
|
|
94
|
+
gap: 4,
|
|
95
|
+
border: '1px solid var(--cs-chrome-border, rgba(0,0,0,0.18))',
|
|
96
|
+
borderRadius: 6,
|
|
97
|
+
background: 'var(--cs-chrome-input-bg, #fff)',
|
|
98
|
+
color: 'var(--cs-chrome-fg, #201f1e)',
|
|
99
|
+
font: 'inherit',
|
|
100
|
+
fontSize: 12,
|
|
101
|
+
cursor: 'pointer',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export interface ColorPickerProps {
|
|
105
|
+
/** Live API, or `null` until the editor is ready. */
|
|
106
|
+
api: CasualSheetsAPI | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function ColorPicker({ api }: ColorPickerProps) {
|
|
110
|
+
const [open, setOpen] = useState<Kind | null>(null);
|
|
111
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
112
|
+
|
|
113
|
+
// Close on Escape and on any pointerdown outside the picker root.
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!open) return;
|
|
116
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
117
|
+
if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(null);
|
|
118
|
+
};
|
|
119
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
120
|
+
if (e.key === 'Escape') setOpen(null);
|
|
121
|
+
};
|
|
122
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
123
|
+
document.addEventListener('keydown', onKeyDown);
|
|
124
|
+
return () => {
|
|
125
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
126
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
127
|
+
};
|
|
128
|
+
}, [open]);
|
|
129
|
+
|
|
130
|
+
const pick = (kind: Kind, hex: string | null) => {
|
|
131
|
+
if (api) {
|
|
132
|
+
if (kind === 'text') {
|
|
133
|
+
void api.executeCommand('sheet.command.set-range-text-color', { value: hex });
|
|
134
|
+
} else if (hex === null) {
|
|
135
|
+
void api.executeCommand('sheet.command.reset-background-color');
|
|
136
|
+
} else {
|
|
137
|
+
void api.executeCommand('sheet.command.set-background-color', { value: hex });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
setOpen(null);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const toggle = (kind: Kind) => setOpen((cur) => (cur === kind ? null : kind));
|
|
144
|
+
|
|
145
|
+
const renderButton = (kind: Kind, icon: string, label: string, testid: string) => {
|
|
146
|
+
const on = open === kind;
|
|
147
|
+
const baseBg = on ? 'var(--cs-chrome-active, #e6f3f7)' : 'transparent';
|
|
148
|
+
return (
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
title={label}
|
|
152
|
+
aria-label={label}
|
|
153
|
+
aria-haspopup="true"
|
|
154
|
+
aria-expanded={on}
|
|
155
|
+
data-testid={testid}
|
|
156
|
+
style={{
|
|
157
|
+
...BTN_STYLE,
|
|
158
|
+
background: baseBg,
|
|
159
|
+
color: on ? 'var(--cs-chrome-active-fg, #0e7490)' : BTN_STYLE.color,
|
|
160
|
+
}}
|
|
161
|
+
// mousedown (not click) so the grid's selection isn't lost first.
|
|
162
|
+
onMouseDown={(e) => {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
toggle(kind);
|
|
165
|
+
}}
|
|
166
|
+
onMouseEnter={(e) => {
|
|
167
|
+
if (!on) e.currentTarget.style.background = 'var(--cs-chrome-hover, rgba(0,0,0,0.06))';
|
|
168
|
+
}}
|
|
169
|
+
onMouseLeave={(e) => {
|
|
170
|
+
e.currentTarget.style.background = baseBg;
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
<Icon name={icon} size={20} />
|
|
174
|
+
</button>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const renderPopover = (kind: Kind) => (
|
|
179
|
+
<div style={POPOVER_STYLE} data-testid="cs-color-popover" role="menu">
|
|
180
|
+
<div style={GRID_STYLE}>
|
|
181
|
+
{SWATCHES.map((s) => (
|
|
182
|
+
<button
|
|
183
|
+
key={s.hex}
|
|
184
|
+
type="button"
|
|
185
|
+
role="menuitem"
|
|
186
|
+
title={s.label}
|
|
187
|
+
aria-label={s.label}
|
|
188
|
+
data-color={s.hex}
|
|
189
|
+
style={{ ...SWATCH_STYLE, background: s.hex }}
|
|
190
|
+
onMouseDown={(e) => {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
pick(kind, s.hex);
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
role="menuitem"
|
|
200
|
+
data-color="none"
|
|
201
|
+
style={NONE_STYLE}
|
|
202
|
+
onMouseDown={(e) => {
|
|
203
|
+
e.preventDefault();
|
|
204
|
+
pick(kind, null);
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
<Icon name="format_color_reset" size={16} />
|
|
208
|
+
None
|
|
209
|
+
</button>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div ref={rootRef} style={ROW_STYLE} data-testid="cs-color-picker">
|
|
215
|
+
{renderButton('text', 'format_color_text', 'Text color', 'cs-color-text')}
|
|
216
|
+
{renderButton('fill', 'format_color_fill', 'Fill color', 'cs-color-fill')}
|
|
217
|
+
{open && renderPopover(open)}
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|