@apliteni/apliteni-ui 0.54.0 → 0.56.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.
@@ -20,7 +20,7 @@
20
20
 
21
21
  **Why:** A confirm keeps keyboard focus until answered; a callout does not interrupt.
22
22
 
23
- **Except:** Use a drawer when the answer needs a form instead of two buttons.
23
+ **Except:** Use a content-sized modal for a short form and a drawer for a long form.
24
24
 
25
25
  **Do:** Ask “Rotate this token?” in a confirm with “Keep token” and “Rotate token” buttons.
26
26
 
@@ -26,15 +26,15 @@
26
26
 
27
27
  **Don't:** Put each group in a card inside the drawer.
28
28
 
29
- ## Three divider lines
29
+ ## Header and group dividers
30
30
 
31
31
  <!-- rule: three-lines -->
32
32
 
33
- **Rule:** Draw lines under the header, over the footer, and between each group; draw no other lines.
33
+ **Rule:** Add lines below the header and between groups. Leave the footer unfilled and without a rule.
34
34
 
35
- **Why:** These lines mark scrolling boundaries and separate groups, not rows.
35
+ **Why:** The header line marks the scrolling body; group lines separate content, and actions share the panel surface.
36
36
 
37
- **Do:** Separate header, footer and groups.
37
+ **Do:** Separate the header and groups.
38
38
 
39
39
  **Don't:** Add dividers between every row.
40
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -385,8 +385,20 @@ uses its normal opening focus.
385
385
 
386
386
  `Timeline` displays a record’s events in the order provided. Pass events from oldest
387
387
  to newest, with stable IDs, machine-readable `dateTime` values, and formatted
388
- `timestamp` text.
389
- Descriptions should say what changed and to where.
388
+ `timestamp` text. Place Timeline on a page or card reading surface, never a grey
389
+ fill; the standalone stories compose it inside `Card`.
390
+ Descriptions should say what changed and to where. Optional `kind` accepts
391
+ `person`, `rule`, or `reversal` (`TimelineEventKind`); these show the kit’s user,
392
+ bolt, or refresh glyph in a 20px ring. The last event is newest and its ring is
393
+ filled, using the danger colour for a reversal. Events without a kind keep their
394
+ dot. Mixed histories mute older dots; older reversal glyphs keep danger ink.
395
+
396
+ Initial history stays still, including history loaded into an empty list.
397
+ New IDs added to a non-empty history enter with the kit’s slide-up motion (400ms)
398
+ and marker scale-in (250ms, delayed 60ms). Existing IDs stay still when reordered
399
+ or edited. Reduced motion disables the entrance. Optional `relativeTimestamp`
400
+ (such as "just now") appears beside the absolute stamp only on the newest row,
401
+ in accent ink. The caller updates this text; Timeline does not run a clock.
390
402
 
