@lessly/ui 4.1.1 → 5.0.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/router.d.ts CHANGED
@@ -68,16 +68,37 @@ interface SidebarNavProps {
68
68
  declare function SidebarNav({ items, collapsed, basePath, LinkComponent, isItemActive, className, }: SidebarNavProps): React.JSX.Element;
69
69
 
70
70
  interface AppSidebarProps {
71
- items: NavItem[];
71
+ /** The rows in the middle, as links. Optional since #878: a rail whose middle is not a list of
72
+ * links writes it as `children` instead. */
73
+ items?: NavItem[];
74
+ /** The middle, written by the caller — panes, disclosures, a back row, anything `NavItem` has no
75
+ * field for. It stands exactly where the rows stand, and it owns its own scrolling: the box the
76
+ * rail gives it holds the rail's own inset and does not scroll, so an `overflow-y-auto` region
77
+ * inside it measures against the rail's own height instead of against a box the padding made
78
+ * taller.
79
+ *
80
+ * Like `railTop` it takes a function, for the same reason: the rail drives its own collapse, so
81
+ * this is the only way a caller's node can read a state it has no other route to.
82
+ *
83
+ * Passing this and `items` together is a caller error. This wins — a middle somebody wrote is
84
+ * the more specific instruction, where a list may have arrived through a wrapper's spread — and
85
+ * development says so out loud, because the alternative is rows that render nowhere in silence. */
86
+ children?: React.ReactNode | ((collapsed: boolean) => React.ReactNode);
72
87
  /** Expanded-slot mark/wordmark, rendered when `header` is absent. Also the narrow rail's fallback
73
88
  * when `logoCollapsed` isn't given. */
74
89
  logo?: React.ReactNode;
75
- /** Narrow-rail identity — a small mark, for the width where `header` has no room to be read.
76
- * Falls back to `logo`. */
90
+ /** The band's mark — a small one, standing on the glyph column at 32. It is drawn at both widths
91
+ * and never fades: at the narrow width it is the whole of the identity, and at the wide one it is
92
+ * the mark the node in `header` leads with. Falls back to `logo`. */
77
93
  logoCollapsed?: React.ReactNode;
78
94
  /** Identity slot above the rail — e.g. an <OrgProductSwitcher/> (#266). Falls back to `logo`.
79
- * Expanded-only: `logoCollapsed ?? logo` stands in it at the narrow width, because a switcher
80
- * clipped to the rail's own width reads as a tile with a severed word beside it. */
95
+ * Expanded-only: it fades out at the narrow width, because a switcher clipped to the rail's own
96
+ * width reads as a tile with a severed word beside it.
97
+ *
98
+ * A node here draws no mark of its own. `logoCollapsed ?? logo` is the band's one mark and the
99
+ * rail draws it at BOTH widths, so a second one stands 2px beside it, solid, in the same band.
100
+ * `OrgProductSwitcher` yields its own with `mark="hold"`, which leaves its box and its words
101
+ * exactly where they are. */
81
102
  header?: React.ReactNode;
82
103
  /** Top of the rail — e.g. a <QuickSearchRow/>. It sits between the header's rule and the rows,
83
104
  * and it is pinned: outside the scroll region, so it holds one place while the rows below it
@@ -94,7 +115,7 @@ interface AppSidebarProps {
94
115
  isItemActive?: (item: NavItem, pathname: string) => boolean;
95
116
  className?: string;
96
117
  }
97
- declare function AppSidebar({ items, logo, logoCollapsed, header, railTop, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React.JSX.Element;
118
+ declare function AppSidebar({ items, children, logo, logoCollapsed, header, railTop, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React.JSX.Element;
98
119
 
99
120
  interface AppShellProps {
100
121
  /** The sidebar, typically an <AppSidebar/>. Rendered in the desktop rail and, on mobile, inside a
package/dist/router.js CHANGED
@@ -9,14 +9,17 @@ import {
9
9
  SheetDescription,
10
10
  SheetTitle,
11
11
  SheetTrigger,
12
+ SidebarDrawer,
12
13
  Tooltip,
13
14
  TooltipContent,
14
15
  TooltipProvider,
15
16
  TooltipTrigger,
16
17
  cn,
18
+ railToggleLabel,
17
19
  useIsMobile,
18
- useSidebar
19
- } from "./chunk-LTHOLX7I.js";
20
+ useSidebar,
21
+ useSidebarDrawer
22
+ } from "./chunk-OTYVKDP2.js";
20
23
 
21
24
  // src/components/extension-link.tsx
22
25
  import { Link } from "react-router";
@@ -99,19 +102,19 @@ function SidebarNav({
99
102
  }
100
103
 
101
104
  // src/components/app-sidebar.tsx
102
- import * as React from "react";
103
105
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
104
- var SidebarDrawerContext = React.createContext(false);
105
- function SidebarDrawer({ children }) {
106
- return /* @__PURE__ */ jsx3(SidebarDrawerContext.Provider, { value: true, children });
107
- }
108
106
  function Divider({ testId, className }) {
109
107
  return /* @__PURE__ */ jsx3("div", { "data-testid": testId, className: cn("h-px shrink-0 bg-border-subtle", className) });
110
108
  }
111
109
  var RAIL_WIDE = "w-[248px]";
112
110
  var RAIL_NARROW = "w-[64px]";
111
+ var footTooltipOffset = (collapsed) => collapsed ? 16 : 4;
112
+ function drawsNothing(node) {
113
+ return node == null || typeof node === "boolean" || node === "" || Array.isArray(node) && node.length === 0;
114
+ }
113
115
  function AppSidebar({
114
116
  items,
117
+ children,
115
118
  logo,
116
119
  logoCollapsed,
117
120
  header,
@@ -124,7 +127,7 @@ function AppSidebar({
124
127
  isItemActive,
125
128
  className
126
129
  }) {
127
- const drawer = React.useContext(SidebarDrawerContext);
130
+ const drawer = useSidebarDrawer();
128
131
  const { collapsed: railCollapsed, toggle } = useSidebar({
129
132
  collapsed: controlled,
130
133
  defaultCollapsed,
@@ -132,54 +135,135 @@ function AppSidebar({
132
135
  });
133
136
  const collapsed = drawer ? false : railCollapsed;
134
137
  const railTopNode = typeof railTop === "function" ? railTop(collapsed) : railTop;
135
- return /* @__PURE__ */ jsx3(
136
- "aside",
137
- {
138
- "data-collapsed": collapsed,
139
- className: cn(
140
- "flex h-full shrink-0 flex-col overflow-hidden motion-rail-collapse",
141
- // A nav row drops its own label at the narrow width. What this fades is a label whatever
142
- // stands in `railTop` keeps mounted at both — QuickSearchRow's, for one — so the closing
143
- // edge slides over the words instead of cutting through them.
144
- "[&_[data-slot=nav-row-label]]:transition-opacity [&_[data-slot=nav-row-label]]:duration-fast",
145
- collapsed ? RAIL_NARROW : RAIL_WIDE,
146
- collapsed && "[&_[data-slot=nav-row-label]]:opacity-0",
147
- className
148
- ),
149
- children: /* @__PURE__ */ jsxs2("div", { className: cn("flex h-full shrink-0 flex-col", RAIL_WIDE), children: [
150
- /* @__PURE__ */ jsx3("div", { className: cn("flex h-14 shrink-0 items-center gap-2", drawer ? "pl-3 pr-10" : "px-3"), children: collapsed ? /* @__PURE__ */ jsx3("div", { className: RAIL_COLUMN_BOX, children: logoCollapsed ?? logo }) : header ?? logo }),
151
- /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-header" }),
152
- railTopNode && /* @__PURE__ */ jsx3("div", { "data-testid": "sidebar-rail-top", className: "shrink-0 px-3 pt-3", children: railTopNode }),
153
- /* @__PURE__ */ jsx3(
154
- ScrollArea,
155
- {
156
- className: "min-h-0 flex-1",
157
- viewportClassName: cn("px-3 pb-2", railTopNode ? "pt-2" : "pt-3"),
158
- viewportProps: { "data-testid": "sidebar-scroll" },
159
- children: /* @__PURE__ */ jsx3(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive })
160
- }
138
+ const separateMark = header != null || logoCollapsed != null;
139
+ const asked = typeof children === "function" ? children(collapsed) : children;
140
+ const written = drawsNothing(asked) ? null : asked;
141
+ if (process.env.NODE_ENV !== "production" && written !== null && items != null) {
142
+ console.warn(
143
+ "[AppSidebar] `children` and `items` are two middles and only one is drawn: `children` wins and the rows are dropped. Put the links inside the middle you wrote, or drop `children`."
144
+ );
145
+ }
146
+ return (
147
+ // ONE PROVIDER, ROUND THE WHOLE RAIL.
148
+ //
149
+ // The rail composes a tooltip at its foot and `SidebarNav` composes one per row at the narrow
150
+ // width, and a provider per instance is what `.storybook/preview.tsx` states the rule against:
151
+ // nested, the foot's control opened at Radix's own 700ms while every row beside it opened at
152
+ // 200, and it could never join their skip-delay group. Here rather than left to `AppShell`, so
153
+ // a rail rendered on its own still has one nesting inside the shell's is harmless.
154
+ /* @__PURE__ */ jsx3(TooltipProvider, { children: /* @__PURE__ */ jsx3(
155
+ "aside",
156
+ {
157
+ "data-collapsed": collapsed,
158
+ className: cn(
159
+ "flex h-full shrink-0 flex-col overflow-hidden motion-rail-collapse",
160
+ // A nav row drops its own label at the narrow width. What this fades is a label whatever
161
+ // stands in `railTop` keeps mounted at both — QuickSearchRow's, for one — so the closing
162
+ // edge slides over the words instead of cutting through them.
163
+ //
164
+ // The trailing mark goes on the same clock: a disclosure chevron is `nav-row-trailing`, it
165
+ // is as far past the closing edge as the words beside it, and left untimed it snapped out
166
+ // while they faded.
167
+ //
168
+ // `duration-fast` is the token scale rather than a typed number, so a `@lessly/tokens`
169
+ // release retunes it, and the step reaches the utility as `var(--motion-duration-fast,
170
+ // 100ms)` — the same variable `.motion-rail-collapse` reads, which the token layer's
171
+ // `prefers-reduced-motion` block takes to 0ms, so the words and the width stop together.
172
+ // `motion-reduce:transition-none` is what a consumer running the preset without
173
+ // `@lessly/ui/styles.css` stops on: they are left with the 100ms fallback, and no media
174
+ // query reaches a literal.
175
+ "[&_[data-slot=nav-row-label]]:transition-opacity [&_[data-slot=nav-row-label]]:duration-fast",
176
+ "[&_[data-slot=nav-row-label]]:motion-reduce:transition-none",
177
+ "[&_[data-slot=nav-row-trailing]]:transition-opacity [&_[data-slot=nav-row-trailing]]:duration-fast",
178
+ "[&_[data-slot=nav-row-trailing]]:motion-reduce:transition-none",
179
+ collapsed ? RAIL_NARROW : RAIL_WIDE,
180
+ collapsed && "[&_[data-slot=nav-row-label]]:opacity-0 [&_[data-slot=nav-row-trailing]]:opacity-0",
181
+ className
161
182
  ),
162
- !drawer && /* @__PURE__ */ jsxs2(Fragment, { children: [
163
- /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-foot" }),
164
- /* @__PURE__ */ jsx3("div", { className: "flex shrink-0 px-3 py-2", children: /* @__PURE__ */ jsx3(RailToggle, { collapsed, onClick: toggle }) })
183
+ children: /* @__PURE__ */ jsxs2("div", { className: cn("flex h-full shrink-0 flex-col", RAIL_WIDE), children: [
184
+ /* @__PURE__ */ jsx3("div", { className: cn("relative flex h-14 shrink-0 items-center", drawer ? "pl-3 pr-10" : "px-3"), children: separateMark ? /* @__PURE__ */ jsxs2(Fragment, { children: [
185
+ /* @__PURE__ */ jsx3(
186
+ "div",
187
+ {
188
+ "data-testid": "sidebar-identity-expanded",
189
+ "aria-hidden": collapsed || void 0,
190
+ inert: collapsed,
191
+ className: cn(
192
+ "flex w-full items-center gap-2 transition-opacity duration-normal motion-reduce:transition-none",
193
+ collapsed && "pointer-events-none opacity-0"
194
+ ),
195
+ children: header ?? logo
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx3(
199
+ "div",
200
+ {
201
+ "data-testid": "sidebar-identity-collapsed",
202
+ "aria-hidden": !collapsed || void 0,
203
+ inert: !collapsed,
204
+ className: cn("absolute", RAIL_COLUMN_BOX, !collapsed && "pointer-events-none"),
205
+ children: logoCollapsed ?? logo
206
+ }
207
+ )
208
+ ] }) : (
209
+ /* ONE MARK, MOUNTED ONCE.
210
+ With neither `header` nor `logoCollapsed`, both slots resolve to the same `logo`, and
211
+ a node fading into an identical node is not a transition — it is one mark ghosted off
212
+ another. Mounted twice it was worse than a picture: one `id` inside an SVG logo
213
+ belongs to two elements and `getElementById` finds the hidden one, a `ref` fires
214
+ twice and measures the transparent copy, effects subscribe twice, and `inert` pauses
215
+ no player.
216
+ It stands in RAIL_COLUMN_BOX at BOTH widths, which is the box the closed mark and the
217
+ foot's toggle already stand in: centre 32, the column every row's glyph is on. That
218
+ is a class list that never changes, so there is no frame for it to move on. */
219
+ /* @__PURE__ */ jsx3("div", { "data-testid": "sidebar-identity-single", className: RAIL_COLUMN_BOX, children: logo })
220
+ ) }),
221
+ /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-header" }),
222
+ railTopNode && /* @__PURE__ */ jsx3("div", { "data-testid": "sidebar-rail-top", className: "shrink-0 px-3 pt-3", children: railTopNode }),
223
+ written !== null ? /* @__PURE__ */ jsx3(
224
+ "div",
225
+ {
226
+ "data-testid": "sidebar-body",
227
+ className: cn(
228
+ "flex min-h-0 flex-1 flex-col overflow-hidden px-3 pb-2",
229
+ railTopNode ? "pt-2" : "pt-3"
230
+ ),
231
+ children: written
232
+ }
233
+ ) : /* @__PURE__ */ jsx3(
234
+ ScrollArea,
235
+ {
236
+ className: "min-h-0 flex-1",
237
+ viewportClassName: cn("px-3 pb-2", railTopNode ? "pt-2" : "pt-3"),
238
+ viewportProps: { "data-testid": "sidebar-scroll" },
239
+ children: /* @__PURE__ */ jsx3(SidebarNav, { items: items ?? [], collapsed, basePath, LinkComponent, isItemActive })
240
+ }
241
+ ),
242
+ !drawer && /* @__PURE__ */ jsxs2(Fragment, { children: [
243
+ /* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-foot" }),
244
+ /* @__PURE__ */ jsx3("div", { className: "flex shrink-0 px-3 py-2", children: /* @__PURE__ */ jsxs2(Tooltip, { children: [
245
+ /* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, "aria-describedby": void 0, children: /* @__PURE__ */ jsx3(RailToggle, { collapsed, onClick: toggle }) }),
246
+ /* @__PURE__ */ jsx3(TooltipContent, { side: "right", sideOffset: footTooltipOffset(collapsed), children: railToggleLabel(collapsed) })
247
+ ] }) })
248
+ ] })
165
249
  ] })
166
- ] })
167
- }
250
+ }
251
+ ) })
168
252
  );
169
253
  }
170
254
 
171
255
  // src/components/app-shell.tsx
172
- import * as React2 from "react";
256
+ import * as React from "react";
173
257
  import { Menu } from "lucide-react";
174
258
  import { useLocation as useLocation2 } from "react-router";
175
259
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
176
260
  function expandedSidebar(sidebar) {
177
- return React2.isValidElement(sidebar) ? React2.cloneElement(sidebar, { collapsed: false }) : sidebar;
261
+ return React.isValidElement(sidebar) ? React.cloneElement(sidebar, { collapsed: false }) : sidebar;
178
262
  }
179
263
  function AppShell({ sidebar, topBar, children, className }) {
180
264
  const { pathname } = useLocation2();
181
- const [mobileOpen, setMobileOpen] = React2.useState(false);
182
- React2.useEffect(() => {
265
+ const [mobileOpen, setMobileOpen] = React.useState(false);
266
+ React.useEffect(() => {
183
267
  setMobileOpen(false);
184
268
  }, [pathname]);
185
269
  const isMobile = useIsMobile();
@@ -196,7 +280,7 @@ function AppShell({ sidebar, topBar, children, className }) {
196
280
  /* @__PURE__ */ jsxs3(SheetContent, { side: "left", className: "w-[248px] bg-bg-primary p-0", children: [
197
281
  /* @__PURE__ */ jsx4(SheetTitle, { className: "sr-only", children: "Navigation" }),
198
282
  /* @__PURE__ */ jsx4(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
199
- /* @__PURE__ */ jsx4(SidebarDrawer, { children: expandedSidebar(sidebar) })
283
+ /* @__PURE__ */ jsx4(SidebarDrawer, { onDismiss: () => setMobileOpen(false), children: expandedSidebar(sidebar) })
200
284
  ] })
201
285
  ] }),
202
286
  /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-2", children: topBar })
@@ -19,7 +19,7 @@
19
19
  --bg-elevated: #252936;
20
20
  --bg-overlay: #252936;
21
21
  --bg-sunken: #0e1015;
22
- --bg-selected: #1f222d;
22
+ --bg-selected: #252735;
23
23
  --bg-brand: #06297d;
24
24
  --bg-brand-subtle: #1f2541;
25
25
  --bg-brand-hover: #08349b;
@@ -103,12 +103,12 @@
103
103
  --grid-container-max: 1440px;
104
104
 
105
105
  /* typography text-styles — theme-invariant, :root only (see typographyVars in build.js) */
106
- --text-display-xl-font-family: "Instrument Serif", Georgia, "Times New Roman", serif;
106
+ --text-display-xl-font-family: "Sora", "Inter", system-ui, -apple-system, sans-serif;
107
107
  --text-display-xl-font-size: 3.5rem;
108
108
  --text-display-xl-line-height: 1.05;
109
109
  --text-display-xl-font-weight: 400;
110
110
  --text-display-xl-letter-spacing: -0.02em;
111
- --text-display-lg-font-family: "Instrument Serif", Georgia, "Times New Roman", serif;
111
+ --text-display-lg-font-family: "Sora", "Inter", system-ui, -apple-system, sans-serif;
112
112
  --text-display-lg-font-size: 3rem;
113
113
  --text-display-lg-line-height: 1.1;
114
114
  --text-display-lg-font-weight: 400;
@@ -283,7 +283,7 @@
283
283
  --bg-elevated: #edeef0;
284
284
  --bg-overlay: #ffffff;
285
285
  --bg-sunken: #e1e2e4;
286
- --bg-selected: #f2f3f4;
286
+ --bg-selected: #eceded;
287
287
  --bg-brand-subtle: #e4ecfe;
288
288
  --bg-brand-hover: #05226a;
289
289
  --bg-brand-bright-hover: #0b4cd5;
@@ -342,6 +342,27 @@
342
342
  --overlay-modal-bg: #ffffff;
343
343
  --focus-ring-color: #0940b8;
344
344
 
345
+ /* Same value as :root on purpose, re-declared so .light lists every colour token a
346
+ light consumer gets rather than leaving some to inheritance. Why, and what holds
347
+ it honest: docs/tokens.md, "Theme invariance". */
348
+ --bg-brand: #06297d;
349
+ --bg-brand-bright: #165ff2;
350
+ --bg-success: #13703c;
351
+ --bg-success-bright: #21be63;
352
+ --bg-danger: #78100b;
353
+ --text-on-brand: #f6f7f7;
354
+ --text-on-success: #f6f7f7;
355
+ --text-on-success-bright: #08090c;
356
+ --text-on-warning: #08090c;
357
+ --text-on-danger: #f6f7f7;
358
+ --border-selected: #737c95;
359
+ --border-brand: #165ff2;
360
+ --border-warning: #b3780e;
361
+ --border-danger: #e82020;
362
+ --field-border-hover: #737c95;
363
+ --field-border-filled: #737c95;
364
+ --field-border-error: #e82020;
365
+
345
366
  /* shadcn role bridge re-declared so .light on a nested element (not just <html>)
346
367
  re-resolves the aliases to light values. */
347
368
  --background: var(--bg-primary);
@@ -471,11 +492,15 @@
471
492
  --color-bg-surface: var(--bg-surface);
472
493
  --color-bg-elevated: var(--bg-elevated);
473
494
  --color-bg-sunken: var(--bg-sunken);
495
+ --color-bg-brand: var(--bg-brand);
474
496
  --color-bg-brand-subtle: var(--bg-brand-subtle);
475
497
  --color-bg-brand-hover: var(--bg-brand-hover);
498
+ --color-bg-brand-bright: var(--bg-brand-bright);
499
+ --color-bg-success: var(--bg-success);
476
500
  --color-bg-success-subtle: var(--bg-success-subtle);
477
501
  --color-bg-warning: var(--bg-warning);
478
502
  --color-bg-warning-subtle: var(--bg-warning-subtle);
503
+ --color-bg-danger: var(--bg-danger);
479
504
  --color-bg-danger-subtle: var(--bg-danger-subtle);
480
505
  --color-bg-teal-subtle: var(--bg-teal-subtle);
481
506
  --color-bg-violet-subtle: var(--bg-violet-subtle);
@@ -485,6 +510,10 @@
485
510
  --color-text-tertiary: var(--text-tertiary);
486
511
  --color-text-disabled: var(--text-disabled);
487
512
  --color-text-inverse: var(--text-inverse);
513
+ --color-text-on-brand: var(--text-on-brand);
514
+ --color-text-on-success: var(--text-on-success);
515
+ --color-text-on-warning: var(--text-on-warning);
516
+ --color-text-on-danger: var(--text-on-danger);
488
517
  --color-text-brand: var(--text-brand);
489
518
  --color-text-success: var(--text-success);
490
519
  --color-text-warning: var(--text-warning);
@@ -502,17 +531,25 @@
502
531
  --color-border-subtle: var(--border-subtle);
503
532
  --color-border-default: var(--border-default);
504
533
  --color-border-strong: var(--border-strong);
534
+ --color-border-brand: var(--border-brand);
505
535
  --color-border-focus: var(--border-focus);
506
536
  --color-border-success: var(--border-success);
537
+ --color-border-warning: var(--border-warning);
538
+ --color-border-danger: var(--border-danger);
507
539
  }
508
540
 
509
541
  /* === Fonts === */
542
+ /* This kit declares no face of its own, and scripts/build-styles.mjs refuses a build where one
543
+ reappears here. Written without the at-rule spelled out: that guard is a character scanner
544
+ over this file, so naming the rule in prose would register as a block.
510
545
 
511
-
512
-
513
-
514
-
515
-
546
+ The faces live in @lessly/tokens/fonts.css — Inter, Fira Mono and Sora, against RELATIVE
547
+ `./fonts/...` paths at files inside that package. A consumer imports that sheet and is done.
548
+ What stood here declared four of them a second time against ABSOLUTE `/fonts/**.woff2`, which
549
+ named files the kit does not ship and every consumer had to serve itself; one who did not
550
+ rendered in system fallback with no error at all. Sora, which `--text-display-*-font-family`
551
+ has named since @lessly/tokens@0.8.0, was never declared here in any form and so never
552
+ painted at all. */
516
553
 
517
554
  /* === Theme Variables === */
518
555
  /* The token palettes (colors, typography, motion, shadows, grid) live in
@@ -606,8 +643,10 @@
606
643
 
607
644
  /* === Motion === */
608
645
  /* Backs the <AutoHeight> primitive: eases a container's height as its content changes size.
609
- Motion-token driven, so it inherits reduce-motion (the vars resolve to 0ms). A real class rather
610
- than a Tailwind arbitrary utility, which the content scanner can drop for a newly-used class.
646
+ A real class because `height` is a property no Tailwind transition utility names: the only way to
647
+ write it in a class list is the arbitrary `transition-[height]`, which the content scanner can
648
+ drop the first time a file writes it. (The duration is not the reason — `duration-normal` carries
649
+ the same variable this line does.)
611
650
  Overflow is NOT clipped here — the component clips only while the height is animating (so the
612
651
  settled box doesn't slice focus rings or tooltips on full-width children). */
613
652
  .motion-auto-height {
@@ -617,9 +656,11 @@
617
656
  }
618
657
 
619
658
  /* Backs ToggleGroup's `sliding` mark: one thumb that travels between equal-width slots instead of a
620
- fill appearing on each. A real class for the same reason as the one above, and `slide` because the
621
- thumb crosses the width of a slot `ui`, the hover/focus tint step, would clip it to near a snap.
622
- `standard` because it is on screen before and after and only moves in place. */
659
+ fill appearing on each. A named primitive rather than the three utilities that would now say the
660
+ same thing, so the thumb's timing is stated once and cannot drift between the call sites that
661
+ write it. `slide` because the thumb crosses the width of a slot `ui`, the hover/focus tint step,
662
+ would clip it to near a snap. `standard` because it is on screen before and after and only moves
663
+ in place. */
623
664
  .motion-segment-thumb {
624
665
  transition-property: transform;
625
666
  transition-duration: var(--motion-duration-slide);
@@ -630,9 +671,9 @@
630
671
  frame as it travels. One class on both, so the mark cannot be retuned away from the edge it
631
672
  answers for — the same reason two carets read one `caretMotion`. `transform` is here rather than
632
673
  in a class of its own because the bar is that edge said in miniature: retune one and the two stop
633
- arriving together. A real class for the reason the two above are, and
634
- token-driven because a Tailwind `duration-200` is a literal 200ms: under prefers-reduced-motion
635
- the rail kept sliding for its full 200ms while every token-driven motion beside it went instant. */
674
+ arriving together. A real class for the reason the first one above is, and more so: `width,
675
+ transform` is a pair no Tailwind utility names at all, so writing it in a class list means the
676
+ arbitrary `transition-[width,transform]` the form the scanner can drop. */
636
677
  .motion-rail-collapse {
637
678
  transition-property: width, transform;
638
679
  transition-duration: var(--motion-duration-normal);
@@ -697,6 +738,9 @@
697
738
  /* === Animation layer (tw-animate-css, compiled) — see scripts/build-styles.mjs === */
698
739
  /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
699
740
  @layer properties;
741
+ .animate-accordion-down {
742
+ animation: accordion-down var(--tw-animation-duration,var(--tw-duration,.2s))var(--tw-ease,ease-out)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none);
743
+ }
700
744
  .animate-in {
701
745
  animation: enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none);
702
746
  }