@cosxai/ui 0.4.6 → 0.4.8
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
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
|
-
import { useContext, useEffect
|
|
1
|
+
import { useContext, useEffect } from "react";
|
|
2
2
|
import { ActionBarContext } from "./actionbar-context";
|
|
3
3
|
import type { ActionBarItem } from "./types";
|
|
4
4
|
|
|
5
5
|
// Register a set of items into the global action-bar registry.
|
|
6
6
|
// Auto-unregisters on unmount + on sourceKey change.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
8
|
+
// **Pass a `useMemo`'d array.** The effect re-registers whenever
|
|
9
|
+
// the array identity changes; the provider's `register()` then
|
|
10
|
+
// dedups by item-by-item identity, so a fresh array with identical
|
|
11
|
+
// item refs is cheap (state stays === and no re-render fires) but
|
|
12
|
+
// a fresh array with NEW item objects (e.g. consumers whose
|
|
13
|
+
// onClick closures captured updated state) does re-register —
|
|
14
|
+
// downstream renders pick up the fresh handlers.
|
|
15
|
+
//
|
|
16
|
+
// Inline-array consumers (`useActionBarItems(key, [{...}])`)
|
|
17
|
+
// will allocate new items every render and loop infinitely
|
|
18
|
+
// through `register → setState → re-render → register`. The
|
|
19
|
+
// useMemo on the caller side is what keeps that loop bounded
|
|
20
|
+
// to "items whose deps actually changed".
|
|
21
|
+
//
|
|
22
|
+
// Previous versions wrapped items in a local `useMemo` keyed by
|
|
23
|
+
// `[items.length, items.map(i => i.key).join('|')]`. That gate
|
|
24
|
+
// suppressed identity updates whenever the keys + length stayed
|
|
25
|
+
// stable, which is precisely the case for a selection-mode
|
|
26
|
+
// toolbar whose buttons are fixed but whose onClicks close over
|
|
27
|
+
// changing state — they ended up registered ONCE with the first
|
|
28
|
+
// render's closures and never refreshed, shipping stale state to
|
|
29
|
+
// every subsequent click. The gate is gone; the provider's
|
|
30
|
+
// shallow item-identity dedup is the single source of truth.
|
|
13
31
|
//
|
|
14
32
|
// **Important**: the useEffect deps below intentionally do NOT
|
|
15
33
|
// include the ctx object itself — only its stable function refs
|
|
@@ -27,15 +45,10 @@ export function useActionBarItems(sourceKey: string, items: ActionBarItem[]) {
|
|
|
27
45
|
throw new Error("useActionBarItems must be used within <ActionBarProvider>");
|
|
28
46
|
}
|
|
29
47
|
const { register, unregister } = ctx;
|
|
30
|
-
const memo = useMemo(
|
|
31
|
-
() => items,
|
|
32
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
-
[items.length, items.map((i) => i.key).join("|")],
|
|
34
|
-
);
|
|
35
48
|
useEffect(() => {
|
|
36
|
-
register(sourceKey,
|
|
49
|
+
register(sourceKey, items);
|
|
37
50
|
return () => unregister(sourceKey);
|
|
38
|
-
}, [register, unregister, sourceKey,
|
|
51
|
+
}, [register, unregister, sourceKey, items]);
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
export function useActionBarItemsContext() {
|
package/src/dialogs/Modal.tsx
CHANGED
|
@@ -161,6 +161,14 @@ export function ModalHeader({ title, subtitle, onClose, titleId }: ModalHeaderPr
|
|
|
161
161
|
type="button"
|
|
162
162
|
onClick={onClose}
|
|
163
163
|
aria-label="Close"
|
|
164
|
+
onMouseEnter={(e) => {
|
|
165
|
+
(e.currentTarget as HTMLButtonElement).style.background =
|
|
166
|
+
"var(--ck-bg-muted)";
|
|
167
|
+
}}
|
|
168
|
+
onMouseLeave={(e) => {
|
|
169
|
+
(e.currentTarget as HTMLButtonElement).style.background =
|
|
170
|
+
"transparent";
|
|
171
|
+
}}
|
|
164
172
|
style={{
|
|
165
173
|
border: "none",
|
|
166
174
|
background: "transparent",
|
|
@@ -168,7 +176,10 @@ export function ModalHeader({ title, subtitle, onClose, titleId }: ModalHeaderPr
|
|
|
168
176
|
cursor: "pointer",
|
|
169
177
|
fontSize: 18,
|
|
170
178
|
lineHeight: 1,
|
|
171
|
-
padding:
|
|
179
|
+
padding: 4,
|
|
180
|
+
borderRadius: "var(--ck-radius-sm, 4px)",
|
|
181
|
+
transition:
|
|
182
|
+
"background var(--ck-dur-fast) var(--ck-ease)",
|
|
172
183
|
}}
|
|
173
184
|
>
|
|
174
185
|
×
|
|
@@ -116,6 +116,18 @@ html[data-ck-chrome="editorial"] [data-ck-leftnav] {
|
|
|
116
116
|
border-right-color: var(--ck-border-subtle);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/* Nav-item hover — match the actionbar / button hover token so
|
|
120
|
+
every interactive surface in the editorial chrome reads as
|
|
121
|
+
actionable on pointer enter. The sketch / ambient / riso / etc.
|
|
122
|
+
chromes have always carried this rule; editorial was the only
|
|
123
|
+
gap (consumers saw rail items as static even though they're
|
|
124
|
+
navigable). Active rows keep their accent-muted background from
|
|
125
|
+
the base NavItem inline style, so the `:not([data-active="true"])`
|
|
126
|
+
guard is the same shape the other chromes use. */
|
|
127
|
+
html[data-ck-chrome="editorial"] [data-ck-navitem]:not([data-active="true"]):hover {
|
|
128
|
+
background: var(--ck-bg-muted);
|
|
129
|
+
}
|
|
130
|
+
|
|
119
131
|
/* Topbar — flatten to the paper, lose the bottom border (the
|
|
120
132
|
folio rule under it is the visual divider in editorial). */
|
|
121
133
|
html[data-ck-chrome="editorial"] [data-ck-topbar] {
|