@lessly/ui 1.5.0 → 2.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.
@@ -0,0 +1,101 @@
1
+ import * as React from 'react';
2
+ import { LucideIcon } from 'lucide-react';
3
+
4
+ type ExtensionLinkUiMode = 'federation' | 'standalone';
5
+ interface ExtensionLinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
6
+ /**
7
+ * 'federation' → the extension runs inside the workspace shell and navigates
8
+ * client-side via react-router's <Link>. 'standalone' → the extension is
9
+ * served full-page, so a plain <a href> triggers a normal document load.
10
+ */
11
+ uiMode: ExtensionLinkUiMode;
12
+ /** Target route/href. */
13
+ to: string;
14
+ }
15
+ /**
16
+ * Federation-aware navigation link. Federation extensions are mounted in-shell
17
+ * and navigate client-side (react-router <Link>); standalone extensions load
18
+ * full-page (<a href>). Anchor props are forwarded to both variants — in
19
+ * particular onMouseEnter/onFocus, which hosts use to preload the target route.
20
+ *
21
+ * The federation branch renders a react-router <Link>, so it must be used
22
+ * within a Router context (the workspace shell / MemoryRouter in tests).
23
+ */
24
+ declare function ExtensionLink({ uiMode, to, className, children, ...anchorProps }: ExtensionLinkProps): React.JSX.Element;
25
+ declare namespace ExtensionLink {
26
+ var displayName: string;
27
+ }
28
+
29
+ interface NavItem {
30
+ label: string;
31
+ href: string;
32
+ icon?: LucideIcon;
33
+ exact?: boolean;
34
+ }
35
+ type NavLinkComponent = React.ComponentType<{
36
+ to: string;
37
+ className?: string;
38
+ children?: React.ReactNode;
39
+ } & Record<string, unknown>>;
40
+ interface SidebarNavLinkProps {
41
+ item: NavItem;
42
+ collapsed?: boolean;
43
+ active?: boolean;
44
+ basePath?: string;
45
+ LinkComponent?: NavLinkComponent;
46
+ className?: string;
47
+ }
48
+ declare function SidebarNavLink({ item, collapsed, active, basePath, LinkComponent, className, }: SidebarNavLinkProps): React.JSX.Element;
49
+ interface SidebarNavProps {
50
+ items: NavItem[];
51
+ collapsed?: boolean;
52
+ basePath?: string;
53
+ LinkComponent?: NavLinkComponent;
54
+ isItemActive?: (item: NavItem, pathname: string) => boolean;
55
+ className?: string;
56
+ }
57
+ declare function SidebarNav({ items, collapsed, basePath, LinkComponent, isItemActive, className, }: SidebarNavProps): React.JSX.Element;
58
+
59
+ interface AppSidebarProps {
60
+ items: NavItem[];
61
+ /** Expanded-slot mark/wordmark, rendered when `header` is absent. Also the collapsed-rail
62
+ * fallback when `logoCollapsed` isn't given, on a rail that isn't `collapsible` — see
63
+ * `logoCollapsed`. */
64
+ logo?: React.ReactNode;
65
+ /** Collapsed-rail identity — a small mark, for a rail that cannot be expanded from inside it.
66
+ * A `collapsible` rail shows the pin here instead and never reads this: the 52px rail holds
67
+ * one box, and the way back to expanded is what it holds. */
68
+ logoCollapsed?: React.ReactNode;
69
+ /** Identity slot above the rail — e.g. an <OrgProductSwitcher/> (#266). Falls back to `logo`.
70
+ * Expanded-only: rendering it in the 52px collapsed rail would overflow, so the collapsed
71
+ * branch never reads it. What stands in depends on `collapsible` — the pin when it is set,
72
+ * `logoCollapsed ?? logo` when it is not. */
73
+ header?: React.ReactNode;
74
+ /** Account slot below the rail — e.g. a <UserMenu/>. Its divider renders only with it. */
75
+ footer?: React.ReactNode;
76
+ /** Render the built-in collapse pin. Off by default: the shipped rail has none.
77
+ * `collapsed` is still honoured without it, for consumers driving collapse themselves. */
78
+ collapsible?: boolean;
79
+ collapsed?: boolean;
80
+ defaultCollapsed?: boolean;
81
+ onCollapsedChange?: (collapsed: boolean) => void;
82
+ basePath?: string;
83
+ LinkComponent?: NavLinkComponent;
84
+ isItemActive?: (item: NavItem, pathname: string) => boolean;
85
+ className?: string;
86
+ }
87
+ declare function AppSidebar({ items, logo, logoCollapsed, header, footer, collapsible, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React.JSX.Element;
88
+
89
+ interface AppShellProps {
90
+ /** The sidebar, typically an <AppSidebar/>. Rendered in the desktop rail and,
91
+ * on mobile, inside a Sheet (force-expanded via cloneElement collapsed=false). */
92
+ sidebar: React.ReactNode;
93
+ topBar?: React.ReactNode;
94
+ children?: React.ReactNode;
95
+ className?: string;
96
+ }
97
+ declare function AppShell({ sidebar, topBar, children, className }: AppShellProps): React.JSX.Element;
98
+ /** Alias of AppShell — reads better in app router files. */
99
+ declare const AuthenticatedLayout: typeof AppShell;
100
+
101
+ export { AppShell, type AppShellProps, AppSidebar, type AppSidebarProps, AuthenticatedLayout, ExtensionLink, type ExtensionLinkProps, type ExtensionLinkUiMode, type NavItem, type NavLinkComponent, SidebarNav, SidebarNavLink, type SidebarNavLinkProps, type SidebarNavProps };
package/dist/router.js ADDED
@@ -0,0 +1,254 @@
1
+ import {
2
+ Button,
3
+ NavRow,
4
+ Sheet,
5
+ SheetContent,
6
+ SheetDescription,
7
+ SheetTitle,
8
+ SheetTrigger,
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ cn,
14
+ useIsMobile,
15
+ useSidebar
16
+ } from "./chunk-F5RMHSPS.js";
17
+
18
+ // src/components/extension-link.tsx
19
+ import { Link } from "react-router";
20
+ import { jsx } from "react/jsx-runtime";
21
+ function ExtensionLink({ uiMode, to, className, children, ...anchorProps }) {
22
+ if (uiMode === "federation") {
23
+ return /* @__PURE__ */ jsx(Link, { to, className, ...anchorProps, children });
24
+ }
25
+ return /* @__PURE__ */ jsx("a", { href: to, className, ...anchorProps, children });
26
+ }
27
+ ExtensionLink.displayName = "ExtensionLink";
28
+
29
+ // src/components/sidebar-nav.tsx
30
+ import { Link as Link2, useLocation } from "react-router";
31
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
32
+ function isActivePath(pathname, full, exact) {
33
+ if (exact) return pathname === full;
34
+ return pathname === full || pathname.startsWith(`${full}/`);
35
+ }
36
+ function SidebarNavLink({
37
+ item,
38
+ collapsed = false,
39
+ active = false,
40
+ basePath = "",
41
+ LinkComponent,
42
+ className
43
+ }) {
44
+ const Comp = LinkComponent ?? Link2;
45
+ const to = `${basePath}${item.href}`;
46
+ const Icon = item.icon;
47
+ const link = /* @__PURE__ */ jsx2(
48
+ NavRow,
49
+ {
50
+ variant: "rail",
51
+ active,
52
+ label: item.label,
53
+ hideLabel: collapsed,
54
+ icon: Icon ? /* @__PURE__ */ jsx2(Icon, { "aria-hidden": "true" }) : void 0,
55
+ className: cn(collapsed && "size-9 justify-center px-0 py-0", className),
56
+ asChild: true,
57
+ children: /* @__PURE__ */ jsx2(Comp, { to, "aria-current": active ? "page" : void 0 })
58
+ }
59
+ );
60
+ if (!collapsed) return link;
61
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
62
+ /* @__PURE__ */ jsx2(TooltipTrigger, { asChild: true, children: link }),
63
+ /* @__PURE__ */ jsx2(TooltipContent, { side: "right", children: item.label })
64
+ ] });
65
+ }
66
+ function SidebarNav({
67
+ items,
68
+ collapsed = false,
69
+ basePath = "",
70
+ LinkComponent,
71
+ isItemActive,
72
+ className
73
+ }) {
74
+ const { pathname } = useLocation();
75
+ const nav = /* @__PURE__ */ jsx2(
76
+ "nav",
77
+ {
78
+ className: cn(
79
+ "flex flex-col gap-2",
80
+ collapsed && "items-center",
81
+ className
82
+ ),
83
+ children: items.map((item) => {
84
+ const full = `${basePath}${item.href}`;
85
+ const active = isItemActive ? isItemActive(item, pathname) : isActivePath(pathname, full, item.exact);
86
+ return /* @__PURE__ */ jsx2(
87
+ SidebarNavLink,
88
+ {
89
+ item,
90
+ active,
91
+ collapsed,
92
+ basePath,
93
+ LinkComponent
94
+ },
95
+ item.href
96
+ );
97
+ })
98
+ }
99
+ );
100
+ return collapsed ? /* @__PURE__ */ jsx2(TooltipProvider, { children: nav }) : nav;
101
+ }
102
+
103
+ // src/components/app-sidebar.tsx
104
+ import { PanelLeft } from "lucide-react";
105
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
106
+ function Divider({ collapsed, testId, className }) {
107
+ return /* @__PURE__ */ jsx3(
108
+ "div",
109
+ {
110
+ "data-testid": testId,
111
+ className: cn(
112
+ "h-px shrink-0 bg-border-subtle",
113
+ collapsed ? "ml-[6px] w-[40px]" : "ml-[14px] w-[220px]",
114
+ className
115
+ )
116
+ }
117
+ );
118
+ }
119
+ function AppSidebar({
120
+ items,
121
+ logo,
122
+ logoCollapsed,
123
+ header,
124
+ footer,
125
+ collapsible = false,
126
+ collapsed: controlled,
127
+ defaultCollapsed,
128
+ onCollapsedChange,
129
+ basePath,
130
+ LinkComponent,
131
+ isItemActive,
132
+ className
133
+ }) {
134
+ const { collapsed, toggle } = useSidebar({
135
+ collapsed: controlled,
136
+ defaultCollapsed,
137
+ onCollapsedChange
138
+ });
139
+ const pin = collapsible ? /* @__PURE__ */ jsx3(
140
+ "button",
141
+ {
142
+ type: "button",
143
+ onClick: toggle,
144
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
145
+ "aria-expanded": !collapsed,
146
+ className: "flex size-6 shrink-0 items-center justify-center rounded-md text-text-secondary transition-colors hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
147
+ children: /* @__PURE__ */ jsx3(PanelLeft, { className: "size-4", "aria-hidden": "true" })
148
+ }
149
+ ) : null;
150
+ return /* @__PURE__ */ jsxs2(
151
+ "aside",
152
+ {
153
+ "data-collapsed": collapsed,
154
+ className: cn(
155
+ "flex h-full shrink-0 flex-col overflow-hidden transition-[width] duration-200 ease-in-out",
156
+ collapsed ? "w-[52px]" : "w-[248px]",
157
+ className
158
+ ),
159
+ children: [
160
+ /* @__PURE__ */ jsx3("div", { className: cn("flex h-14 items-center gap-2", collapsed ? "justify-center px-1" : "px-3"), children: collapsed ? collapsible ? (
161
+ /* The 52px rail holds one 32px box, and the way back to expanded is what it holds.
162
+ The logo has the expanded band and the top bar; the pin has nowhere else, and a
163
+ pin hidden behind the mark is no pin at all on a device that cannot hover. */
164
+ /* @__PURE__ */ jsx3(
165
+ "button",
166
+ {
167
+ type: "button",
168
+ onClick: toggle,
169
+ "aria-label": "Expand sidebar",
170
+ "aria-expanded": false,
171
+ className: "flex size-8 shrink-0 items-center justify-center rounded-md text-text-secondary transition-colors hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
172
+ children: /* @__PURE__ */ jsx3(PanelLeft, { className: "size-4", "aria-hidden": "true" })
173
+ }
174
+ )
175
+ ) : logoCollapsed ?? logo : /* @__PURE__ */ jsxs2(Fragment, { children: [
176
+ header ?? logo,
177
+ pin
178
+ ] }) }),
179
+ /* @__PURE__ */ jsx3(Divider, { collapsed, testId: "sidebar-divider-header" }),
180
+ /* @__PURE__ */ jsx3(
181
+ "div",
182
+ {
183
+ "data-testid": "sidebar-scroll",
184
+ className: cn(
185
+ "min-h-0 flex-1 overflow-y-auto overflow-x-hidden pt-3 pb-2",
186
+ collapsed ? "px-1" : "px-3"
187
+ ),
188
+ children: /* @__PURE__ */ jsx3(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive })
189
+ }
190
+ ),
191
+ footer && /* @__PURE__ */ jsxs2(Fragment, { children: [
192
+ /* @__PURE__ */ jsx3(Divider, { collapsed, testId: "sidebar-divider-account" }),
193
+ /* @__PURE__ */ jsx3("div", { className: cn("py-2", collapsed ? "px-1" : "px-3"), children: footer })
194
+ ] })
195
+ ]
196
+ }
197
+ );
198
+ }
199
+
200
+ // src/components/app-shell.tsx
201
+ import * as React from "react";
202
+ import { Menu } from "lucide-react";
203
+ import { useLocation as useLocation2 } from "react-router";
204
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
205
+ function expandedSidebar(sidebar) {
206
+ return React.isValidElement(sidebar) ? React.cloneElement(sidebar, { collapsed: false }) : sidebar;
207
+ }
208
+ function AppShell({ sidebar, topBar, children, className }) {
209
+ const { pathname } = useLocation2();
210
+ const [mobileOpen, setMobileOpen] = React.useState(false);
211
+ React.useEffect(() => {
212
+ setMobileOpen(false);
213
+ }, [pathname]);
214
+ const isMobile = useIsMobile();
215
+ if (isMobile) {
216
+ return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen flex-col bg-bg-surface", className), children: [
217
+ /* @__PURE__ */ jsxs3(
218
+ "header",
219
+ {
220
+ "data-slot": "app-shell-mobile-header",
221
+ className: "flex shrink-0 items-center justify-between border-b border-border-subtle bg-bg-surface px-4 py-3",
222
+ children: [
223
+ /* @__PURE__ */ jsxs3(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
224
+ /* @__PURE__ */ jsx4(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx4(Button, { variant: "ghost", size: "icon", className: "size-8 text-text-secondary", "aria-label": "Open navigation", children: /* @__PURE__ */ jsx4(Menu, { className: "size-5" }) }) }),
225
+ /* @__PURE__ */ jsxs3(SheetContent, { side: "left", className: "w-[248px] bg-bg-surface p-0", children: [
226
+ /* @__PURE__ */ jsx4(SheetTitle, { className: "sr-only", children: "Navigation" }),
227
+ /* @__PURE__ */ jsx4(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
228
+ expandedSidebar(sidebar)
229
+ ] })
230
+ ] }),
231
+ /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-2", children: topBar })
232
+ ]
233
+ }
234
+ ),
235
+ /* @__PURE__ */ jsx4("main", { className: "flex-1 overflow-auto px-4", children })
236
+ ] }) });
237
+ }
238
+ return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen gap-3 bg-bg-surface p-4", className), children: [
239
+ /* @__PURE__ */ jsx4("div", { className: "sticky top-4 h-[calc(100vh-32px)] shrink-0", children: sidebar }),
240
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: [
241
+ topBar && /* @__PURE__ */ jsx4("div", { "data-slot": "app-shell-topbar", className: "flex h-14 shrink-0 items-center gap-2 px-6", children: topBar }),
242
+ /* @__PURE__ */ jsx4("main", { className: "flex-1 overflow-y-auto px-6", children })
243
+ ] })
244
+ ] }) });
245
+ }
246
+ var AuthenticatedLayout = AppShell;
247
+ export {
248
+ AppShell,
249
+ AppSidebar,
250
+ AuthenticatedLayout,
251
+ ExtensionLink,
252
+ SidebarNav,
253
+ SidebarNavLink
254
+ };
@@ -12,7 +12,7 @@
12
12
  * on a parent element (usually <html> or <body>).
