basalt-ui 2.0.1 → 2.0.3
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/CHANGELOG.md +18 -0
- package/MIGRATING.md +37 -19
- package/configs/check.yml +5 -5
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +1 -1
- package/dist/cli/doctor-upgrade.d.ts +1 -1
- package/dist/cli/doctor-upgrade.d.ts.map +1 -1
- package/dist/cli/doctor-upgrade.js +36 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +28 -3
- package/dist/cli/index.d.ts +28 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +60 -7
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +7 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +5 -0
- package/dist/provider/index.d.ts +8 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +3 -2
- package/dist/provider/spotlight-actions.d.ts +3 -2
- package/dist/provider/spotlight-actions.d.ts.map +1 -1
- package/dist/provider/spotlight-actions.js +5 -3
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +9 -2
- package/dist/shell/app-sidebar.module.css +6 -0
- package/dist/shell/app-sidebar.module.css.d.ts +1 -0
- package/dist/tokens/core.d.ts +12 -11
- package/dist/tokens/core.d.ts.map +1 -1
- package/dist/tokens/core.js +9 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/cli/config.ts +11 -1
- package/src/cli/doctor-upgrade.ts +49 -2
- package/src/cli/doctor.ts +37 -3
- package/src/cli/index.ts +93 -7
- package/src/cli/init.ts +10 -1
- package/src/cli/sync.ts +6 -0
- package/src/provider/index.tsx +10 -2
- package/src/provider/spotlight-actions.tsx +8 -3
- package/src/shell/app-sidebar.module.css +6 -0
- package/src/shell/app-sidebar.module.css.d.ts +1 -0
- package/src/shell/app-sidebar.tsx +16 -2
- package/src/tokens/core.ts +25 -9
package/src/cli/sync.ts
CHANGED
|
@@ -101,6 +101,12 @@ export function sync(opts: SyncOptions = {}, invocationCwd: string = process.cwd
|
|
|
101
101
|
return 0
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
const appError = resolvePlacement(cwd).appError
|
|
105
|
+
if (appError !== null) {
|
|
106
|
+
console.error(`✖ basalt-ui sync: ${appError}`)
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
|
|
104
110
|
// Refuse BEFORE reconcileRoots, which writes `basalt.roots` into package.json.
|
|
105
111
|
if (!existsSync(resolve(cwd, MANIFEST_PATH))) {
|
|
106
112
|
const elsewhere = findManifestAbove(cwd)
|
package/src/provider/index.tsx
CHANGED
|
@@ -41,6 +41,7 @@ import type { Telemetry } from '../telemetry'
|
|
|
41
41
|
import type { BasaltDevDockTool } from '../dev-dock'
|
|
42
42
|
import type { BasaltRouter, BasaltRouterContext } from './create-router'
|
|
43
43
|
import { useRegistryHotkeys } from './hotkeys'
|
|
44
|
+
import type { FlatNavItem } from '../router-tanstack/nav'
|
|
44
45
|
import { buildSpotlightActions } from './spotlight-actions'
|
|
45
46
|
|
|
46
47
|
export type BasaltProviderProps = {
|
|
@@ -79,6 +80,12 @@ export type BasaltProviderProps = {
|
|
|
79
80
|
* can answer. Default `'compact'`. SSR only; an SSR desktop app SHOULD pass it.
|
|
80
81
|
*/
|
|
81
82
|
sizeClassHint?: SizeClass
|
|
83
|
+
/**
|
|
84
|
+
* Label for the `nav.<id>` Spotlight entries generated from `defineNav`, e.g.
|
|
85
|
+
* `(item) => `Go to ${item.label}``. Default: the destination's own label. A `defineCommands`
|
|
86
|
+
* entry with id `nav.<id>` replaces the generated one outright.
|
|
87
|
+
*/
|
|
88
|
+
navCommandLabel?: (item: FlatNavItem) => string
|
|
82
89
|
} & Omit<MantineProviderProps, 'children' | 'theme' | 'defaultColorScheme' | 'cssVariablesResolver'>
|
|
83
90
|
|
|
84
91
|
// ── Root error boundary ───────────────────────────────────────────────────────────────────────────
|
|
@@ -408,6 +415,7 @@ export function BasaltProvider({
|
|
|
408
415
|
defaultColorScheme = 'dark',
|
|
409
416
|
nonce,
|
|
410
417
|
sizeClassHint = 'compact',
|
|
418
|
+
navCommandLabel,
|
|
411
419
|
...rest
|
|
412
420
|
}: BasaltProviderProps) {
|
|
413
421
|
useDuplicateProviderGuard()
|
|
@@ -432,8 +440,8 @@ export function BasaltProvider({
|
|
|
432
440
|
// appears, not frozen at mount — and `detectMac` (inside the builder) never runs during render.
|
|
433
441
|
const [actions, setActions] = useState<SpotlightActionData[]>([])
|
|
434
442
|
const refreshActions = useCallback(
|
|
435
|
-
() => setActions(buildSpotlightActions(router, setDevTool)),
|
|
436
|
-
[router],
|
|
443
|
+
() => setActions(buildSpotlightActions(router, setDevTool, navCommandLabel)),
|
|
444
|
+
[router, navCommandLabel],
|
|
437
445
|
)
|
|
438
446
|
useEffect(refreshActions, [refreshActions])
|
|
439
447
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The provider's Spotlight list: the `defineCommands` registry, then the `defineNav` destinations
|
|
3
|
-
* (via `flattenNav`), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
|
|
3
|
+
* (via `flattenNav`, skipping any id a registered `nav.<id>` command already owns), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
|
|
4
4
|
* opens, so a command's `when()` is current. Not for use during render (`detectMac` reads `navigator`).
|
|
5
5
|
*/
|
|
6
6
|
import type { SpotlightActionData } from '@mantine/spotlight'
|
|
@@ -8,6 +8,7 @@ import type { AnyRouter } from '@tanstack/react-router'
|
|
|
8
8
|
import { getActiveCommands } from '../commands/define-commands'
|
|
9
9
|
import { detectMac, formatShortcutDisplay } from '../commands/shortcut-format'
|
|
10
10
|
import { flattenNav, getActiveNav } from '../router-tanstack/nav'
|
|
11
|
+
import type { FlatNavItem } from '../router-tanstack/nav'
|
|
11
12
|
import type { BasaltDevDockTool } from '../dev-dock'
|
|
12
13
|
import { runCommandSafely } from './hotkeys'
|
|
13
14
|
|
|
@@ -20,10 +21,12 @@ const DEV_TOOLS: ReadonlyArray<{ tool: BasaltDevDockTool; label: string }> = [
|
|
|
20
21
|
export function buildSpotlightActions(
|
|
21
22
|
router: AnyRouter,
|
|
22
23
|
openDevTool: (tool: BasaltDevDockTool) => void,
|
|
24
|
+
navCommandLabel?: (item: FlatNavItem) => string,
|
|
23
25
|
): SpotlightActionData[] {
|
|
24
26
|
const actions: SpotlightActionData[] = []
|
|
27
|
+
const commands = getActiveCommands()
|
|
25
28
|
|
|
26
|
-
for (const [id, cmd] of Object.entries(
|
|
29
|
+
for (const [id, cmd] of Object.entries(commands)) {
|
|
27
30
|
if (cmd.when !== undefined && !cmd.when()) continue
|
|
28
31
|
actions.push({
|
|
29
32
|
id,
|
|
@@ -42,9 +45,11 @@ export function buildSpotlightActions(
|
|
|
42
45
|
if (nav !== undefined) {
|
|
43
46
|
for (const item of flattenNav(nav)) {
|
|
44
47
|
if (item.disabled === true) continue
|
|
48
|
+
// A registered `nav.<id>` command owns that destination: the consumer wins, no duplicate.
|
|
49
|
+
if (`nav.${item.id}` in commands) continue
|
|
45
50
|
actions.push({
|
|
46
51
|
id: `nav.${item.id}`,
|
|
47
|
-
label: item.label,
|
|
52
|
+
label: navCommandLabel?.(item) ?? item.label,
|
|
48
53
|
group: item.groupLabel,
|
|
49
54
|
leftSection: item.icon,
|
|
50
55
|
// `link` is the destination's own `linkOptions`, so a store-backed `search` thunk restores
|
|
@@ -472,6 +472,12 @@
|
|
|
472
472
|
box-shadow: 0 0 0 2px var(--mantine-color-body);
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
/* Host for a nav row's rail dot: the dot anchors on the icon itself, not the 48px row. */
|
|
476
|
+
.navIconDotHost {
|
|
477
|
+
position: relative;
|
|
478
|
+
display: inline-flex;
|
|
479
|
+
}
|
|
480
|
+
|
|
475
481
|
.blockItems {
|
|
476
482
|
display: flex;
|
|
477
483
|
flex-direction: column;
|
|
@@ -207,14 +207,28 @@ function NavLinkBody({
|
|
|
207
207
|
collapsed?: boolean
|
|
208
208
|
}) {
|
|
209
209
|
const Anchor = item.Anchor
|
|
210
|
+
const icon = iconOrInitial(item.icon, item.label)
|
|
211
|
+
// The rail hides the right section (where the badge lives), so a badged row keeps a dot on its
|
|
212
|
+
// icon instead — the count rides the accessible name since the number itself has no room.
|
|
213
|
+
const railDot =
|
|
214
|
+
collapsed && ((item.badge !== undefined && item.badge !== null) || Boolean(item.count))
|
|
210
215
|
const shared = {
|
|
211
216
|
classNames: { root: classes.link },
|
|
212
217
|
label: item.label,
|
|
213
|
-
leftSection:
|
|
218
|
+
leftSection: railDot ? (
|
|
219
|
+
<span className={classes.navIconDotHost}>
|
|
220
|
+
{icon}
|
|
221
|
+
<span className={classes.blockDot} data-basalt-rail-dot aria-hidden />
|
|
222
|
+
</span>
|
|
223
|
+
) : (
|
|
224
|
+
icon
|
|
225
|
+
),
|
|
214
226
|
// Only the collapsed rail hides the label (CSS), leaving the icon alone — name the link there.
|
|
215
227
|
// Expanded, the visible text IS the name and carries the count badge; an `aria-label` would
|
|
216
228
|
// override both and drop the count ("Inbox 5" → "Inbox").
|
|
217
|
-
...(collapsed && {
|
|
229
|
+
...(collapsed && {
|
|
230
|
+
'aria-label': item.count ? `${item.label}, ${item.count}` : item.label,
|
|
231
|
+
}),
|
|
218
232
|
rightSection: item.badge ?? (item.count ? <NavCountBadge count={item.count} /> : undefined),
|
|
219
233
|
active,
|
|
220
234
|
// Basalt is the one that stamps `aria-current`, not the theme and not a router `<Link>`'s own
|
package/src/tokens/core.ts
CHANGED
|
@@ -820,15 +820,23 @@ export function defineSeries<const T extends SeriesMap>(map: T): T {
|
|
|
820
820
|
/** Runtime key of the {@link SeriesGroup} brand. */
|
|
821
821
|
const SERIES_GROUP: unique symbol = Symbol('basalt.series-group')
|
|
822
822
|
|
|
823
|
-
|
|
824
|
-
* The brand `groupTokens` puts on its return value. `BuildPaletteOpts.groups` takes these, so the
|
|
825
|
-
* `${name}-` key that emits the variables and the one that references them come from one call and
|
|
826
|
-
* cannot be spelled two ways.
|
|
827
|
-
*/
|
|
828
|
-
export type SeriesGroup = {
|
|
823
|
+
type GroupPayload = {
|
|
829
824
|
readonly [SERIES_GROUP]: { readonly prefix: string; readonly map: SeriesMap }
|
|
830
825
|
}
|
|
831
826
|
|
|
827
|
+
declare const GROUP_REF: unique symbol
|
|
828
|
+
|
|
829
|
+
/** A `var(--vx-<name>-<key>)` ref minted by `groupTokens`. The brand lives on the VALUES, so it never shows up in `keyof` the group. */
|
|
830
|
+
export type GroupRef = string & { readonly [GROUP_REF]: true }
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* What `groupTokens` returns. `BuildPaletteOpts.groups` takes these, so the `${name}-` key that
|
|
834
|
+
* emits the variables and the one that references them come from one call and cannot be spelled
|
|
835
|
+
* two ways. A bare series map (object values) and a plain string record (unbranded values) are
|
|
836
|
+
* both rejected; the runtime payload sits under the non-enumerable {@link SERIES_GROUP} symbol.
|
|
837
|
+
*/
|
|
838
|
+
export type SeriesGroup = { readonly [key: string]: GroupRef }
|
|
839
|
+
|
|
832
840
|
/**
|
|
833
841
|
* Build a single namespaced group's token refs (`var(--vx-<name>-<key>)`).
|
|
834
842
|
*
|
|
@@ -838,12 +846,12 @@ export type SeriesGroup = {
|
|
|
838
846
|
export function groupTokens<const T extends SeriesMap>(
|
|
839
847
|
name: string,
|
|
840
848
|
map: T,
|
|
841
|
-
): { [K in keyof T]:
|
|
849
|
+
): { readonly [K in keyof T]: GroupRef } {
|
|
842
850
|
const refs = seriesTokens(map, `${name}-`)
|
|
843
851
|
// Non-enumerable, symbol-keyed: a series key can never collide with the brand, and
|
|
844
852
|
// `Object.keys(refs)` still lists exactly the series.
|
|
845
853
|
Object.defineProperty(refs, SERIES_GROUP, { value: { prefix: `${name}-`, map } })
|
|
846
|
-
return refs as { [K in keyof T]:
|
|
854
|
+
return refs as { readonly [K in keyof T]: GroupRef }
|
|
847
855
|
}
|
|
848
856
|
|
|
849
857
|
/**
|
|
@@ -1024,7 +1032,15 @@ export function buildPaletteCss(
|
|
|
1024
1032
|
const derived = extraDerived ? `${derivedBlock}\n${extraDerived}` : derivedBlock
|
|
1025
1033
|
const side = (s: Side): string => {
|
|
1026
1034
|
const extra = groups
|
|
1027
|
-
.map((group) =>
|
|
1035
|
+
.map((group) => {
|
|
1036
|
+
const payload = (group as unknown as Partial<GroupPayload>)[SERIES_GROUP]
|
|
1037
|
+
if (!payload) {
|
|
1038
|
+
throw new Error(
|
|
1039
|
+
'BuildPaletteOpts.groups: every group must come from groupTokens(name, map)',
|
|
1040
|
+
)
|
|
1041
|
+
}
|
|
1042
|
+
return groupSide(payload.prefix, payload.map, s)
|
|
1043
|
+
})
|
|
1028
1044
|
.join('\n')
|
|
1029
1045
|
const framework = frameworkPrimitives(s, data, legacyAliases)
|
|
1030
1046
|
return extra ? `${framework}\n${extra}` : framework
|