391
403
  ```tsx
392
404
  <Timeline aria-label="Record history" events={[
@@ -400,11 +412,9 @@ Descriptions should say what changed and to where.
400
412
 
401
413
  Omit `undo` for read-only events. For a reversible batch, pass
402
414
  `undo: { label: 'Undo batch DEMO-12', onUndo }`. The app checks permissions,
403
- asks for confirmation when the batch affects finalized records, reverses the
404
- entire batch, and adds a new event. Timeline only calls `onUndo`; it does not
415
+ reverses the entire batch, and adds a new event without a confirmation dialog. Timeline only calls `onUndo`; it does not
405
416
  modify history.
406
- Import both the kit CSS and React CSS. The Privileged story shows confirmation
407
- and a reversing event.
417
+ Import both the kit CSS and React CSS. The Privileged story shows Undo adding a reversing event.
408
418
 
409
419
  ## Fields
410
420
 
@@ -30,8 +30,7 @@
30
30
  .rx-scrim {
31
31
  position: fixed;
32
32
  inset: 0;
33
- background: rgba(0, 0, 0, .55);
34
- backdrop-filter: blur(2px);
33
+ background: color-mix(in srgb, var(--shadow-ink) 82%, transparent);
35
34
  display: grid;
36
35
  place-items: center;
37
36
  z-index: calc(var(--z-overlay) + 1);
@@ -42,14 +41,18 @@
42
41
  opacity: 1;
43
42
  }
44
43
  .rx-modal {
45
- width: min(480px, 92vw);
44
+ width: min(calc(var(--panel-lg) - var(--space-10) * 2), 96vw);
45
+ max-height: 96vh;
46
+ max-height: 96dvh;
47
+ display: flex;
48
+ flex-direction: column;
46
49
  background: var(--bg-elevated);
47
50
  --ring-gap: var(--bg-elevated);
48
- border: 1px solid var(--border-strong);
49
- box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop);
50
- border-radius: var(--radius-xl);
51
+ border: 0;
52
+ box-shadow: var(--elev-drop);
53
+ border-radius: var(--radius-md);
51
54
  overflow: hidden;
52
- transform: translateY(8px);
55
+ transform: translateY(var(--space-2));
53
56
  transition: transform var(--dur-med) var(--ease);
54
57
  }
55
58
  .rx-scrim.is-open .rx-modal {
@@ -59,25 +62,38 @@
59
62
  display: flex;
60
63
  align-items: center;
61
64
  justify-content: space-between;
62
- padding: 16px 20px;
65
+ gap: var(--space-3);
66
+ flex: none;
67
+ padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
63
68
  border-bottom: 1px solid var(--border);
64
69
  }
65
70
  .rx-modal__title {
66
- font-weight: 600;
67
- font-size: var(--text-lg);
71
+ font-weight: var(--weight-normal);
72
+ font-size: var(--text-base);
73
+ color: var(--text);
68
74
  }
69
75
  .rx-modal__body {
70
- padding: 20px;
76
+ padding: var(--space-4);
71
77
  display: flex;
72
78
  flex-direction: column;
73
- gap: 16px;
79
+ gap: var(--space-4);
80
+ min-height: 0;
81
+ overflow: auto;
82
+ }
83
+ .rx-modal__body > * {
84
+ flex-shrink: 0;
74
85
  }
75
86
  .rx-modal__foot {
76
87
  display: flex;
77
88
  justify-content: flex-end;
78
- gap: 12px;
79
- padding: 16px 20px;
80
- border-top: 1px solid var(--border);
89
+ gap: var(--space-3);
90
+ flex: none;
91
+ padding: var(--space-4);
92
+ }
93
+ .rx-modal__head > .ui-btn {
94
+ width: calc(var(--space-10) + var(--space-1));
95
+ height: calc(var(--space-10) + var(--space-1));
96
+ flex: none;
81
97
  }
82
98
 
83
99
  /* src/DataTable.css */
@@ -173,8 +189,7 @@
173
189
  .ui-timeline__event:last-child::before {
174
190
  display: none;
175
191
  }
176
- .ui-timeline__event::after {
177
- content: "";
192
+ .ui-timeline__marker {
178
193
  position: absolute;
179
194
  inset-inline-start: 0;
180
195
  inset-block-start: calc(0.75em - var(--space-2) / 2);
@@ -198,6 +213,10 @@
198
213
  .ui-timeline__meta {
199
214
  font-size: var(--text-xs);
200
215
  }
216
+ .ui-timeline__relative {
217
+ color: var(--accent);
218
+ font-size: var(--text-xs);
219
+ }
201
220
  .ui-timeline__description {
202
221
  margin: var(--space-1) 0;
203
222
  }
@@ -208,6 +227,55 @@
208
227
  .ui-timeline__undo:focus-visible {
209
228
  color: var(--pink);
210
229
  }
230
+ .ui-timeline--kinds .ui-timeline__event {
231
+ padding-inline-start: var(--space-8);
232
+ }
233
+ .ui-timeline--kinds .ui-timeline__event::before {
234
+ inset-inline-start: calc(var(--space-5) / 2);
235
+ }
236
+ .ui-timeline--kinds .ui-timeline__marker {
237
+ inset-inline-start: calc((var(--space-5) - var(--space-2)) / 2);
238
+ background: var(--muted);
239
+ }
240
+ .ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker {
241
+ background: var(--accent);
242
+ }
243
+ .ui-timeline--kinds .ui-timeline__marker--kind {
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: center;
247
+ inset-inline-start: 0;
248
+ inset-block-start: calc(0.75em - var(--space-5) / 2);
249
+ width: var(--space-5);
250
+ height: var(--space-5);
251
+ border: 1px solid var(--muted);
252
+ background: var(--surface);
253
+ color: var(--muted);
254
+ }
255
+ .ui-timeline--kinds .ui-timeline__marker--reversal {
256
+ color: var(--pink);
257
+ }
258
+ .ui-timeline__marker--kind svg {
259
+ width: var(--space-3);
260
+ height: var(--space-3);
261
+ }
262
+ .ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker--kind {
263
+ border-color: var(--accent-strong);
264
+ background: var(--accent-strong);
265
+ color: var(--accent-contrast);
266
+ }
267
+ .ui-timeline--kinds .ui-timeline__event:last-child .ui-timeline__marker--reversal {
268
+ border-color: var(--pink);
269
+ background: var(--pink);
270
+ color: var(--danger-contrast);
271
+ }
272
+ .ui-timeline__event.is-entering {
273
+ --m-dist: var(--space-2);
274
+ animation: m-slide-up var(--dur-slow) var(--ease-out) both;
275
+ }
276
+ .ui-timeline__marker.is-entering {
277
+ animation: m-scale-in var(--dur-med) var(--ease-out) var(--delay-1) both;
278
+ }
211
279
 
212
280
  /* ../src/styles/input.css */
213
281
  .ui-field {
@@ -498,11 +498,14 @@ type TabsProps = {
498
498
  };
499
499
  declare function Tabs({ items, value, onChange, label }: TabsProps): react.JSX.Element;
500
500
 
501
+ type TimelineEventKind = 'person' | 'rule' | 'reversal';
501
502
  type TimelineEvent = {
502
503
  id: string;
504
+ kind?: TimelineEventKind;
503
505
  actor: string;
504
506
  dateTime: string;
505
507
  timestamp: string;
508
+ relativeTimestamp?: string;
506
509
  description: string;
507
510
  meta?: ReactNode;
508
511
  undo?: {
@@ -608,4 +611,4 @@ declare function ThemeToggle({ labelled }: ThemeToggleProps): react.JSX.Element;
608
611
  declare const PAGE_SIZES: readonly number[];
609
612
  declare const DEFAULT_PAGE_SIZE: number;
610
613
 
611
- export { AppShell, type AppShellProps, type AppShellSection, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineProps, Toast, type ToastNotice, type ToastProps, sortTableRows, useToast };
614
+ export { AppShell, type AppShellProps, type AppShellSection, BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Callout, type CalloutProps, type CalloutVariant, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, EmptyState, type EmptyStateProps, type EmptyStateVariant, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, THEME_INIT_SCRIPT, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, ThemeToggle, type ThemeToggleProps, Timeline, type TimelineEvent, type TimelineEventKind, type TimelineProps, Toast, type ToastNotice, type ToastProps, sortTableRows, useToast };
@@ -1890,23 +1890,57 @@ function Tabs({ items, value, onChange, label }) {
1890
1890
  }
1891
1891
 
1892
1892
  // src/Timeline.tsx
1893
+ import { useEffect as useEffect7, useRef as useRef12 } from "react";
1894
+ import { playEntrance as playEntrance2, prefersReducedMotion } from "@apliteni/apliteni-ui/motion";
1893
1895
  import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
1896
+ var kindIcons = { person: "user", rule: "bolt", reversal: "refresh" };
1894
1897
  function Timeline({ events, className, ...rest }) {
1895
- return /* @__PURE__ */ jsx22("ol", { ...rest, role: "list", className: ["ui-timeline", className].filter(Boolean).join(" "), children: events.map((event) => /* @__PURE__ */ jsxs18("li", { className: "ui-timeline__event", children: [
1896
- /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__head", children: [
1897
- /* @__PURE__ */ jsx22("span", { className: "ui-timeline__actor", children: event.actor }),
1898
- /* @__PURE__ */ jsx22("time", { dateTime: event.dateTime, children: event.timestamp })
1899
- ] }),
1900
- /* @__PURE__ */ jsx22("p", { className: "ui-timeline__description", children: event.description }),
1901
- (event.meta != null || event.undo) && /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__meta", children: [
1902
- event.meta != null && /* @__PURE__ */ jsx22("span", { children: event.meta }),
1903
- event.undo && /* @__PURE__ */ jsx22(Button, { variant: "ghost", size: "xs", className: "ui-btn ui-btn--ghost ui-btn--xs ui-timeline__undo", "aria-label": event.undo.label, onClick: event.undo.onUndo, children: "Undo" })
1904
- ] })
1905
- ] }, event.id)) });
1898
+ const previousIds = useRef12(new Set(events.map((event) => event.id)));
1899
+ const rows = useRef12(/* @__PURE__ */ new Map());
1900
+ useEffect7(() => {
1901
+ if (previousIds.current.size > 0 && !prefersReducedMotion()) {
1902
+ for (const event of events) {
1903
+ if (previousIds.current.has(event.id)) continue;
1904
+ const row = rows.current.get(event.id);
1905
+ if (!row) continue;
1906
+ playEntrance2(row);
1907
+ playEntrance2(row.querySelector(".ui-timeline__marker"));
1908
+ }
1909
+ }
1910
+ previousIds.current = new Set(events.map((event) => event.id));
1911
+ }, [events]);
1912
+ return /* @__PURE__ */ jsx22("ol", { ...rest, role: "list", className: ["ui-timeline", events.some((event) => event.kind) && "ui-timeline--kinds", className].filter(Boolean).join(" "), children: events.map((event, index) => /* @__PURE__ */ jsxs18(
1913
+ "li",
1914
+ {
1915
+ className: "ui-timeline__event",
1916
+ ref: (row) => {
1917
+ if (row) rows.current.set(event.id, row);
1918
+ else rows.current.delete(event.id);
1919
+ },
1920
+ children: [
1921
+ /* @__PURE__ */ jsx22("span", { "aria-hidden": "true", className: [
1922
+ "ui-timeline__marker",
1923
+ event.kind && "ui-timeline__marker--kind",
1924
+ event.kind === "reversal" && "ui-timeline__marker--reversal"
1925
+ ].filter(Boolean).join(" "), children: event.kind && /* @__PURE__ */ jsx22(Icon, { name: kindIcons[event.kind] }) }),
1926
+ /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__head", children: [
1927
+ /* @__PURE__ */ jsx22("span", { className: "ui-timeline__actor", children: event.actor }),
1928
+ /* @__PURE__ */ jsx22("time", { dateTime: event.dateTime, children: event.timestamp }),
1929
+ index === events.length - 1 && event.relativeTimestamp && /* @__PURE__ */ jsx22("span", { className: "ui-timeline__relative", children: event.relativeTimestamp })
1930
+ ] }),
1931
+ /* @__PURE__ */ jsx22("p", { className: "ui-timeline__description", children: event.description }),
1932
+ (event.meta != null || event.undo) && /* @__PURE__ */ jsxs18("div", { className: "ui-timeline__meta", children: [
1933
+ event.meta != null && /* @__PURE__ */ jsx22("span", { children: event.meta }),
1934
+ event.undo && /* @__PURE__ */ jsx22(Button, { variant: "ghost", size: "xs", className: "ui-btn ui-btn--ghost ui-btn--xs ui-timeline__undo", "aria-label": event.undo.label, onClick: event.undo.onUndo, children: "Undo" })
1935
+ ] })
1936
+ ]
1937
+ },
1938
+ event.id
1939
+ )) });
1906
1940
  }
1907
1941
 
1908
1942
  // src/Field.tsx
1909
- import { useEffect as useEffect7, useId as useId8, useRef as useRef12, useState as useState7 } from "react";
1943
+ import { useEffect as useEffect8, useId as useId8, useRef as useRef13, useState as useState7 } from "react";
1910
1944
  import { esc, icon as icon4 } from "@apliteni/apliteni-ui";
1911
1945
  import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1912
1946
  function Frame({ id, label, hint, error, required, children }) {
@@ -1978,10 +2012,10 @@ function SelectField({ label, hint, error, required, className = "", children, .
1978
2012
  }
1979
2013
  function FileField({ label, hint, error, required, disabled, onFileChange, className = "", ...props }) {
1980
2014
  const id = useId8();
1981
- const input = useRef12(null);
2015
+ const input = useRef13(null);
1982
2016
  const [file, setFile] = useState7(null);
1983
2017
  const [dragging, setDragging] = useState7(false);
1984
- useEffect7(() => {
2018
+ useEffect8(() => {
1985
2019
  const form = input.current?.form;
1986
2020
  const reset = () => {
1987
2021
  setFile(null);
@@ -2040,7 +2074,7 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
2040
2074
  }
2041
2075
 
2042
2076
  // src/Toast.tsx
2043
- import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect8, useRef as useRef13, useState as useState8 } from "react";
2077
+ import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect9, useRef as useRef14, useState as useState8 } from "react";
2044
2078
  import { createPortal as createPortal4 } from "react-dom";
2045
2079
  import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
2046
2080
  var Context = createContext2(null);
@@ -2051,10 +2085,10 @@ function useToast() {
2051
2085
  return push;
2052
2086
  }
2053
2087
  function Notice({ notice, remove }) {
2054
- const root = useRef13(null);
2088
+ const root = useRef14(null);
2055
2089
  const [leaving2, setLeaving] = useState8(false);
2056
- const dismissed = useRef13(false);
2057
- const remaining = useRef13(5e3);
2090
+ const dismissed = useRef14(false);
2091
+ const remaining = useRef14(5e3);
2058
2092
  const [hovered, setHovered] = useState8(false);
2059
2093
  const [focused, setFocused] = useState8(false);
2060
2094
  const paused = hovered || focused;
@@ -2065,7 +2099,7 @@ function Notice({ notice, remove }) {
2065
2099
  if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) remove();
2066
2100
  else setLeaving(true);
2067
2101
  }, [remove]);
2068
- useEffect8(() => {
2102
+ useEffect9(() => {
2069
2103
  if (leaving2) {
2070
2104
  const element = root.current;
2071
2105
  const finish = (event) => {
@@ -2079,7 +2113,7 @@ function Notice({ notice, remove }) {
2079
2113
  };
2080
2114
  }
2081
2115
  }, [leaving2, remove]);
2082
- useEffect8(() => {
2116
+ useEffect9(() => {
2083
2117
  if (leaving2 || notice.action || paused) return;
2084
2118
  const started = Date.now();
2085
2119
  const timer = setTimeout(dismiss, remaining.current);
@@ -2137,7 +2171,7 @@ function Notice({ notice, remove }) {
2137
2171
  }
2138
2172
  function Toast({ children }) {
2139
2173
  const [entries, setEntries] = useState8([]);
2140
- const nextId = useRef13(0);
2174
+ const nextId = useRef14(0);
2141
2175
  const push = useCallback3((notice) => {
2142
2176
  const id = nextId.current++;
2143
2177
  const remove = () => setEntries((current) => current.filter((entry) => entry.id !== id));
@@ -2153,7 +2187,7 @@ function Toast({ children }) {
2153
2187
  }
2154
2188
 
2155
2189
  // src/AppShell.tsx
2156
- import { useEffect as useEffect9, useId as useId9, useState as useState9 } from "react";
2190
+ import { useEffect as useEffect10, useId as useId9, useState as useState9 } from "react";
2157
2191
  import { applyTheme, paletteHotkey, prism, railCollapsed, themeIcon, themeName } from "@apliteni/apliteni-ui";
2158
2192
  import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
2159
2193
  var path = (value) => value.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
@@ -2183,14 +2217,14 @@ function AppShell({
2183
2217
  const [theme, setTheme] = useState9("dark");
2184
2218
  const current = path(pathname);
2185
2219
  const active = sections.filter((section) => current === path(section.href) || path(section.href) !== "/" && current.startsWith(`${path(section.href)}/`)).sort((a, b) => path(b.href).length - path(a.href).length)[0];
2186
- useEffect9(() => {
2220
+ useEffect10(() => {
2187
2221
  setCollapsed(railCollapsed() ?? defaultCollapsed);
2188
2222
  }, [defaultCollapsed]);
2189
- useEffect9(() => {
2223
+ useEffect10(() => {
2190
2224
  setMore(false);
2191
2225
  setSearch(false);
2192
2226
  }, [pathname]);
2193
- useEffect9(() => {
2227
+ useEffect10(() => {
2194
2228
  const root = document.documentElement;
2195
2229
  const sync2 = () => setTheme(root.getAttribute("data-theme") === "light" ? "light" : "dark");
2196
2230
  sync2();
@@ -2198,7 +2232,7 @@ function AppShell({
2198
2232
  observer.observe(root, { attributes: true, attributeFilter: ["data-theme"] });
2199
2233
  return () => observer.disconnect();
2200
2234
  }, []);
2201
- useEffect9(() => {
2235
+ useEffect10(() => {
2202
2236
  const hotkey = (event) => {
2203
2237
  if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== "k") return;
2204
2238
  const target = event.target;
@@ -127,16 +127,13 @@
127
127
  .ui-drawer--top.ui-drawer--lg, .ui-drawer--bottom.ui-drawer--lg { --drawer-h: 72vh; }
128
128
 
129
129
  /* -- Slots ----------------------------------------------------------------- */
130
- /* The header keeps its rule, and the footer keeps its. A drawer's normal state is a
131
- long record scrolling, and those two lines are what say where the scrolling body
132
- ends: the one place inside the panel a line still earns.
133
- why: docs/specification.md#the-drawer */
130
+ /* The header marks the scroll boundary; actions share the panel surface. */
134
131
  .ui-drawer__header {
135
132
  display: flex;
136
133
  align-items: center;
137
134
  justify-content: space-between;
138
- gap: 12px;
139
- padding: 18px 20px;
135
+ gap: var(--space-3);
136
+ padding: var(--space-4) var(--space-5);
140
137
  border-bottom: 1px solid var(--border);
141
138
  flex: none;
142
139
  }
@@ -188,10 +185,8 @@
188
185
  display: flex;
189
186
  align-items: center;
190
187
  justify-content: flex-end;
191
- gap: 10px;
192
- padding: 16px 20px;
193
- border-top: 1px solid var(--border);
194
- background: var(--surface-2); --ring-gap: var(--surface-2);
188
+ gap: var(--space-3);
189
+ padding: var(--space-4) var(--space-5);
195
190
  flex: none;
196
191
  }
197
192