@lessly/ui 3.0.0 → 4.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/{chunk-CEXND5YW.js → chunk-DYHP7W3X.js} +175 -28
- package/dist/index.d.ts +334 -51
- package/dist/index.js +837 -670
- package/dist/router.d.ts +27 -17
- package/dist/router.js +69 -94
- package/dist/styles-federated.css +45 -9
- package/dist/styles.css +45 -9
- package/package.json +14 -13
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,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Button,
|
|
3
3
|
NavRow,
|
|
4
|
+
RAIL_COLUMN_BOX,
|
|
5
|
+
RailToggle,
|
|
4
6
|
Sheet,
|
|
5
7
|
SheetContent,
|
|
6
8
|
SheetDescription,
|
|
@@ -13,7 +15,7 @@ import {
|
|
|
13
15
|
cn,
|
|
14
16
|
useIsMobile,
|
|
15
17
|
useSidebar
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-DYHP7W3X.js";
|
|
17
19
|
|
|
18
20
|
// src/components/extension-link.tsx
|
|
19
21
|
import { Link } from "react-router";
|
|
@@ -52,7 +54,7 @@ function SidebarNavLink({
|
|
|
52
54
|
label: item.label,
|
|
53
55
|
hideLabel: collapsed,
|
|
54
56
|
icon: Icon ? /* @__PURE__ */ jsx2(Icon, { "aria-hidden": "true" }) : void 0,
|
|
55
|
-
className
|
|
57
|
+
className,
|
|
56
58
|
asChild: true,
|
|
57
59
|
children: /* @__PURE__ */ jsx2(Comp, { to, "aria-current": active ? "page" : void 0 })
|
|
58
60
|
}
|
|
@@ -72,57 +74,47 @@ function SidebarNav({
|
|
|
72
74
|
className
|
|
73
75
|
}) {
|
|
74
76
|
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
|
-
}
|
|
77
|
+
const nav = (
|
|
78
|
+
// One layout at both widths. A row is `w-full` and its glyph sits at the row's own left
|
|
79
|
+
// padding, so the column a mark stands on is a fact about the rail around these rows and not
|
|
80
|
+
// about their width — a narrow box of their own is what took the glyph off that column.
|
|
81
|
+
/* @__PURE__ */ jsx2("nav", { className: cn("flex flex-col gap-2", className), children: items.map((item) => {
|
|
82
|
+
const full = `${basePath}${item.href}`;
|
|
83
|
+
const active = isItemActive ? isItemActive(item, pathname) : isActivePath(pathname, full, item.exact);
|
|
84
|
+
return /* @__PURE__ */ jsx2(
|
|
85
|
+
SidebarNavLink,
|
|
86
|
+
{
|
|
87
|
+
item,
|
|
88
|
+
active,
|
|
89
|
+
collapsed,
|
|
90
|
+
basePath,
|
|
91
|
+
LinkComponent
|
|
92
|
+
},
|
|
93
|
+
item.href
|
|
94
|
+
);
|
|
95
|
+
}) })
|
|
99
96
|
);
|
|
100
97
|
return collapsed ? /* @__PURE__ */ jsx2(TooltipProvider, { children: nav }) : nav;
|
|
101
98
|
}
|
|
102
99
|
|
|
103
100
|
// src/components/app-sidebar.tsx
|
|
104
|
-
import
|
|
101
|
+
import * as React from "react";
|
|
105
102
|
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
|
-
);
|
|
103
|
+
var SidebarDrawerContext = React.createContext(false);
|
|
104
|
+
function SidebarDrawer({ children }) {
|
|
105
|
+
return /* @__PURE__ */ jsx3(SidebarDrawerContext.Provider, { value: true, children });
|
|
106
|
+
}
|
|
107
|
+
function Divider({ testId, className }) {
|
|
108
|
+
return /* @__PURE__ */ jsx3("div", { "data-testid": testId, className: cn("h-px shrink-0 bg-border-subtle", className) });
|
|
118
109
|
}
|
|
110
|
+
var RAIL_WIDE = "w-[248px]";
|
|
111
|
+
var RAIL_NARROW = "w-[64px]";
|
|
119
112
|
function AppSidebar({
|
|
120
113
|
items,
|
|
121
114
|
logo,
|
|
122
115
|
logoCollapsed,
|
|
123
116
|
header,
|
|
124
|
-
|
|
125
|
-
collapsible = false,
|
|
117
|
+
railTop,
|
|
126
118
|
collapsed: controlled,
|
|
127
119
|
defaultCollapsed,
|
|
128
120
|
onCollapsedChange,
|
|
@@ -131,89 +123,69 @@ function AppSidebar({
|
|
|
131
123
|
isItemActive,
|
|
132
124
|
className
|
|
133
125
|
}) {
|
|
134
|
-
const
|
|
126
|
+
const drawer = React.useContext(SidebarDrawerContext);
|
|
127
|
+
const { collapsed: railCollapsed, toggle } = useSidebar({
|
|
135
128
|
collapsed: controlled,
|
|
136
129
|
defaultCollapsed,
|
|
137
130
|
onCollapsedChange
|
|
138
131
|
});
|
|
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(
|
|
132
|
+
const collapsed = drawer ? false : railCollapsed;
|
|
133
|
+
const railTopNode = typeof railTop === "function" ? railTop(collapsed) : railTop;
|
|
134
|
+
return /* @__PURE__ */ jsx3(
|
|
151
135
|
"aside",
|
|
152
136
|
{
|
|
153
137
|
"data-collapsed": collapsed,
|
|
154
138
|
className: cn(
|
|
155
|
-
"flex h-full shrink-0 flex-col overflow-hidden
|
|
156
|
-
|
|
139
|
+
"flex h-full shrink-0 flex-col overflow-hidden motion-rail-collapse",
|
|
140
|
+
// A nav row drops its own label at the narrow width. What this fades is a label whatever
|
|
141
|
+
// stands in `railTop` keeps mounted at both — QuickSearchRow's, for one — so the closing
|
|
142
|
+
// edge slides over the words instead of cutting through them.
|
|
143
|
+
"[&_[data-slot=nav-row-label]]:transition-opacity [&_[data-slot=nav-row-label]]:duration-fast",
|
|
144
|
+
collapsed ? RAIL_NARROW : RAIL_WIDE,
|
|
145
|
+
collapsed && "[&_[data-slot=nav-row-label]]:opacity-0",
|
|
157
146
|
className
|
|
158
147
|
),
|
|
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" }),
|
|
148
|
+
children: /* @__PURE__ */ jsxs2("div", { className: cn("flex h-full shrink-0 flex-col", RAIL_WIDE), children: [
|
|
149
|
+
/* @__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 }),
|
|
150
|
+
/* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-header" }),
|
|
151
|
+
railTopNode && /* @__PURE__ */ jsx3("div", { "data-testid": "sidebar-rail-top", className: "shrink-0 px-3 pt-3", children: railTopNode }),
|
|
180
152
|
/* @__PURE__ */ jsx3(
|
|
181
153
|
"div",
|
|
182
154
|
{
|
|
183
155
|
"data-testid": "sidebar-scroll",
|
|
184
156
|
className: cn(
|
|
185
|
-
"min-h-0 flex-1 overflow-y-auto overflow-x-hidden
|
|
186
|
-
|
|
157
|
+
"min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-3 pb-2",
|
|
158
|
+
railTopNode ? "pt-2" : "pt-3"
|
|
187
159
|
),
|
|
188
160
|
children: /* @__PURE__ */ jsx3(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive })
|
|
189
161
|
}
|
|
190
162
|
),
|
|
191
|
-
|
|
192
|
-
/* @__PURE__ */ jsx3(Divider, {
|
|
193
|
-
/* @__PURE__ */ jsx3("div", { className:
|
|
163
|
+
!drawer && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
164
|
+
/* @__PURE__ */ jsx3(Divider, { testId: "sidebar-divider-foot" }),
|
|
165
|
+
/* @__PURE__ */ jsx3("div", { className: "flex shrink-0 px-3 py-2", children: /* @__PURE__ */ jsx3(RailToggle, { collapsed, onClick: toggle }) })
|
|
194
166
|
] })
|
|
195
|
-
]
|
|
167
|
+
] })
|
|
196
168
|
}
|
|
197
169
|
);
|
|
198
170
|
}
|
|
199
171
|
|
|
200
172
|
// src/components/app-shell.tsx
|
|
201
|
-
import * as
|
|
173
|
+
import * as React2 from "react";
|
|
202
174
|
import { Menu } from "lucide-react";
|
|
203
175
|
import { useLocation as useLocation2 } from "react-router";
|
|
204
|
-
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
176
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
205
177
|
function expandedSidebar(sidebar) {
|
|
206
|
-
return
|
|
178
|
+
return React2.isValidElement(sidebar) ? React2.cloneElement(sidebar, { collapsed: false }) : sidebar;
|
|
207
179
|
}
|
|
208
180
|
function AppShell({ sidebar, topBar, children, className }) {
|
|
209
181
|
const { pathname } = useLocation2();
|
|
210
|
-
const [mobileOpen, setMobileOpen] =
|
|
211
|
-
|
|
182
|
+
const [mobileOpen, setMobileOpen] = React2.useState(false);
|
|
183
|
+
React2.useEffect(() => {
|
|
212
184
|
setMobileOpen(false);
|
|
213
185
|
}, [pathname]);
|
|
214
186
|
const isMobile = useIsMobile();
|
|
215
187
|
if (isMobile) {
|
|
216
|
-
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen flex-col bg-bg-
|
|
188
|
+
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen flex-col bg-bg-primary", className), children: [
|
|
217
189
|
/* @__PURE__ */ jsxs3(
|
|
218
190
|
"header",
|
|
219
191
|
{
|
|
@@ -222,10 +194,10 @@ function AppShell({ sidebar, topBar, children, className }) {
|
|
|
222
194
|
children: [
|
|
223
195
|
/* @__PURE__ */ jsxs3(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
|
|
224
196
|
/* @__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-
|
|
197
|
+
/* @__PURE__ */ jsxs3(SheetContent, { side: "left", className: "w-[248px] bg-bg-primary p-0", children: [
|
|
226
198
|
/* @__PURE__ */ jsx4(SheetTitle, { className: "sr-only", children: "Navigation" }),
|
|
227
199
|
/* @__PURE__ */ jsx4(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
|
|
228
|
-
expandedSidebar(sidebar)
|
|
200
|
+
/* @__PURE__ */ jsx4(SidebarDrawer, { children: expandedSidebar(sidebar) })
|
|
229
201
|
] })
|
|
230
202
|
] }),
|
|
231
203
|
/* @__PURE__ */ jsx4("div", { className: "flex items-center gap-2", children: topBar })
|
|
@@ -235,10 +207,13 @@ function AppShell({ sidebar, topBar, children, className }) {
|
|
|
235
207
|
/* @__PURE__ */ jsx4("main", { className: "flex-1 overflow-auto px-4", children })
|
|
236
208
|
] }) });
|
|
237
209
|
}
|
|
238
|
-
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen
|
|
239
|
-
/* @__PURE__ */ jsx4("div", { className: "sticky top-
|
|
210
|
+
return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsxs3("div", { className: cn("flex min-h-screen bg-bg-primary", className), children: [
|
|
211
|
+
/* @__PURE__ */ jsx4("div", { className: "sticky top-0 h-screen shrink-0 border-r border-border-subtle", children: sidebar }),
|
|
240
212
|
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: [
|
|
241
|
-
topBar && /* @__PURE__ */
|
|
213
|
+
topBar && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
214
|
+
/* @__PURE__ */ jsx4("div", { "data-slot": "app-shell-topbar", className: "flex h-14 shrink-0 items-center gap-2 px-6", children: topBar }),
|
|
215
|
+
/* @__PURE__ */ jsx4("div", { "data-slot": "app-shell-topbar-rule", className: "h-px shrink-0 bg-border-subtle" })
|
|
216
|
+
] }),
|
|
242
217
|
/* @__PURE__ */ jsx4("main", { className: "flex-1 overflow-y-auto px-6", children })
|
|
243
218
|
] })
|
|
244
219
|
] }) });
|
|
@@ -535,8 +535,8 @@
|
|
|
535
535
|
/* A semantic token is a whole colour with no `<alpha-value>` placeholder, so `bg-bg-secondary/50`
|
|
536
536
|
compiles to nothing on Tailwind 3 while Tailwind 4 answers it with color-mix. These are the
|
|
537
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.
|
|
539
|
-
|
|
538
|
+
and both consumption paths resolve them. The inner var() resolves on the element `:root` and
|
|
539
|
+
`.light` both match, so `.light` needs no override. Delete each the release @lessly/tokens
|
|
540
540
|
publishes its semantic colours with an `<alpha-value>` placeholder. */
|
|
541
541
|
--bg-secondary-a50: color-mix(in oklab, var(--bg-secondary) 50%, transparent);
|
|
542
542
|
--bg-secondary-a80: color-mix(in oklab, var(--bg-secondary) 80%, transparent);
|
|
@@ -545,8 +545,32 @@
|
|
|
545
545
|
/* === Nav / menu surfaces (0.3.0 sidebar) === */
|
|
546
546
|
/* The floating surface sits on the package's overlay pair: --menu-bg on
|
|
547
547
|
--bg-overlay, --menu-shadow on --shadow-overlay. theme.css re-resolves both
|
|
548
|
-
per theme, so `.light` needs no override for either.
|
|
549
|
-
|
|
548
|
+
per theme, so `.light` needs no override for either.
|
|
549
|
+
|
|
550
|
+
The pointer contrasts with whatever it lands on, and the direction flips by itself: this is a
|
|
551
|
+
wash of --text-primary, which is light ink in dark and dark ink in light, so one declaration
|
|
552
|
+
lightens on a dark ground and darkens on a light one. A wash rather than a rung because it is
|
|
553
|
+
ground-relative by construction — the same 4% reads on the page, a rail, a card, a menu and a
|
|
554
|
+
nested card alike, where every opaque step fails on at least one of them (--bg-secondary is a
|
|
555
|
+
nested card's own fill, so a hover painted with it disappears there). Measured: dark 1.099:1
|
|
556
|
+
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
|
|
557
|
+
card (Guidelines/Look/Surface tone).
|
|
558
|
+
|
|
559
|
+
--current-bg is the plate a resting selection takes, and it is the end of the ladder the ink
|
|
560
|
+
points at: the wash above travels in the ink's direction, so the current row is the last rung
|
|
561
|
+
that way and the four states of a rail row read as one walk. Dark ink is light, so the top,
|
|
562
|
+
--bg-overlay, 1.184:1 up from the page. Light ink is dark, so the bottom, --bg-sunken,
|
|
563
|
+
1.208:1 down. Two declarations because the two ends are two different tokens, not because the
|
|
564
|
+
rule differs.
|
|
565
|
+
|
|
566
|
+
--hov-current is what the pointer paints on the row that already holds that plate, and the 4%
|
|
567
|
+
cap is why it has to exist: in dark the whole gap between the page and the plate is 1.184:1,
|
|
568
|
+
so a hover step big enough to be seen ON the plate would overshoot it on the ground. Same
|
|
569
|
+
rule, one layer up — the wash taken over --current-bg rather than over the ground, which is
|
|
570
|
+
also what carries the pointer past a rung the ladder has no successor for. */
|
|
571
|
+
--hov-bg: color-mix(in srgb, var(--text-primary) 4%, transparent);
|
|
572
|
+
--current-bg: var(--bg-overlay);
|
|
573
|
+
--hov-current: color-mix(in srgb, var(--text-primary) 4%, var(--current-bg));
|
|
550
574
|
--menu-bg: var(--bg-overlay);
|
|
551
575
|
--menu-shadow: var(--shadow-overlay);
|
|
552
576
|
--menu-hov: var(--hov-bg);
|
|
@@ -565,11 +589,10 @@
|
|
|
565
589
|
}
|
|
566
590
|
|
|
567
591
|
:where(.light) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
--
|
|
572
|
-
--menu-hov: #f2f4f7;
|
|
592
|
+
|
|
593
|
+
/* The other end of the ladder — see --current-bg in :root above. --hov-current is declared once
|
|
594
|
+
and reads this, so the pointer's step off the plate follows without a second copy. */
|
|
595
|
+
--current-bg: var(--bg-sunken);
|
|
573
596
|
|
|
574
597
|
/* Auth screen — see :root above. */
|
|
575
598
|
--auth-glow-a: rgba(11, 76, 213, 0.09);
|
|
@@ -604,6 +627,19 @@
|
|
|
604
627
|
transition-timing-function: var(--motion-easing-standard);
|
|
605
628
|
}
|
|
606
629
|
|
|
630
|
+
/* Backs AppSidebar's collapse: the rail's width, and the bar in the toggle's mark that crosses the
|
|
631
|
+
frame as it travels. One class on both, so the mark cannot be retuned away from the edge it
|
|
632
|
+
answers for — the same reason two carets read one `caretMotion`. `transform` is here rather than
|
|
633
|
+
in a class of its own because the bar is that edge said in miniature: retune one and the two stop
|
|
634
|
+
arriving together. A real class for the reason the two above are, and
|
|
635
|
+
token-driven because a Tailwind `duration-200` is a literal 200ms: under prefers-reduced-motion
|
|
636
|
+
the rail kept sliding for its full 200ms while every token-driven motion beside it went instant. */
|
|
637
|
+
.motion-rail-collapse {
|
|
638
|
+
transition-property: width, transform;
|
|
639
|
+
transition-duration: var(--motion-duration-normal);
|
|
640
|
+
transition-timing-function: var(--motion-easing-standard);
|
|
641
|
+
}
|
|
642
|
+
|
|
607
643
|
/* Backs the `useFreshHighlight` hook: a freshly-created row gets a brief background wash that fades
|
|
608
644
|
back to the resting surface, so the eye lands on the thing you just made. The tint is driven by
|
|
609
645
|
--fresh-tint (default the amber wash — closest to GitHub's yellow flash, chosen over the
|
package/dist/styles.css
CHANGED
|
@@ -576,8 +576,8 @@
|
|
|
576
576
|
/* A semantic token is a whole colour with no `<alpha-value>` placeholder, so `bg-bg-secondary/50`
|
|
577
577
|
compiles to nothing on Tailwind 3 while Tailwind 4 answers it with color-mix. These are the
|
|
578
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.
|
|
580
|
-
|
|
579
|
+
and both consumption paths resolve them. The inner var() resolves on the element `:root` and
|
|
580
|
+
`.light` both match, so `.light` needs no override. Delete each the release @lessly/tokens
|
|
581
581
|
publishes its semantic colours with an `<alpha-value>` placeholder. */
|
|
582
582
|
--bg-secondary-a50: color-mix(in oklab, var(--bg-secondary) 50%, transparent);
|
|
583
583
|
--bg-secondary-a80: color-mix(in oklab, var(--bg-secondary) 80%, transparent);
|
|
@@ -586,8 +586,32 @@
|
|
|
586
586
|
/* === Nav / menu surfaces (0.3.0 sidebar) === */
|
|
587
587
|
/* The floating surface sits on the package's overlay pair: --menu-bg on
|
|
588
588
|
--bg-overlay, --menu-shadow on --shadow-overlay. theme.css re-resolves both
|
|
589
|
-
per theme, so `.light` needs no override for either.
|
|
590
|
-
|
|
589
|
+
per theme, so `.light` needs no override for either.
|
|
590
|
+
|
|
591
|
+
The pointer contrasts with whatever it lands on, and the direction flips by itself: this is a
|
|
592
|
+
wash of --text-primary, which is light ink in dark and dark ink in light, so one declaration
|
|
593
|
+
lightens on a dark ground and darkens on a light one. A wash rather than a rung because it is
|
|
594
|
+
ground-relative by construction — the same 4% reads on the page, a rail, a card, a menu and a
|
|
595
|
+
nested card alike, where every opaque step fails on at least one of them (--bg-secondary is a
|
|
596
|
+
nested card's own fill, so a hover painted with it disappears there). Measured: dark 1.099:1
|
|
597
|
+
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
|
|
598
|
+
card (Guidelines/Look/Surface tone).
|
|
599
|
+
|
|
600
|
+
--current-bg is the plate a resting selection takes, and it is the end of the ladder the ink
|
|
601
|
+
points at: the wash above travels in the ink's direction, so the current row is the last rung
|
|
602
|
+
that way and the four states of a rail row read as one walk. Dark ink is light, so the top,
|
|
603
|
+
--bg-overlay, 1.184:1 up from the page. Light ink is dark, so the bottom, --bg-sunken,
|
|
604
|
+
1.208:1 down. Two declarations because the two ends are two different tokens, not because the
|
|
605
|
+
rule differs.
|
|
606
|
+
|
|
607
|
+
--hov-current is what the pointer paints on the row that already holds that plate, and the 4%
|
|
608
|
+
cap is why it has to exist: in dark the whole gap between the page and the plate is 1.184:1,
|
|
609
|
+
so a hover step big enough to be seen ON the plate would overshoot it on the ground. Same
|
|
610
|
+
rule, one layer up — the wash taken over --current-bg rather than over the ground, which is
|
|
611
|
+
also what carries the pointer past a rung the ladder has no successor for. */
|
|
612
|
+
--hov-bg: color-mix(in srgb, var(--text-primary) 4%, transparent);
|
|
613
|
+
--current-bg: var(--bg-overlay);
|
|
614
|
+
--hov-current: color-mix(in srgb, var(--text-primary) 4%, var(--current-bg));
|
|
591
615
|
--menu-bg: var(--bg-overlay);
|
|
592
616
|
--menu-shadow: var(--shadow-overlay);
|
|
593
617
|
--menu-hov: var(--hov-bg);
|
|
@@ -606,11 +630,10 @@
|
|
|
606
630
|
}
|
|
607
631
|
|
|
608
632
|
.light {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
--
|
|
613
|
-
--menu-hov: #f2f4f7;
|
|
633
|
+
|
|
634
|
+
/* The other end of the ladder — see --current-bg in :root above. --hov-current is declared once
|
|
635
|
+
and reads this, so the pointer's step off the plate follows without a second copy. */
|
|
636
|
+
--current-bg: var(--bg-sunken);
|
|
614
637
|
|
|
615
638
|
/* Auth screen — see :root above. */
|
|
616
639
|
--auth-glow-a: rgba(11, 76, 213, 0.09);
|
|
@@ -651,6 +674,19 @@
|
|
|
651
674
|
transition-timing-function: var(--motion-easing-standard);
|
|
652
675
|
}
|
|
653
676
|
|
|
677
|
+
/* Backs AppSidebar's collapse: the rail's width, and the bar in the toggle's mark that crosses the
|
|
678
|
+
frame as it travels. One class on both, so the mark cannot be retuned away from the edge it
|
|
679
|
+
answers for — the same reason two carets read one `caretMotion`. `transform` is here rather than
|
|
680
|
+
in a class of its own because the bar is that edge said in miniature: retune one and the two stop
|
|
681
|
+
arriving together. A real class for the reason the two above are, and
|
|
682
|
+
token-driven because a Tailwind `duration-200` is a literal 200ms: under prefers-reduced-motion
|
|
683
|
+
the rail kept sliding for its full 200ms while every token-driven motion beside it went instant. */
|
|
684
|
+
.motion-rail-collapse {
|
|
685
|
+
transition-property: width, transform;
|
|
686
|
+
transition-duration: var(--motion-duration-normal);
|
|
687
|
+
transition-timing-function: var(--motion-easing-standard);
|
|
688
|
+
}
|
|
689
|
+
|
|
654
690
|
/* Backs the `useFreshHighlight` hook: a freshly-created row gets a brief background wash that fades
|
|
655
691
|
back to the resting surface, so the eye lands on the thing you just made. The tint is driven by
|
|
656
692
|
--fresh-tint (default the amber wash — closest to GitHub's yellow flash, chosen over the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessly/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.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:current-emails": "tsx scripts/build-current-emails.mts",
|
|
40
|
+
"build:email-logo": "tsx scripts/build-email-logo.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/",
|