13
13
  */
14
14
 
15
- :root {
15
+ :where(:root) {
16
16
  --bg-primary: #191b24;
17
17
  --bg-secondary: #12141b;
18
18
  --bg-surface: #1f222d;
@@ -276,7 +276,7 @@
276
276
  --inverted-foreground: var(--bg-brand);
277
277
  }
278
278
 
279
- .light {
279
+ :where(.light) {
280
280
  --bg-primary: #f6f7f7;
281
281
  --bg-secondary: #eeeff0;
282
282
  --bg-surface: #f2f3f4;
@@ -385,7 +385,7 @@
385
385
 
386
386
  /* Reduced motion — collapse every motion variable to instant/linear (see reducedMotionBlock in build.js). */
387
387
  @media (prefers-reduced-motion: reduce) {
388
- :root {
388
+ :where(:root) {
389
389
  --motion-duration-instant: 0ms;
390
390
  --motion-duration-fast: 0ms;
391
391
  --motion-duration-normal: 0ms;
@@ -413,7 +413,7 @@
413
413
  /* === Deprecated aliases — remove in the next major ===
414
414
  Old vendored `--color-*` names mapped onto the @lessly/tokens variables above.
415
415
  Generated by scripts/build-styles.mjs — do not edit. */
416
- :root {
416
+ :where(:root) {
417
417
  --color-bg-primary: var(--bg-primary);
418
418
  --color-bg-secondary: var(--bg-secondary);
419
419
  --color-bg-surface: var(--bg-surface);
@@ -465,7 +465,7 @@
465
465
  --color-border-danger: var(--border-danger);
466
466
  }
467
467
 
468
- .light {
468
+ :where(.light) {
469
469
  --color-bg-primary: var(--bg-primary);
470
470
  --color-bg-secondary: var(--bg-secondary);
471
471
  --color-bg-surface: var(--bg-surface);
@@ -530,7 +530,18 @@
530
530
  :where(:root) {
531
531
  --spacing: 0.25rem;
532
532
  }
533
- :root {
533
+ :where(:root) {
534
+ /* === Translucent tints the kit paints (#701) === */
535
+ /* A semantic token is a whole colour with no `<alpha-value>` placeholder, so `bg-bg-secondary/50`
536
+ compiles to nothing on Tailwind 3 while Tailwind 4 answers it with color-mix. These are the
537
+ three alphas the kit writes, as variables the preset maps a colour name onto so both majors
538
+ and both consumption paths resolve them. Like --hov-bg below, the inner var() resolves at the
539
+ element that paints, so `.light` needs no override. Delete each the release @lessly/tokens
540
+ publishes its semantic colours with an `<alpha-value>` placeholder. */
541
+ --bg-secondary-a50: color-mix(in oklab, var(--bg-secondary) 50%, transparent);
542
+ --bg-secondary-a80: color-mix(in oklab, var(--bg-secondary) 80%, transparent);
543
+ --bg-danger-a90: color-mix(in oklab, var(--bg-danger) 90%, transparent);
544
+
534
545
  /* === Nav / menu surfaces (0.3.0 sidebar) === */
535
546
  /* The floating surface sits on the package's overlay pair: --menu-bg on
536
547
  --bg-overlay, --menu-shadow on --shadow-overlay. theme.css re-resolves both
@@ -553,7 +564,7 @@
553
564
  --auth-btn-shadow: none;
554
565
  }
555
566
 
556
- .light {
567
+ :where(.light) {
557
568
  /* Light keeps opaque hover plates: a translucent text-mix would vanish on a
558
569
  white menu. Pinned 0.3.0-prototype literals — the package has no hover-surface
559
570
  roles yet, so these can't move onto tokens the way --menu-bg did. */
@@ -593,6 +604,15 @@
593
604
  transition-timing-function: var(--motion-easing-standard);
594
605
  }
595
606
 
607
+ /* === Layout === */
608
+ /* Backs the <PageColumn> primitive: the reading column, capped and centred in the well. A real class
609
+ rather than a Tailwind arbitrary utility, which the content scanner can drop for a newly-used
610
+ class. Horizontal padding is deliberately absent — AppShell owns the inset. */
611
+ .page-column {
612
+ max-width: 780px;
613
+ margin-inline: auto;
614
+ }
615
+
596
616
  /* === Animation layer (tw-animate-css, compiled) — see scripts/build-styles.mjs === */
597
617
  /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
598
618
  @layer properties;
@@ -935,3 +955,88 @@
935
955
  }
936
956
  }
937
957
  }
958
+
959
+ /* === Tailwind v4 theme bridge — generated by scripts/build-styles.mjs === */
960
+ /* Colour utilities exist in v4 only for names in the `@theme` namespace; the deprecated
961
+ --color-* aliases above are :root properties, which is not the same thing. `inline` so a
962
+ utility substitutes the token at the element it paints rather than freezing it on :root,
963
+ which is what keeps a `.light` scope inside the page working. Permanent, and inert on
964
+ Tailwind 3. */
965
+ @theme inline {
966
+ --color-bg-brand: var(--bg-brand);
967
+ --color-bg-brand-bright: var(--bg-brand-bright);
968
+ --color-bg-brand-bright-hover: var(--bg-brand-bright-hover);
969
+ --color-bg-brand-hover: var(--bg-brand-hover);
970
+ --color-bg-brand-subtle: var(--bg-brand-subtle);
971
+ --color-bg-danger: var(--bg-danger);
972
+ --color-bg-danger-a90: var(--bg-danger-a90);
973
+ --color-bg-danger-subtle: var(--bg-danger-subtle);
974
+ --color-bg-elevated: var(--bg-elevated);
975
+ --color-bg-overlay: var(--bg-overlay);
976
+ --color-bg-primary: var(--bg-primary);
977
+ --color-bg-rose-subtle: var(--bg-rose-subtle);
978
+ --color-bg-secondary: var(--bg-secondary);
979
+ --color-bg-secondary-a50: var(--bg-secondary-a50);
980
+ --color-bg-secondary-a80: var(--bg-secondary-a80);
981
+ --color-bg-selected: var(--bg-selected);
982
+ --color-bg-success: var(--bg-success);
983
+ --color-bg-success-bright: var(--bg-success-bright);
984
+ --color-bg-success-subtle: var(--bg-success-subtle);
985
+ --color-bg-sunken: var(--bg-sunken);
986
+ --color-bg-surface: var(--bg-surface);
987
+ --color-bg-teal-subtle: var(--bg-teal-subtle);
988
+ --color-bg-violet-subtle: var(--bg-violet-subtle);
989
+ --color-bg-warning: var(--bg-warning);
990
+ --color-bg-warning-subtle: var(--bg-warning-subtle);
991
+ --color-border-brand: var(--border-brand);
992
+ --color-border-danger: var(--border-danger);
993
+ --color-border-default: var(--border-default);
994
+ --color-border-focus: var(--border-focus);
995
+ --color-border-selected: var(--border-selected);
996
+ --color-border-strong: var(--border-strong);
997
+ --color-border-subtle: var(--border-subtle);
998
+ --color-border-success: var(--border-success);
999
+ --color-border-warning: var(--border-warning);
1000
+ --color-field-bg: var(--field-bg);
1001
+ --color-field-bg-focus: var(--field-bg-focus);
1002
+ --color-field-bg-hover: var(--field-bg-hover);
1003
+ --color-field-bg-valid: var(--field-bg-valid);
1004
+ --color-field-border: var(--field-border);
1005
+ --color-field-border-error: var(--field-border-error);
1006
+ --color-field-border-filled: var(--field-border-filled);
1007
+ --color-field-border-focus: var(--field-border-focus);
1008
+ --color-field-border-hover: var(--field-border-hover);
1009
+ --color-field-border-success: var(--field-border-success);
1010
+ --color-field-ring-error: var(--field-ring-error);
1011
+ --color-field-ring-focus: var(--field-ring-focus);
1012
+ --color-field-ring-success: var(--field-ring-success);
1013
+ --color-icon-brand: var(--icon-brand);
1014
+ --color-icon-danger: var(--icon-danger);
1015
+ --color-icon-disabled: var(--icon-disabled);
1016
+ --color-icon-primary: var(--icon-primary);
1017
+ --color-icon-secondary: var(--icon-secondary);
1018
+ --color-icon-success: var(--icon-success);
1019
+ --color-icon-warning: var(--icon-warning);
1020
+ --color-overlay-backdrop: var(--overlay-backdrop);
1021
+ --color-overlay-modal-bg: var(--overlay-modal-bg);
1022
+ --color-overlay-scrim: var(--overlay-scrim);
1023
+ --color-text-brand: var(--text-brand);
1024
+ --color-text-danger: var(--text-danger);
1025
+ --color-text-disabled: var(--text-disabled);
1026
+ --color-text-inverse: var(--text-inverse);
1027
+ --color-text-link: var(--text-link);
1028
+ --color-text-link-hover: var(--text-link-hover);
1029
+ --color-text-on-brand: var(--text-on-brand);
1030
+ --color-text-on-danger: var(--text-on-danger);
1031
+ --color-text-on-success: var(--text-on-success);
1032
+ --color-text-on-success-bright: var(--text-on-success-bright);
1033
+ --color-text-on-warning: var(--text-on-warning);
1034
+ --color-text-primary: var(--text-primary);
1035
+ --color-text-rose: var(--text-rose);
1036
+ --color-text-secondary: var(--text-secondary);
1037
+ --color-text-success: var(--text-success);
1038
+ --color-text-teal: var(--text-teal);
1039
+ --color-text-tertiary: var(--text-tertiary);
1040
+ --color-text-violet: var(--text-violet);
1041
+ --color-text-warning: var(--text-warning);
1042
+ }
package/dist/styles.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /* === @lessly/ui styles — generated by scripts/build-styles.mjs === */
2
2
  /* @lessly/tokens theme (composite .text-<style> classes stripped) + deprecated
3
- --color-* aliases + ui pieces + animation layer */
3
+ --color-* aliases + ui pieces + animation layer + v4 @theme bridge */
4
4
 
5
5
  /*
6
6
  * Auto-generated by tokens/build.js — do not edit manually.
@@ -572,6 +572,17 @@
572
572
  --spacing: 0.25rem;
573
573
  }
574
574
  :root {
575
+ /* === Translucent tints the kit paints (#701) === */
576
+ /* A semantic token is a whole colour with no `<alpha-value>` placeholder, so `bg-bg-secondary/50`
577
+ compiles to nothing on Tailwind 3 while Tailwind 4 answers it with color-mix. These are the
578
+ three alphas the kit writes, as variables the preset maps a colour name onto so both majors
579
+ and both consumption paths resolve them. Like --hov-bg below, the inner var() resolves at the
580
+ element that paints, so `.light` needs no override. Delete each the release @lessly/tokens
581
+ publishes its semantic colours with an `<alpha-value>` placeholder. */
582
+ --bg-secondary-a50: color-mix(in oklab, var(--bg-secondary) 50%, transparent);
583
+ --bg-secondary-a80: color-mix(in oklab, var(--bg-secondary) 80%, transparent);
584
+ --bg-danger-a90: color-mix(in oklab, var(--bg-danger) 90%, transparent);
585
+
575
586
  /* === Nav / menu surfaces (0.3.0 sidebar) === */
576
587
  /* The floating surface sits on the package's overlay pair: --menu-bg on
577
588
  --bg-overlay, --menu-shadow on --shadow-overlay. theme.css re-resolves both
@@ -640,6 +651,15 @@
640
651
  transition-timing-function: var(--motion-easing-standard);
641
652
  }
642
653
 
654
+ /* === Layout === */
655
+ /* Backs the <PageColumn> primitive: the reading column, capped and centred in the well. A real class
656
+ rather than a Tailwind arbitrary utility, which the content scanner can drop for a newly-used
657
+ class. Horizontal padding is deliberately absent — AppShell owns the inset. */
658
+ .page-column {
659
+ max-width: 780px;
660
+ margin-inline: auto;
661
+ }
662
+
643
663
  /* === Animation layer (tw-animate-css, compiled) — see scripts/build-styles.mjs === */
644
664
  /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
645
665
  @layer properties;
@@ -982,3 +1002,88 @@
982
1002
  }
983
1003
  }
984
1004
  }
1005
+
1006
+ /* === Tailwind v4 theme bridge — generated by scripts/build-styles.mjs === */
1007
+ /* Colour utilities exist in v4 only for names in the `@theme` namespace; the deprecated
1008
+ --color-* aliases above are :root properties, which is not the same thing. `inline` so a
1009
+ utility substitutes the token at the element it paints rather than freezing it on :root,
1010
+ which is what keeps a `.light` scope inside the page working. Permanent, and inert on
1011
+ Tailwind 3. */
1012
+ @theme inline {
1013
+ --color-bg-brand: var(--bg-brand);
1014
+ --color-bg-brand-bright: var(--bg-brand-bright);
1015
+ --color-bg-brand-bright-hover: var(--bg-brand-bright-hover);
1016
+ --color-bg-brand-hover: var(--bg-brand-hover);
1017
+ --color-bg-brand-subtle: var(--bg-brand-subtle);
1018
+ --color-bg-danger: var(--bg-danger);
1019
+ --color-bg-danger-a90: var(--bg-danger-a90);
1020
+ --color-bg-danger-subtle: var(--bg-danger-subtle);
1021
+ --color-bg-elevated: var(--bg-elevated);
1022
+ --color-bg-overlay: var(--bg-overlay);
1023
+ --color-bg-primary: var(--bg-primary);
1024
+ --color-bg-rose-subtle: var(--bg-rose-subtle);
1025
+ --color-bg-secondary: var(--bg-secondary);
1026
+ --color-bg-secondary-a50: var(--bg-secondary-a50);
1027
+ --color-bg-secondary-a80: var(--bg-secondary-a80);
1028
+ --color-bg-selected: var(--bg-selected);
1029
+ --color-bg-success: var(--bg-success);
1030
+ --color-bg-success-bright: var(--bg-success-bright);
1031
+ --color-bg-success-subtle: var(--bg-success-subtle);
1032
+ --color-bg-sunken: var(--bg-sunken);
1033
+ --color-bg-surface: var(--bg-surface);
1034
+ --color-bg-teal-subtle: var(--bg-teal-subtle);
1035
+ --color-bg-violet-subtle: var(--bg-violet-subtle);
1036
+ --color-bg-warning: var(--bg-warning);
1037
+ --color-bg-warning-subtle: var(--bg-warning-subtle);
1038
+ --color-border-brand: var(--border-brand);
1039
+ --color-border-danger: var(--border-danger);
1040
+ --color-border-default: var(--border-default);
1041
+ --color-border-focus: var(--border-focus);
1042
+ --color-border-selected: var(--border-selected);
1043
+ --color-border-strong: var(--border-strong);
1044
+ --color-border-subtle: var(--border-subtle);
1045
+ --color-border-success: var(--border-success);
1046
+ --color-border-warning: var(--border-warning);
1047
+ --color-field-bg: var(--field-bg);
1048
+ --color-field-bg-focus: var(--field-bg-focus);
1049
+ --color-field-bg-hover: var(--field-bg-hover);
1050
+ --color-field-bg-valid: var(--field-bg-valid);
1051
+ --color-field-border: var(--field-border);
1052
+ --color-field-border-error: var(--field-border-error);
1053
+ --color-field-border-filled: var(--field-border-filled);
1054
+ --color-field-border-focus: var(--field-border-focus);
1055
+ --color-field-border-hover: var(--field-border-hover);
1056
+ --color-field-border-success: var(--field-border-success);
1057
+ --color-field-ring-error: var(--field-ring-error);
1058
+ --color-field-ring-focus: var(--field-ring-focus);
1059
+ --color-field-ring-success: var(--field-ring-success);
1060
+ --color-icon-brand: var(--icon-brand);
1061
+ --color-icon-danger: var(--icon-danger);
1062
+ --color-icon-disabled: var(--icon-disabled);
1063
+ --color-icon-primary: var(--icon-primary);
1064
+ --color-icon-secondary: var(--icon-secondary);
1065
+ --color-icon-success: var(--icon-success);
1066
+ --color-icon-warning: var(--icon-warning);
1067
+ --color-overlay-backdrop: var(--overlay-backdrop);
1068
+ --color-overlay-modal-bg: var(--overlay-modal-bg);
1069
+ --color-overlay-scrim: var(--overlay-scrim);
1070
+ --color-text-brand: var(--text-brand);
1071
+ --color-text-danger: var(--text-danger);
1072
+ --color-text-disabled: var(--text-disabled);
1073
+ --color-text-inverse: var(--text-inverse);
1074
+ --color-text-link: var(--text-link);
1075
+ --color-text-link-hover: var(--text-link-hover);
1076
+ --color-text-on-brand: var(--text-on-brand);
1077
+ --color-text-on-danger: var(--text-on-danger);
1078
+ --color-text-on-success: var(--text-on-success);
1079
+ --color-text-on-success-bright: var(--text-on-success-bright);
1080
+ --color-text-on-warning: var(--text-on-warning);
1081
+ --color-text-primary: var(--text-primary);
1082
+ --color-text-rose: var(--text-rose);
1083
+ --color-text-secondary: var(--text-secondary);
1084
+ --color-text-success: var(--text-success);
1085
+ --color-text-teal: var(--text-teal);
1086
+ --color-text-tertiary: var(--text-tertiary);
1087
+ --color-text-violet: var(--text-violet);
1088
+ --color-text-warning: var(--text-warning);
1089
+ }