@lessly/ui 3.0.0 → 4.1.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/{chunk-CEXND5YW.js → chunk-LTHOLX7I.js} +223 -28
- package/dist/index.d.ts +359 -57
- package/dist/index.js +886 -752
- package/dist/router.d.ts +27 -17
- package/dist/router.js +72 -98
- package/dist/styles-federated.css +63 -10
- package/dist/styles.css +63 -16
- package/package.json +14 -14
package/dist/router.d.ts
CHANGED
|
@@ -54,28 +54,38 @@ interface SidebarNavProps {
|
|
|
54
54
|
isItemActive?: (item: NavItem, pathname: string) => boolean;
|
|
55
55
|
className?: string;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The rail's rows, and the part of it that reads the router: it takes `useLocation`, marks the row
|
|
59
|
+
* whose path is open, and renders each through `LinkComponent`. That import is why it and
|
|
60
|
+
* `AppSidebar` ship from `@lessly/ui/router` rather than the main barrel.
|
|
61
|
+
*
|
|
62
|
+
* **Beside `AppSidebar`.** `AppSidebar` is the rail itself — the identity slot above the rows, the
|
|
63
|
+
* collapse toggle at the foot, the dividers and the two widths — and it renders this component for
|
|
64
|
+
* the rows in the middle. Take `AppSidebar` for the console's rail; take `SidebarNav`
|
|
65
|
+
* when the surrounding chrome is yours and only the rows and their active-path rule are not, or
|
|
66
|
+
* when one rail carries a second list. Either way the row is `NavRow`, so the two cannot drift.
|
|
67
|
+
*/
|
|
57
68
|
declare function SidebarNav({ items, collapsed, basePath, LinkComponent, isItemActive, className, }: SidebarNavProps): React.JSX.Element;
|
|
58
69
|
|
|
59
70
|
interface AppSidebarProps {
|
|
60
71
|
items: NavItem[];
|
|
61
|
-
/** Expanded-slot mark/wordmark, rendered when `header` is absent. Also the
|
|
62
|
-
*
|
|
63
|
-
* `logoCollapsed`. */
|
|
72
|
+
/** Expanded-slot mark/wordmark, rendered when `header` is absent. Also the narrow rail's fallback
|
|
73
|
+
* when `logoCollapsed` isn't given. */
|
|
64
74
|
logo?: React.ReactNode;
|
|
65
|
-
/**
|
|
66
|
-
*
|
|
67
|
-
* one box, and the way back to expanded is what it holds. */
|
|
75
|
+
/** Narrow-rail identity — a small mark, for the width where `header` has no room to be read.
|
|
76
|
+
* Falls back to `logo`. */
|
|
68
77
|
logoCollapsed?: React.ReactNode;
|
|
69
78
|
/** Identity slot above the rail — e.g. an <OrgProductSwitcher/> (#266). Falls back to `logo`.
|
|
70
|
-
* Expanded-only:
|
|
71
|
-
*
|
|
72
|
-
* `logoCollapsed ?? logo` when it is not. */
|
|
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. */
|
|
73
81
|
header?: React.ReactNode;
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
|
|
82
|
+
/** Top of the rail — e.g. a <QuickSearchRow/>. It sits between the header's rule and the rows,
|
|
83
|
+
* and it is pinned: outside the scroll region, so it holds one place while the rows below it
|
|
84
|
+
* scroll. No rule of its own — what is above it belongs to the organization and what is below
|
|
85
|
+
* is the rail, while this is the rail's own first row, and a line would file it elsewhere.
|
|
86
|
+
* Unlike `header` it is drawn at both widths, so it takes a function to read `collapsed` with —
|
|
87
|
+
* the state a caller has no other way to reach when the rail drives its own collapse. */
|
|
88
|
+
railTop?: React.ReactNode | ((collapsed: boolean) => React.ReactNode);
|
|
79
89
|
collapsed?: boolean;
|
|
80
90
|
defaultCollapsed?: boolean;
|
|
81
91
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
@@ -84,11 +94,11 @@ interface AppSidebarProps {
|
|
|
84
94
|
isItemActive?: (item: NavItem, pathname: string) => boolean;
|
|
85
95
|
className?: string;
|
|
86
96
|
}
|
|
87
|
-
declare function AppSidebar({ items, logo, logoCollapsed, header,
|
|
97
|
+
declare function AppSidebar({ items, logo, logoCollapsed, header, railTop, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React.JSX.Element;
|
|
88
98
|
|
|
89
99
|
interface AppShellProps {
|
|
90
|
-
/** The sidebar, typically an <AppSidebar/>. Rendered in the desktop rail and,
|
|
91
|
-
*
|
|
100
|
+
/** The sidebar, typically an <AppSidebar/>. Rendered in the desktop rail and, on mobile, inside a
|
|
101
|
+
* Sheet — where an <AppSidebar/> at any depth draws the drawer's shape rather than the rail's. */
|
|
92
102
|
sidebar: React.ReactNode;
|
|
93
103
|
topBar?: React.ReactNode;
|
|
94
104
|
children?: React.ReactNode;
|
package/dist/router.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Button,
|
|
3
3
|
NavRow,
|
|
4
|
+
RAIL_COLUMN_BOX,
|
|
5
|
+
RailToggle,
|
|
6
|
+
ScrollArea,
|
|
4
7
|
Sheet,
|
|
5
8
|
SheetContent,
|
|
6
9
|
SheetDescription,
|
|
@@ -13,7 +16,7 @@ import {
|
|
|
13
16
|
cn,
|
|
14
17
|
useIsMobile,
|
|
15
18
|
useSidebar
|
|
16
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-LTHOLX7I.js";
|
|
17
20
|
|
|
18
21
|
// src/components/extension-link.tsx
|
|
19
22
|
import { Link } from "react-router";
|
|
@@ -52,7 +55,7 @@ function SidebarNavLink({
|
|
|
52
55
|
label: item.label,
|
|
53
56
|
hideLabel: collapsed,
|
|
54
57
|
icon: Icon ? /* @__PURE__ */ jsx2(Icon, { "aria-hidden": "true" }) : void 0,
|
|
55
|
-
className
|
|
58
|
+
className,
|
|
56
59
|
asChild: true,
|
|
57
60
|
children: /* @__PURE__ */ jsx2(Comp, { to, "aria-current": active ? "page" : void 0 })
|
|
58
61
|
}
|
|
@@ -72,57 +75,47 @@ function SidebarNav({
|
|
|
72
75
|
className
|
|
73
76
|
}) {
|
|
74
77
|
const { pathname } = useLocation();
|
|
75
|
-
const nav =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
},
|
|
95
|
-
item.href
|
|
96
|
-
);
|
|
97
|
-
})
|
|
98
|
-
}
|
|
78
|
+
const nav = (
|
|
79
|
+
// One layout at both widths. A row is `w-full` and its glyph sits at the row's own left
|
|
80
|
+
// padding, so the column a mark stands on is a fact about the rail around these rows and not
|
|
81
|
+
// about their width — a narrow box of their own is what took the glyph off that column.
|
|
82
|
+
/* @__PURE__ */ jsx2("nav", { className: cn("flex flex-col gap-2", className), children: items.map((item) => {
|
|
83
|
+
const full = `${basePath}${item.href}`;
|
|
84
|
+
const active = isItemActive ? isItemActive(item, pathname) : isActivePath(pathname, full, item.exact);
|
|
85
|
+
return /* @__PURE__ */ jsx2(
|
|
86
|
+
SidebarNavLink,
|
|
87
|
+
{
|
|
88
|
+
item,
|
|
89
|
+
active,
|
|
90
|
+
collapsed,
|
|
91
|
+
basePath,
|
|
92
|
+
LinkComponent
|
|
93
|
+
},
|
|
94
|
+
item.href
|
|
95
|
+
);
|
|
96
|
+
}) })
|
|
99
97
|
);
|
|
100
98
|
return collapsed ? /* @__PURE__ */ jsx2(TooltipProvider, { children: nav }) : nav;
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
// src/components/app-sidebar.tsx
|
|
104
|
-
import
|
|
102
|
+
import * as React from "react";
|
|
105
103
|
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"h-px shrink-0 bg-border-subtle",
|
|
113
|
-
collapsed ? "ml-[6px] w-[40px]" : "ml-[14px] w-[220px]",
|
|
114
|
-
className
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
);
|
|
104
|
+
var SidebarDrawerContext = React.createContext(false);
|
|
105
|
+
function SidebarDrawer({ children }) {
|
|
106
|
+
return /* @__PURE__ */ jsx3(SidebarDrawerContext.Provider, { value: true, children });
|
|
107
|
+
}
|
|
108
|
+
function Divider({ testId, className }) {
|
|
109
|
+
return /* @__PURE__ */ jsx3("div", { "data-testid": testId, className: cn("h-px shrink-0 bg-border-subtle", className) });
|
|
118
110
|
}
|
|
111
|
+
var RAIL_WIDE = "w-[248px]";
|
|
112
|
+
var RAIL_NARROW = "w-[64px]";
|
|
119
113
|
function AppSidebar({
|
|
120
114
|
items,
|
|
121
115
|
logo,
|
|
122
116
|
logoCollapsed,
|
|
123
117
|
header,
|
|
124
|
-
|
|
125
|
-
collapsible = false,
|
|
118
|
+
railTop,
|
|
126
119
|
collapsed: controlled,
|
|
127
120
|
defaultCollapsed,
|
|
128
121
|
onCollapsedChange,
|
|
@@ -131,89 +124,67 @@ function AppSidebar({
|
|
|
131
124
|
isItemActive,
|
|
132
125
|
className
|
|
133
126
|
}) {
|
|
134
|
-
const
|
|
127
|
+
const drawer = React.useContext(SidebarDrawerContext);
|
|
128
|
+
const { collapsed: railCollapsed, toggle } = useSidebar({
|
|
135
129
|
collapsed: controlled,
|
|
136
130
|
defaultCollapsed,
|
|
137
131
|
onCollapsedChange
|
|
138
132
|
});
|
|
139
|
-
const
|
|
140
|
-
|
|
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 duration-fast 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(
|
|
133
|
+
const collapsed = drawer ? false : railCollapsed;
|
|
134
|
+
const railTopNode = typeof railTop === "function" ? railTop(collapsed) : railTop;
|
|
135
|
+
return /* @__PURE__ */ jsx3(
|
|
151
136
|
"aside",
|
|
152
137
|
{
|
|
153
138
|
"data-collapsed": collapsed,
|
|
154
139
|
className: cn(
|
|
155
|
-
"flex h-full shrink-0 flex-col overflow-hidden
|
|
156
|
-
|
|
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",
|
|
157
147
|
className
|
|
158
148
|
),
|
|
159
|
-
children: [
|
|
160
|
-
/* @__PURE__ */ jsx3("div", { className: cn("flex h-14 items-center gap-2",
|
|
161
|
-
|
|
162
|
-
|
|
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 duration-fast 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" }),
|
|
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 }),
|
|
180
153
|
/* @__PURE__ */ jsx3(
|
|
181
|
-
|
|
154
|
+
ScrollArea,
|
|
182
155
|
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
collapsed ? "px-1" : "px-3"
|
|
187
|
-
),
|
|
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" },
|
|
188
159
|
children: /* @__PURE__ */ jsx3(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive })
|
|
189
160
|
}
|
|
190
161
|
),
|
|
191
|
-
|
|
192
|
-
/* @__PURE__ */ jsx3(Divider, {
|
|
193
|
-
/* @__PURE__ */ jsx3("div", { className:
|
|
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 }) })
|
|
194
165
|
] })
|
|
195
|
-
]
|
|
166
|
+
] })
|
|
196
167
|
}
|
|
197
168
|
);
|
|
198
169
|
}
|
|
199
170
|
|
|
200
171
|
// src/components/app-shell.tsx
|
|
201
|
-
import * as
|
|
172
|
+
import * as React2 from "react";
|
|
202
173
|
import { Menu } from "lucide-react";
|
|
203
174
|
import { useLocation as useLocation2 } from "react-router";
|
|
204
|
-
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
175
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
205
176
|
function expandedSidebar(sidebar) {
|
|
206
|
-
return
|
|
177
|
+
return React2.isValidElement(sidebar) ? React2.cloneElement(sidebar, { collapsed: false }) : sidebar;
|
|
207
178
|
}
|
|
208
179
|
function AppShell({ sidebar, topBar, children, className }) {
|
|
209
180
|
const { pathname } = useLocation2();
|
|
210
|
-
const [mobileOpen, setMobileOpen] =
|
|
211
|
-
|
|
181
|
+
const [mobileOpen, setMobileOpen] = React2.useState(false);
|
|
182
|
+
React2.useEffect(() => {
|
|
212
183
|
setMobileOpen(false);
|
|
213
184
|
}, [pathname]);
|
|
214
185
|
const isMobile = useIsMobile();
|
|
215
186
|
if (isMobile) {
|
|
216
|
-
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen flex-col bg-bg-
|
|
187
|
+
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen flex-col bg-bg-primary", className), children: [
|
|
217
188
|
/* @__PURE__ */ jsxs3(
|
|
218
189
|
"header",
|
|
219
190
|
{
|
|
@@ -222,10 +193,10 @@ function AppShell({ sidebar, topBar, children, className }) {
|
|
|
222
193
|
children: [
|
|
223
194
|
/* @__PURE__ */ jsxs3(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
|
|
224
195
|
/* @__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-
|
|
196
|
+
/* @__PURE__ */ jsxs3(SheetContent, { side: "left", className: "w-[248px] bg-bg-primary p-0", children: [
|
|
226
197
|
/* @__PURE__ */ jsx4(SheetTitle, { className: "sr-only", children: "Navigation" }),
|
|
227
198
|
/* @__PURE__ */ jsx4(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
|
|
228
|
-
expandedSidebar(sidebar)
|
|
199
|
+
/* @__PURE__ */ jsx4(SidebarDrawer, { children: expandedSidebar(sidebar) })
|
|
229
200
|
] })
|
|
230
201
|
] }),
|
|
231
202
|
/* @__PURE__ */ jsx4("div", { className: "flex items-center gap-2", children: topBar })
|
|
@@ -235,10 +206,13 @@ function AppShell({ sidebar, topBar, children, className }) {
|
|
|
235
206
|
/* @__PURE__ */ jsx4("main", { className: "flex-1 overflow-auto px-4", children })
|
|
236
207
|
] }) });
|
|
237
208
|
}
|
|
238
|
-
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen
|
|
239
|
-
/* @__PURE__ */ jsx4("div", { className: "sticky top-
|
|
209
|
+
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen bg-bg-primary", className), children: [
|
|
210
|
+
/* @__PURE__ */ jsx4("div", { className: "sticky top-0 h-screen shrink-0 border-r border-border-subtle", children: sidebar }),
|
|
240
211
|
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: [
|
|
241
|
-
topBar && /* @__PURE__ */
|
|
212
|
+
topBar && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
213
|
+
/* @__PURE__ */ jsx4("div", { "data-slot": "app-shell-topbar", className: "flex h-14 shrink-0 items-center gap-2 px-6", children: topBar }),
|
|
214
|
+
/* @__PURE__ */ jsx4("div", { "data-slot": "app-shell-topbar-rule", className: "h-px shrink-0 bg-border-subtle" })
|
|
215
|
+
] }),
|
|
242
216
|
/* @__PURE__ */ jsx4("main", { className: "flex-1 overflow-y-auto px-6", children })
|
|
243
217
|
] })
|
|
244
218
|
] }) });
|
|
@@ -514,7 +514,6 @@
|
|
|
514
514
|
|
|
515
515
|
|
|
516
516
|
|
|
517
|
-
|
|
518
517
|
/* === Theme Variables === */
|
|
519
518
|
/* The token palettes (colors, typography, motion, shadows, grid) live in
|
|
520
519
|
@lessly/tokens/theme.css — dark `:root` + `.light`, unprefixed names
|
|
@@ -535,8 +534,8 @@
|
|
|
535
534
|
/* A semantic token is a whole colour with no `<alpha-value>` placeholder, so `bg-bg-secondary/50`
|
|
536
535
|
compiles to nothing on Tailwind 3 while Tailwind 4 answers it with color-mix. These are the
|
|
537
536
|
three alphas the kit writes, as variables the preset maps a colour name onto so both majors
|
|
538
|
-
and both consumption paths resolve them.
|
|
539
|
-
|
|
537
|
+
and both consumption paths resolve them. The inner var() resolves on the element `:root` and
|
|
538
|
+
`.light` both match, so `.light` needs no override. Delete each the release @lessly/tokens
|
|
540
539
|
publishes its semantic colours with an `<alpha-value>` placeholder. */
|
|
541
540
|
--bg-secondary-a50: color-mix(in oklab, var(--bg-secondary) 50%, transparent);
|
|
542
541
|
--bg-secondary-a80: color-mix(in oklab, var(--bg-secondary) 80%, transparent);
|
|
@@ -545,8 +544,32 @@
|
|
|
545
544
|
/* === Nav / menu surfaces (0.3.0 sidebar) === */
|
|
546
545
|
/* The floating surface sits on the package's overlay pair: --menu-bg on
|
|
547
546
|
--bg-overlay, --menu-shadow on --shadow-overlay. theme.css re-resolves both
|
|
548
|
-
per theme, so `.light` needs no override for either.
|
|
549
|
-
|
|
547
|
+
per theme, so `.light` needs no override for either.
|
|
548
|
+
|
|
549
|
+
The pointer contrasts with whatever it lands on, and the direction flips by itself: this is a
|
|
550
|
+
wash of --text-primary, which is light ink in dark and dark ink in light, so one declaration
|
|
551
|
+
lightens on a dark ground and darkens on a light one. A wash rather than a rung because it is
|
|
552
|
+
ground-relative by construction — the same 4% reads on the page, a rail, a card, a menu and a
|
|
553
|
+
nested card alike, where every opaque step fails on at least one of them (--bg-secondary is a
|
|
554
|
+
nested card's own fill, so a hover painted with it disappears there). Measured: dark 1.099:1
|
|
555
|
+
up from the page and 1.117:1 from a card; light 1.083:1 down from the page and 1.090:1 from a
|
|
556
|
+
card (Guidelines/Look/Surface tone).
|
|
557
|
+
|
|
558
|
+
--current-bg is the plate a resting selection takes, and it is the end of the ladder the ink
|
|
559
|
+
points at: the wash above travels in the ink's direction, so the current row is the last rung
|
|
560
|
+
that way and the four states of a rail row read as one walk. Dark ink is light, so the top,
|
|
561
|
+
--bg-overlay, 1.184:1 up from the page. Light ink is dark, so the bottom, --bg-sunken,
|
|
562
|
+
1.208:1 down. Two declarations because the two ends are two different tokens, not because the
|
|
563
|
+
rule differs.
|
|
564
|
+
|
|
565
|
+
--hov-current is what the pointer paints on the row that already holds that plate, and the 4%
|
|
566
|
+
cap is why it has to exist: in dark the whole gap between the page and the plate is 1.184:1,
|
|
567
|
+
so a hover step big enough to be seen ON the plate would overshoot it on the ground. Same
|
|
568
|
+
rule, one layer up — the wash taken over --current-bg rather than over the ground, which is
|
|
569
|
+
also what carries the pointer past a rung the ladder has no successor for. */
|
|
570
|
+
--hov-bg: color-mix(in srgb, var(--text-primary) 4%, transparent);
|
|
571
|
+
--current-bg: var(--bg-overlay);
|
|
572
|
+
--hov-current: color-mix(in srgb, var(--text-primary) 4%, var(--current-bg));
|
|
550
573
|
--menu-bg: var(--bg-overlay);
|
|
551
574
|
--menu-shadow: var(--shadow-overlay);
|
|
552
575
|
--menu-hov: var(--hov-bg);
|
|
@@ -565,11 +588,10 @@
|
|
|
565
588
|
}
|
|
566
589
|
|
|
567
590
|
:where(.light) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
--
|
|
572
|
-
--menu-hov: #f2f4f7;
|
|
591
|
+
|
|
592
|
+
/* The other end of the ladder — see --current-bg in :root above. --hov-current is declared once
|
|
593
|
+
and reads this, so the pointer's step off the plate follows without a second copy. */
|
|
594
|
+
--current-bg: var(--bg-sunken);
|
|
573
595
|
|
|
574
596
|
/* Auth screen — see :root above. */
|
|
575
597
|
--auth-glow-a: rgba(11, 76, 213, 0.09);
|
|
@@ -604,6 +626,19 @@
|
|
|
604
626
|
transition-timing-function: var(--motion-easing-standard);
|
|
605
627
|
}
|
|
606
628
|
|
|
629
|
+
/* Backs AppSidebar's collapse: the rail's width, and the bar in the toggle's mark that crosses the
|
|
630
|
+
frame as it travels. One class on both, so the mark cannot be retuned away from the edge it
|
|
631
|
+
answers for — the same reason two carets read one `caretMotion`. `transform` is here rather than
|
|
632
|
+
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. */
|
|
636
|
+
.motion-rail-collapse {
|
|
637
|
+
transition-property: width, transform;
|
|
638
|
+
transition-duration: var(--motion-duration-normal);
|
|
639
|
+
transition-timing-function: var(--motion-easing-standard);
|
|
640
|
+
}
|
|
641
|
+
|
|
607
642
|
/* Backs the `useFreshHighlight` hook: a freshly-created row gets a brief background wash that fades
|
|
608
643
|
back to the resting surface, so the eye lands on the thing you just made. The tint is driven by
|
|
609
644
|
--fresh-tint (default the amber wash — closest to GitHub's yellow flash, chosen over the
|
|
@@ -641,6 +676,24 @@
|
|
|
641
676
|
margin-inline: auto;
|
|
642
677
|
}
|
|
643
678
|
|
|
679
|
+
/* Backs the overlay scrollbar <ScrollArea> draws: the bar rides over the content and is only there
|
|
680
|
+
while it is wanted — while you scroll, and while the pointer is over the region — then fades. A
|
|
681
|
+
real class rather than a Tailwind arbitrary utility for the reason the motion classes above are,
|
|
682
|
+
and token-driven so prefers-reduced-motion takes the fade to 0ms and the bar simply appears. */
|
|
683
|
+
.motion-scroll-overlay {
|
|
684
|
+
transition-property: opacity;
|
|
685
|
+
transition-duration: var(--motion-duration-normal);
|
|
686
|
+
transition-timing-function: var(--motion-easing-standard);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* Radix's viewport wraps its children in a `display: table` div, which is how it lets content
|
|
690
|
+
wider than the region push the horizontal bar. A rail is the opposite case: its rows are as wide
|
|
691
|
+
as the rail and a table row is as wide as its widest cell, so a long label stops truncating and
|
|
692
|
+
spills. `display: block` on that wrapper gives the rows the region's width back. */
|
|
693
|
+
.scroll-overlay-viewport > div {
|
|
694
|
+
display: block !important;
|
|
695
|
+
}
|
|
696
|
+
|
|
644
697
|
/* === Animation layer (tw-animate-css, compiled) — see scripts/build-styles.mjs === */
|
|
645
698
|
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
|
646
699
|
@layer properties;
|
package/dist/styles.css
CHANGED
|
@@ -534,13 +534,6 @@
|
|
|
534
534
|
font-style: normal;
|
|
535
535
|
font-display: swap;
|
|
536
536
|
}
|
|
537
|
-
@font-face {
|
|
538
|
-
font-family: "Instrument Serif";
|
|
539
|
-
src: url("/fonts/instrument-serif/instrument-serif-regular.woff2") format("woff2");
|
|
540
|
-
font-weight: 400;
|
|
541
|
-
font-style: normal;
|
|
542
|
-
font-display: swap;
|
|
543
|
-
}
|
|
544
537
|
@font-face {
|
|
545
538
|
font-family: "Fira Mono";
|
|
546
539
|
src: url("/fonts/fira-mono/fira-mono-regular.woff2") format("woff2");
|
|
@@ -576,8 +569,8 @@
|
|
|
576
569
|
/* A semantic token is a whole colour with no `<alpha-value>` placeholder, so `bg-bg-secondary/50`
|
|
577
570
|
compiles to nothing on Tailwind 3 while Tailwind 4 answers it with color-mix. These are the
|
|
578
571
|
three alphas the kit writes, as variables the preset maps a colour name onto so both majors
|
|
579
|
-
and both consumption paths resolve them.
|
|
580
|
-
|
|
572
|
+
and both consumption paths resolve them. The inner var() resolves on the element `:root` and
|
|
573
|
+
`.light` both match, so `.light` needs no override. Delete each the release @lessly/tokens
|
|
581
574
|
publishes its semantic colours with an `<alpha-value>` placeholder. */
|
|
582
575
|
--bg-secondary-a50: color-mix(in oklab, var(--bg-secondary) 50%, transparent);
|
|
583
576
|
--bg-secondary-a80: color-mix(in oklab, var(--bg-secondary) 80%, transparent);
|
|
@@ -586,8 +579,32 @@
|
|
|
586
579
|
/* === Nav / menu surfaces (0.3.0 sidebar) === */
|
|
587
580
|
/* The floating surface sits on the package's overlay pair: --menu-bg on
|
|
588
581
|
--bg-overlay, --menu-shadow on --shadow-overlay. theme.css re-resolves both
|
|
589
|
-
per theme, so `.light` needs no override for either.
|
|
590
|
-
|
|
582
|
+
per theme, so `.light` needs no override for either.
|
|
583
|
+
|
|
584
|
+
The pointer contrasts with whatever it lands on, and the direction flips by itself: this is a
|
|
585
|
+
wash of --text-primary, which is light ink in dark and dark ink in light, so one declaration
|
|
586
|
+
lightens on a dark ground and darkens on a light one. A wash rather than a rung because it is
|
|
587
|
+
ground-relative by construction — the same 4% reads on the page, a rail, a card, a menu and a
|
|
588
|
+
nested card alike, where every opaque step fails on at least one of them (--bg-secondary is a
|
|
589
|
+
nested card's own fill, so a hover painted with it disappears there). Measured: dark 1.099:1
|
|
590
|
+
up from the page and 1.117:1 from a card; light 1.083:1 down from the page and 1.090:1 from a
|
|
591
|
+
card (Guidelines/Look/Surface tone).
|
|
592
|
+
|
|
593
|
+
--current-bg is the plate a resting selection takes, and it is the end of the ladder the ink
|
|
594
|
+
points at: the wash above travels in the ink's direction, so the current row is the last rung
|
|
595
|
+
that way and the four states of a rail row read as one walk. Dark ink is light, so the top,
|
|
596
|
+
--bg-overlay, 1.184:1 up from the page. Light ink is dark, so the bottom, --bg-sunken,
|
|
597
|
+
1.208:1 down. Two declarations because the two ends are two different tokens, not because the
|
|
598
|
+
rule differs.
|
|
599
|
+
|
|
600
|
+
--hov-current is what the pointer paints on the row that already holds that plate, and the 4%
|
|
601
|
+
cap is why it has to exist: in dark the whole gap between the page and the plate is 1.184:1,
|
|
602
|
+
so a hover step big enough to be seen ON the plate would overshoot it on the ground. Same
|
|
603
|
+
rule, one layer up — the wash taken over --current-bg rather than over the ground, which is
|
|
604
|
+
also what carries the pointer past a rung the ladder has no successor for. */
|
|
605
|
+
--hov-bg: color-mix(in srgb, var(--text-primary) 4%, transparent);
|
|
606
|
+
--current-bg: var(--bg-overlay);
|
|
607
|
+
--hov-current: color-mix(in srgb, var(--text-primary) 4%, var(--current-bg));
|
|
591
608
|
--menu-bg: var(--bg-overlay);
|
|
592
609
|
--menu-shadow: var(--shadow-overlay);
|
|
593
610
|
--menu-hov: var(--hov-bg);
|
|
@@ -606,11 +623,10 @@
|
|
|
606
623
|
}
|
|
607
624
|
|
|
608
625
|
.light {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
--
|
|
613
|
-
--menu-hov: #f2f4f7;
|
|
626
|
+
|
|
627
|
+
/* The other end of the ladder — see --current-bg in :root above. --hov-current is declared once
|
|
628
|
+
and reads this, so the pointer's step off the plate follows without a second copy. */
|
|
629
|
+
--current-bg: var(--bg-sunken);
|
|
614
630
|
|
|
615
631
|
/* Auth screen — see :root above. */
|
|
616
632
|
--auth-glow-a: rgba(11, 76, 213, 0.09);
|
|
@@ -651,6 +667,19 @@
|
|
|
651
667
|
transition-timing-function: var(--motion-easing-standard);
|
|
652
668
|
}
|
|
653
669
|
|
|
670
|
+
/* Backs AppSidebar's collapse: the rail's width, and the bar in the toggle's mark that crosses the
|
|
671
|
+
frame as it travels. One class on both, so the mark cannot be retuned away from the edge it
|
|
672
|
+
answers for — the same reason two carets read one `caretMotion`. `transform` is here rather than
|
|
673
|
+
in a class of its own because the bar is that edge said in miniature: retune one and the two stop
|
|
674
|
+
arriving together. A real class for the reason the two above are, and
|
|
675
|
+
token-driven because a Tailwind `duration-200` is a literal 200ms: under prefers-reduced-motion
|
|
676
|
+
the rail kept sliding for its full 200ms while every token-driven motion beside it went instant. */
|
|
677
|
+
.motion-rail-collapse {
|
|
678
|
+
transition-property: width, transform;
|
|
679
|
+
transition-duration: var(--motion-duration-normal);
|
|
680
|
+
transition-timing-function: var(--motion-easing-standard);
|
|
681
|
+
}
|
|
682
|
+
|
|
654
683
|
/* Backs the `useFreshHighlight` hook: a freshly-created row gets a brief background wash that fades
|
|
655
684
|
back to the resting surface, so the eye lands on the thing you just made. The tint is driven by
|
|
656
685
|
--fresh-tint (default the amber wash — closest to GitHub's yellow flash, chosen over the
|
|
@@ -688,6 +717,24 @@
|
|
|
688
717
|
margin-inline: auto;
|
|
689
718
|
}
|
|
690
719
|
|
|
720
|
+
/* Backs the overlay scrollbar <ScrollArea> draws: the bar rides over the content and is only there
|
|
721
|
+
while it is wanted — while you scroll, and while the pointer is over the region — then fades. A
|
|
722
|
+
real class rather than a Tailwind arbitrary utility for the reason the motion classes above are,
|
|
723
|
+
and token-driven so prefers-reduced-motion takes the fade to 0ms and the bar simply appears. */
|
|
724
|
+
.motion-scroll-overlay {
|
|
725
|
+
transition-property: opacity;
|
|
726
|
+
transition-duration: var(--motion-duration-normal);
|
|
727
|
+
transition-timing-function: var(--motion-easing-standard);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/* Radix's viewport wraps its children in a `display: table` div, which is how it lets content
|
|
731
|
+
wider than the region push the horizontal bar. A rail is the opposite case: its rows are as wide
|
|
732
|
+
as the rail and a table row is as wide as its widest cell, so a long label stops truncating and
|
|
733
|
+
spills. `display: block` on that wrapper gives the rows the region's width back. */
|
|
734
|
+
.scroll-overlay-viewport > div {
|
|
735
|
+
display: block !important;
|
|
736
|
+
}
|
|
737
|
+
|
|
691
738
|
/* === Animation layer (tw-animate-css, compiled) — see scripts/build-styles.mjs === */
|
|
692
739
|
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
|
693
740
|
@layer properties;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessly/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Lessly design system — shared UI primitives, tokens, and theme",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.30.3",
|
|
@@ -34,24 +34,25 @@
|
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsup && node scripts/build-styles.mjs",
|
|
37
|
-
"
|
|
38
|
-
"test": "vitest run",
|
|
39
|
-
"type-check": "tsc --noEmit",
|
|
40
|
-
"lint": "eslint .",
|
|
41
|
-
"prepublishOnly": "pnpm build && pnpm test && node scripts/check-package-contract.mjs",
|
|
42
|
-
"storybook": "storybook dev -p 6006 --no-open",
|
|
37
|
+
"build-site": "pnpm build && vite build --config site/vite.config.ts",
|
|
43
38
|
"build-storybook": "storybook build",
|
|
39
|
+
"build:company-facts": "tsx scripts/build-company-facts.mts",
|
|
40
|
+
"build:current-emails": "tsx scripts/build-current-emails.mts",
|
|
44
41
|
"changeset": "changeset",
|
|
45
|
-
"
|
|
42
|
+
"dev-css-stale": "node scripts/dev-css-stale.mjs",
|
|
43
|
+
"dev-site": "vite --config site/vite.config.ts",
|
|
44
|
+
"gen-changelog": "node site/scripts/gen-changelog.mjs",
|
|
45
|
+
"lint": "eslint .",
|
|
46
|
+
"prepare": "pnpm build",
|
|
47
|
+
"prepublishOnly": "pnpm build && pnpm test && node scripts/check-package-contract.mjs",
|
|
46
48
|
"release:publish": "node scripts/release-publish.mjs",
|
|
47
49
|
"release:tag": "changeset tag",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
50
|
+
"release:version": "changeset version && node scripts/stamp-changelog-date.mjs && pnpm gen-changelog",
|
|
51
|
+
"storybook": "storybook dev -p 6006 --no-open",
|
|
52
|
+
"test": "vitest run",
|
|
51
53
|
"test-site": "node --test site/*.test.mjs",
|
|
52
54
|
"test-site-app": "vitest run --config site/vitest.config.ts",
|
|
53
|
-
"
|
|
54
|
-
"build:email-logo": "tsx scripts/build-email-logo.mts"
|
|
55
|
+
"type-check": "tsc --noEmit"
|
|
55
56
|
},
|
|
56
57
|
"publishConfig": {
|
|
57
58
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -114,7 +115,6 @@
|
|
|
114
115
|
"@eslint/js": "^9.39.5",
|
|
115
116
|
"@react-email/components": "^0.5.6",
|
|
116
117
|
"@react-email/render": "^1.3.2",
|
|
117
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
118
118
|
"@storybook/addon-essentials": "^8.6.14",
|
|
119
119
|
"@storybook/blocks": "8.6.18",
|
|
120
120
|
"@storybook/react": "^8.6.18",
|