@stridge/noctis 1.0.0-beta.5 → 1.0.0-beta.6

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.
Files changed (43) hide show
  1. package/dist/components/breadcrumb/breadcrumb.d.ts +163 -0
  2. package/dist/components/breadcrumb/breadcrumb.js +152 -0
  3. package/dist/components/breadcrumb/breadcrumb.props.d.ts +59 -0
  4. package/dist/components/breadcrumb/breadcrumb.props.js +68 -0
  5. package/dist/components/breadcrumb/breadcrumb.slots.d.ts +16 -0
  6. package/dist/components/breadcrumb/breadcrumb.slots.js +32 -0
  7. package/dist/components/breadcrumb/breadcrumb.types.d.ts +9 -0
  8. package/dist/components/breadcrumb/index.d.ts +3 -0
  9. package/dist/components/command/command-listbox.js +174 -0
  10. package/dist/components/command/command-rank.d.ts +40 -0
  11. package/dist/components/command/command-rank.js +61 -0
  12. package/dist/components/command/command-score.d.ts +25 -0
  13. package/dist/components/command/command-score.js +85 -0
  14. package/dist/components/command/command.context.d.ts +17 -0
  15. package/dist/components/command/command.context.js +13 -0
  16. package/dist/components/command/command.d.ts +396 -0
  17. package/dist/components/command/command.js +471 -0
  18. package/dist/components/command/command.props.d.ts +91 -0
  19. package/dist/components/command/command.props.js +94 -0
  20. package/dist/components/command/command.slots.d.ts +23 -0
  21. package/dist/components/command/command.slots.js +60 -0
  22. package/dist/components/command/index.d.ts +6 -0
  23. package/dist/components/command/use-command-ranking.d.ts +37 -0
  24. package/dist/components/command/use-command-ranking.js +127 -0
  25. package/dist/components/search-dialog/parts/root.js +1 -1
  26. package/dist/components/skeleton/index.d.ts +3 -0
  27. package/dist/components/skeleton/skeleton.context.js +12 -0
  28. package/dist/components/skeleton/skeleton.d.ts +157 -0
  29. package/dist/components/skeleton/skeleton.js +130 -0
  30. package/dist/components/skeleton/skeleton.props.d.ts +47 -0
  31. package/dist/components/skeleton/skeleton.props.js +57 -0
  32. package/dist/components/skeleton/skeleton.slots.d.ts +15 -0
  33. package/dist/components/skeleton/skeleton.slots.js +28 -0
  34. package/dist/components/skeleton/skeleton.types.d.ts +13 -0
  35. package/dist/components/surface/surface.d.ts +1 -1
  36. package/dist/index.d.ts +15 -3
  37. package/dist/index.js +13 -4
  38. package/dist/primitives/index.d.ts +1 -1
  39. package/dist/primitives/index.js +2 -2
  40. package/dist/props.d.ts +37 -34
  41. package/dist/props.js +37 -34
  42. package/dist/styles.css +715 -0
  43. package/package.json +4 -4
package/dist/styles.css CHANGED
@@ -2131,6 +2131,45 @@
2131
2131
  --_combobox-input-height: var(--noctis-combobox-input-height, var(--noctis-size-control-lg));
2132
2132
  --_combobox-input-padding-inline: var(--noctis-combobox-input-padding-inline, 0.875rem);
2133
2133
  }
2134
+ [data-slot="noctis-command"] {
2135
+ --_command-inset-block-start: var(--noctis-command-inset-block-start, 12vh);
2136
+ --_command-height: var(--noctis-command-height, min(60vh, 460px));
2137
+ --_command-width: var(--noctis-command-width, min(40rem, 92vw));
2138
+ --_command-border-radius: var(--noctis-command-border-radius, var(--noctis-radius-md));
2139
+ --_command-transition-duration: var(--noctis-command-transition-duration, var(--noctis-duration-overlay));
2140
+ }
2141
+ [data-slot="noctis-command-input"] {
2142
+ --_command-input-height: var(--noctis-command-input-height, 3rem);
2143
+ }
2144
+ [data-slot="noctis-command-list"] {
2145
+ --_command-list-padding: var(--noctis-command-list-padding, 0.375rem);
2146
+ --_command-list-height: var(--noctis-command-list-height, auto);
2147
+ --_command-list-transition-duration: var(--noctis-command-list-transition-duration, var(--noctis-duration-regular));
2148
+ }
2149
+ [data-slot="noctis-command-item"] {
2150
+ --_command-item-height: var(--noctis-command-item-height, 2.5rem);
2151
+ --_command-item-padding-inline: var(--noctis-command-item-padding-inline, 0.375rem);
2152
+ --_command-item-padding-block: var(--noctis-command-item-padding-block, 0.25rem);
2153
+ --_command-item-gap: var(--noctis-command-item-gap, 0.5rem);
2154
+ --_command-item-border-radius: var(--noctis-command-item-border-radius, var(--noctis-radius-sm));
2155
+ --_command-item-color: var(--noctis-command-item-color, var(--noctis-color-secondary));
2156
+ --_command-item-color-highlighted: var(--noctis-command-item-color-highlighted, var(--noctis-color-foreground));
2157
+ --_command-item-background-color-highlighted: var(
2158
+ --noctis-command-item-background-color-highlighted,
2159
+ var(--noctis-color-control-ghost-hover)
2160
+ );
2161
+ --_command-item-background-color-active: var(
2162
+ --noctis-command-item-background-color-active,
2163
+ var(--noctis-color-control-ghost-selected)
2164
+ );
2165
+ }
2166
+ [data-slot="noctis-command-empty"] {
2167
+ --_command-empty-padding-block: var(--noctis-command-empty-padding-block, var(--noctis-space-6));
2168
+ }
2169
+ [data-slot="noctis-command-group-label"] {
2170
+ --_command-group-label-padding-inline: var(--noctis-command-group-label-padding-inline, var(--noctis-space-2\.5));
2171
+ --_command-group-label-padding-inline-start: var(--noctis-command-group-label-padding-inline-start, 0.375rem);
2172
+ }
2134
2173
  [data-slot="noctis-search-dialog"] {
2135
2174
  --_search-dialog-inset-block-start: var(--noctis-search-dialog-inset-block-start, 12vh);
2136
2175
  --_search-dialog-height: var(--noctis-search-dialog-height, min(56vh, 420px));
@@ -2489,6 +2528,62 @@
2489
2528
  [data-slot="noctis-badge"][data-size="sm"] [data-slot="noctis-badge-icon"] {
2490
2529
  --_badge-icon-size: var(--noctis-badge-icon-size, 0.75rem);
2491
2530
  }
2531
+ [data-slot="noctis-skeleton"] {
2532
+ --_skeleton-root-gap: var(--noctis-skeleton-root-gap, var(--noctis-space-3));
2533
+ }
2534
+ [data-slot="noctis-skeleton-box"],
2535
+ [data-slot="noctis-skeleton-circle"],
2536
+ [data-slot="noctis-skeleton-line"] {
2537
+ --_skeleton-fill-background-color: var(--noctis-skeleton-fill-background-color, var(--noctis-color-well));
2538
+ --_skeleton-shimmer-background-color: var(--noctis-skeleton-shimmer-background-color, var(--noctis-color-surface-raised));
2539
+ --_skeleton-shimmer-animation-duration: var(
2540
+ --noctis-skeleton-shimmer-animation-duration,
2541
+ calc(var(--noctis-duration-slow) * 4)
2542
+ );
2543
+ }
2544
+ [data-slot="noctis-skeleton-box"] {
2545
+ --_skeleton-box-block-size: var(--noctis-skeleton-box-block-size, 1.25rem);
2546
+ --_skeleton-box-border-radius: var(--noctis-skeleton-box-border-radius, var(--noctis-radius-md));
2547
+ }
2548
+ [data-slot="noctis-skeleton-circle"] {
2549
+ --_skeleton-circle-size: var(--noctis-skeleton-circle-size, 2.5rem);
2550
+ }
2551
+ [data-slot="noctis-skeleton-line"] {
2552
+ --_skeleton-line-block-size: var(--noctis-skeleton-line-block-size, 0.7em);
2553
+ --_skeleton-line-border-radius: var(--noctis-skeleton-line-border-radius, var(--noctis-radius-sm));
2554
+ }
2555
+ [data-slot="noctis-skeleton-text"] {
2556
+ --_skeleton-text-gap: var(--noctis-skeleton-text-gap, var(--noctis-space-2));
2557
+ }
2558
+ [data-slot="noctis-breadcrumb"] {
2559
+ --_breadcrumb-gap: var(--noctis-breadcrumb-gap, 0.375rem);
2560
+ --_breadcrumb-font-size: var(--noctis-breadcrumb-font-size, var(--noctis-text-small));
2561
+ }
2562
+ [data-slot="noctis-breadcrumb-list"] {
2563
+ --_breadcrumb-list-gap: var(--noctis-breadcrumb-list-gap, 0.375rem);
2564
+ }
2565
+ [data-slot="noctis-breadcrumb-icon"] {
2566
+ --_breadcrumb-icon-size: var(--noctis-breadcrumb-icon-size, 1rem);
2567
+ }
2568
+ [data-slot="noctis-breadcrumb-separator"] {
2569
+ --_breadcrumb-separator-size: var(--noctis-breadcrumb-separator-size, 1rem);
2570
+ }
2571
+ [data-slot="noctis-breadcrumb-link"] {
2572
+ --_breadcrumb-link-border-radius: var(--noctis-breadcrumb-link-border-radius, var(--noctis-radius-sm));
2573
+ }
2574
+ [data-slot="noctis-breadcrumb"][data-size="sm"] {
2575
+ --_breadcrumb-gap: var(--noctis-breadcrumb-gap, 0.25rem);
2576
+ --_breadcrumb-font-size: var(--noctis-breadcrumb-font-size, var(--noctis-text-mini));
2577
+ }
2578
+ [data-slot="noctis-breadcrumb"][data-size="sm"] [data-slot="noctis-breadcrumb-list"] {
2579
+ --_breadcrumb-list-gap: var(--noctis-breadcrumb-list-gap, 0.25rem);
2580
+ }
2581
+ [data-slot="noctis-breadcrumb"][data-size="sm"] [data-slot="noctis-breadcrumb-icon"] {
2582
+ --_breadcrumb-icon-size: var(--noctis-breadcrumb-icon-size, 0.875rem);
2583
+ }
2584
+ [data-slot="noctis-breadcrumb"][data-size="sm"] [data-slot="noctis-breadcrumb-separator"] {
2585
+ --_breadcrumb-separator-size: var(--noctis-breadcrumb-separator-size, 0.875rem);
2586
+ }
2492
2587
  [data-slot="noctis-otp-field"] {
2493
2588
  --_otp-field-gap: var(--noctis-otp-field-gap, 0.5rem);
2494
2589
  }
@@ -4095,6 +4190,122 @@
4095
4190
  }
4096
4191
  }
4097
4192
 
4193
+ @layer noctis.components {
4194
+ /*
4195
+ * Breadcrumb is self-contained, so every rule keys off the element's own `data-slot`. The metrics
4196
+ * flow through the `--_breadcrumb-*` internals, whose per-size defaults live in the generated
4197
+ * component layer (`tokens.css`) keyed off the `data-size` the root stamps. Colours aren't minted —
4198
+ * the link/page/separator read the `muted`/`foreground`/`subtle` roles directly, so a retheme
4199
+ * propagates for free.
4200
+ *
4201
+ * The root declares `--_breadcrumb-gap` (the icon↔label gap) and `--_breadcrumb-font-size`; both
4202
+ * inherit down to every crumb. The list, icon, and separator carry their own per-size internals.
4203
+ */
4204
+
4205
+ /* root — the nav landmark. Sets the trail's type and a calm default colour the crumbs override. */
4206
+ [data-slot="noctis-breadcrumb"] {
4207
+ font-family: var(--noctis-font-sans);
4208
+ font-size: var(--_breadcrumb-font-size);
4209
+ line-height: 1;
4210
+ color: var(--noctis-color-muted);
4211
+ }
4212
+
4213
+ /* list — the ordered row of crumbs and separators. */
4214
+ [data-slot="noctis-breadcrumb-list"] {
4215
+ display: flex;
4216
+ flex-wrap: wrap;
4217
+ align-items: center;
4218
+ gap: var(--_breadcrumb-list-gap);
4219
+ margin-block: 0;
4220
+ margin-inline: 0;
4221
+ padding-block: 0;
4222
+ padding-inline: 0;
4223
+ list-style: none;
4224
+ }
4225
+
4226
+ /* item — wraps a single crumb (a link or the current page). */
4227
+ [data-slot="noctis-breadcrumb-item"] {
4228
+ display: inline-flex;
4229
+ align-items: center;
4230
+ }
4231
+
4232
+ /* link — an interactive ancestor crumb. Muted by default, lifting to the foreground on hover/focus. */
4233
+ [data-slot="noctis-breadcrumb-link"] {
4234
+ display: inline-flex;
4235
+ align-items: center;
4236
+ gap: var(--_breadcrumb-gap);
4237
+ border-radius: var(--_breadcrumb-link-border-radius);
4238
+ color: var(--noctis-color-muted);
4239
+ text-decoration: none;
4240
+ cursor: pointer;
4241
+ transition: color var(--noctis-duration-quick) var(--noctis-ease-out);
4242
+ }
4243
+
4244
+ [data-slot="noctis-breadcrumb-link"]:hover {
4245
+ color: var(--noctis-color-foreground);
4246
+ }
4247
+
4248
+ [data-slot="noctis-breadcrumb-link"]:focus-visible {
4249
+ outline: var(--noctis-size-focus-ring-width) solid var(--noctis-color-ring);
4250
+ outline-offset: var(--noctis-size-focus-ring-offset);
4251
+ }
4252
+
4253
+ /* page — the current page crumb. Reads in the foreground at a slightly heavier weight. */
4254
+ [data-slot="noctis-breadcrumb-page"] {
4255
+ display: inline-flex;
4256
+ align-items: center;
4257
+ gap: var(--_breadcrumb-gap);
4258
+ color: var(--noctis-color-foreground);
4259
+ font-weight: 500;
4260
+ }
4261
+
4262
+ /* separator — the presentational glyph between crumbs. Subtle, clamped to the separator size. */
4263
+ [data-slot="noctis-breadcrumb-separator"] {
4264
+ display: inline-flex;
4265
+ flex-shrink: 0;
4266
+ align-items: center;
4267
+ color: var(--noctis-color-subtle);
4268
+ }
4269
+
4270
+ [data-slot="noctis-breadcrumb-separator"] svg {
4271
+ inline-size: var(--_breadcrumb-separator-size);
4272
+ block-size: var(--_breadcrumb-separator-size);
4273
+ }
4274
+
4275
+ /* ellipsis — the collapsed-run indicator. Muted, clamped to the crumb icon size. */
4276
+ [data-slot="noctis-breadcrumb-ellipsis"] {
4277
+ display: inline-flex;
4278
+ flex-shrink: 0;
4279
+ align-items: center;
4280
+ justify-content: center;
4281
+ color: var(--noctis-color-muted);
4282
+ }
4283
+
4284
+ [data-slot="noctis-breadcrumb-ellipsis"] svg {
4285
+ inline-size: var(--_breadcrumb-icon-size);
4286
+ block-size: var(--_breadcrumb-icon-size);
4287
+ }
4288
+
4289
+ /* icon — a crumb's leading glyph, clamped to the icon size; inherits the crumb's text colour. */
4290
+ [data-slot="noctis-breadcrumb-icon"] {
4291
+ display: inline-flex;
4292
+ flex-shrink: 0;
4293
+ align-items: center;
4294
+ justify-content: center;
4295
+ }
4296
+
4297
+ [data-slot="noctis-breadcrumb-icon"] svg {
4298
+ inline-size: var(--_breadcrumb-icon-size);
4299
+ block-size: var(--_breadcrumb-icon-size);
4300
+ }
4301
+
4302
+ @media (prefers-reduced-motion: reduce) {
4303
+ [data-slot="noctis-breadcrumb-link"] {
4304
+ transition: none;
4305
+ }
4306
+ }
4307
+ }
4308
+
4098
4309
  @layer noctis.components {
4099
4310
  /*
4100
4311
  * Button is a class-escape-hatch primitive: `Button.props()` styles a *foreign* element (the rail's
@@ -5982,6 +6193,377 @@
5982
6193
  }
5983
6194
  }
5984
6195
 
6196
+ @layer noctis.components {
6197
+ /*
6198
+ * Command — an accessible command palette. The panel renders through `Surface` (its stable
6199
+ * `data-surface` marker paints the background, border, and shadow, and stamps the `menu` elevation
6200
+ * scope), so this file styles only the palette's own slots: layout, the input row, the rows and
6201
+ * sections, the messages, the drill-in breadcrumb, the footer hints, and the modal scale-fade.
6202
+ *
6203
+ * One panel slot (`command`) serves both mounting modes. The modal `Command.Dialog` stamps
6204
+ * `data-modal` on the panel, which the geometry rules gate on (fixed, centred, the scale-fade); the
6205
+ * inline `Command.Root` omits it, so the same panel sits in normal flow and only the shared metrics
6206
+ * (radius, capped height, the rows) apply. Both modes leave the panel auto-height: it grows and
6207
+ * shrinks with its list, which animates its own height as the result set changes (the list feeds its
6208
+ * measured content height into `--_command-list-height`), so the palette glides between sizes rather
6209
+ * than jumping. Every rule keys off the prefixed `data-slot` anchor (no `:where()`), so the cascade
6210
+ * layer alone is the override seam; identity properties read a `--_command-*` internal or a semantic
6211
+ * `var(--noctis-*)` role, never a literal.
6212
+ */
6213
+
6214
+ /* backdrop — the blurred scrim behind the modal palette, fading with the panel. The 2px blur is a
6215
+ * non-token geometric literal (a filter radius), the same class of value as the panel's transition
6216
+ * offsets. */
6217
+ [data-slot="noctis-command-backdrop"] {
6218
+ position: fixed;
6219
+ inset: 0;
6220
+ z-index: var(--noctis-z-modal);
6221
+ background-color: var(--noctis-color-overlay);
6222
+ backdrop-filter: blur(2px);
6223
+ transition: opacity var(--noctis-duration-overlay) var(--noctis-ease-overlay);
6224
+ }
6225
+
6226
+ [data-slot="noctis-command-backdrop"][data-starting-style],
6227
+ [data-slot="noctis-command-backdrop"][data-ending-style] {
6228
+ opacity: 0;
6229
+ }
6230
+
6231
+ /* panel — the column holding the input row over the scrolling list over the optional footer. The
6232
+ * shared shape (rounded, clipping its scroll, capped height) applies in both modes; the paint comes
6233
+ * from the composed Surface. */
6234
+ [data-slot="noctis-command"] {
6235
+ display: flex;
6236
+ min-block-size: 0;
6237
+ max-block-size: var(--_command-height);
6238
+ inline-size: 100%;
6239
+ flex-direction: column;
6240
+ overflow: hidden;
6241
+ border-radius: var(--_command-border-radius);
6242
+ outline: none;
6243
+ }
6244
+
6245
+ /* the modal panel: centred, top-anchored, auto-height (capped by the shared `max-block-size` so it
6246
+ * stays on-screen; the list scrolls past that), and the scale-fade enter/exit. The transition
6247
+ * animates the individual properties (not `transform`) so the close doesn't snap. */
6248
+ [data-slot="noctis-command"][data-modal] {
6249
+ position: fixed;
6250
+ inset-inline: 0;
6251
+ inset-block-start: var(--_command-inset-block-start);
6252
+ z-index: var(--noctis-z-modal);
6253
+ margin-inline: auto;
6254
+ inline-size: var(--_command-width);
6255
+ transform-origin: top;
6256
+ transition:
6257
+ opacity var(--_command-transition-duration) var(--noctis-ease-overlay),
6258
+ translate var(--_command-transition-duration) var(--noctis-ease-overlay),
6259
+ scale var(--_command-transition-duration) var(--noctis-ease-overlay);
6260
+ }
6261
+
6262
+ [data-slot="noctis-command"][data-modal][data-starting-style] {
6263
+ translate: 0 -0.75rem;
6264
+ scale: 0.96;
6265
+ opacity: 0;
6266
+ }
6267
+
6268
+ [data-slot="noctis-command"][data-modal][data-ending-style] {
6269
+ translate: 0 -0.375rem;
6270
+ scale: 0.97;
6271
+ opacity: 0;
6272
+ }
6273
+
6274
+ /* header — the input row: an optional drill-in breadcrumb and leading glyph, the query field, and an
6275
+ * optional trailing action. No divider under it — the input reads as one surface with the list. The
6276
+ * inline padding lines the leading glyph up with the rows' icon column (list padding + row padding),
6277
+ * and the gap matches a row's icon→label gap, so the input and the rows share one left rhythm. */
6278
+ [data-slot="noctis-command-header"] {
6279
+ display: flex;
6280
+ flex-shrink: 0;
6281
+ align-items: center;
6282
+ gap: var(--noctis-space-2);
6283
+ padding-inline: var(--noctis-space-3);
6284
+ }
6285
+
6286
+ /* the leading search glyph (an un-slotted Icon in the header). */
6287
+ [data-slot="noctis-command-header"] > svg {
6288
+ flex-shrink: 0;
6289
+ color: var(--noctis-color-muted);
6290
+ }
6291
+
6292
+ /* breadcrumb — the drill-in trail shown before the field once the palette has been pushed into a
6293
+ * sub-page; each segment reads as quiet meta, not a control. */
6294
+ [data-slot="noctis-command-breadcrumb"] {
6295
+ display: inline-flex;
6296
+ flex-shrink: 0;
6297
+ align-items: center;
6298
+ gap: var(--noctis-space-2);
6299
+ font-size: var(--noctis-text-small);
6300
+ line-height: var(--noctis-leading-small);
6301
+ color: var(--noctis-color-muted);
6302
+ }
6303
+
6304
+ /* one breadcrumb segment: a soft chip on the ghost role so the trail lifts off the panel; clicking
6305
+ * it truncates the drill-in stack back to that depth. */
6306
+ [data-slot="noctis-command-breadcrumb"] > button {
6307
+ display: inline-flex;
6308
+ cursor: pointer;
6309
+ align-items: center;
6310
+ border: none;
6311
+ border-radius: var(--noctis-radius-sm);
6312
+ background-color: var(--noctis-color-control-ghost-hover);
6313
+ padding-inline: var(--noctis-space-2);
6314
+ padding-block: var(--noctis-space-1);
6315
+ font: inherit;
6316
+ color: var(--noctis-color-foreground);
6317
+ outline: none;
6318
+ transition: background-color var(--noctis-duration-quick) var(--noctis-ease-out);
6319
+ }
6320
+
6321
+ [data-slot="noctis-command-breadcrumb"] > button:hover {
6322
+ background-color: var(--noctis-color-control-ghost-selected);
6323
+ }
6324
+
6325
+ [data-slot="noctis-command-breadcrumb"] > button:focus-visible {
6326
+ outline: var(--noctis-size-focus-ring-width) solid var(--noctis-color-ring);
6327
+ outline-offset: var(--noctis-size-focus-ring-width);
6328
+ }
6329
+
6330
+ /* input — the query field: transparent, filling the header, with the native clear button hidden. */
6331
+ [data-slot="noctis-command-input"] {
6332
+ block-size: var(--_command-input-height);
6333
+ min-inline-size: 0;
6334
+ flex: 1;
6335
+ border: none;
6336
+ background-color: transparent;
6337
+ font-size: var(--noctis-text-regular);
6338
+ line-height: var(--noctis-leading-regular);
6339
+ color: var(--noctis-color-foreground);
6340
+ outline: none;
6341
+ }
6342
+
6343
+ [data-slot="noctis-command-input"]::placeholder {
6344
+ color: var(--noctis-color-placeholder);
6345
+ }
6346
+
6347
+ [data-slot="noctis-command-input"]::-webkit-search-cancel-button {
6348
+ appearance: none;
6349
+ }
6350
+
6351
+ /* input-action — the trailing affordance slot in the input row (a mode pill, a hint, an action). */
6352
+ [data-slot="noctis-command-input-action"] {
6353
+ display: inline-flex;
6354
+ flex-shrink: 0;
6355
+ align-items: center;
6356
+ gap: var(--noctis-space-2);
6357
+ font-size: var(--noctis-text-mini);
6358
+ line-height: var(--noctis-leading-mini);
6359
+ color: var(--noctis-color-muted);
6360
+ }
6361
+
6362
+ /* list — the scrolling listbox and its thin scrollbar. Its `block-size` tracks the measured content
6363
+ * height (the hook writes `--_command-list-height`) and tweens between sizes, so the auto-height
6364
+ * panel grows and shrinks with the rows. `flex: 0 1` lets it shrink — but never grow past its
6365
+ * content — so once the panel hits its cap the list yields and scrolls instead of overflowing.
6366
+ *
6367
+ * The scrollbar is thin and themed (the shared menu-scope thumb role) with no reserved gutter, so it
6368
+ * appears only when the rows overflow the capped panel and a short list shows no empty track.
6369
+ * `scroll-padding-block` keeps the inner list padding in view so the keyboard never parks the first or
6370
+ * last row flush against an edge (which read as a cropped row) — the end rows stop a padding short. */
6371
+ [data-slot="noctis-command-list"] {
6372
+ min-block-size: 0;
6373
+ flex: 0 1 auto;
6374
+ overflow-y: auto;
6375
+ block-size: var(--_command-list-height, auto);
6376
+ scroll-padding-block: var(--_command-list-padding);
6377
+ scrollbar-width: thin;
6378
+ scrollbar-color: var(--noctis-color-scrollbar-thumb) transparent;
6379
+ transition: block-size var(--_command-list-transition-duration) var(--noctis-ease-out);
6380
+ }
6381
+
6382
+ /* sizer — the measured content wrapper inside the scroll viewport. It carries the list's inner
6383
+ * padding (so the scrollbar rides outside it) and its natural height is what the hook observes. */
6384
+ [data-slot="noctis-command-list-sizer"] {
6385
+ padding: var(--_command-list-padding);
6386
+ }
6387
+
6388
+ /* list-view — the per-page view. Remounted on every drill in/out (keyed by depth), so this enter
6389
+ * animation replays each time: the new view fades in as the old one gives way (a pure opacity
6390
+ * cross-fade, like the reference — no slide, no scale, so it never fights the panel's height tween).
6391
+ * Opacity only, so the sizer's measured height is untouched. */
6392
+ [data-slot="noctis-command-list-view"] {
6393
+ animation: noctis-command-view-enter var(--noctis-duration-regular) var(--noctis-ease-out);
6394
+ }
6395
+
6396
+ @keyframes noctis-command-view-enter {
6397
+ from {
6398
+ opacity: 0;
6399
+ }
6400
+
6401
+ to {
6402
+ opacity: 1;
6403
+ }
6404
+ }
6405
+
6406
+ /* group — a labelled section wrapper (no box of its own). */
6407
+ [data-slot="noctis-command-group"] {
6408
+ display: block;
6409
+ }
6410
+
6411
+ /*
6412
+ * group-label — a section heading, deliberately NOT an option. The cues are script-independent (no
6413
+ * casing — Noctis is localized): the smallest type, the most muted foreground, a gap above. Its text
6414
+ * is outdented to the row's content edge, left of the option-label column.
6415
+ */
6416
+ [data-slot="noctis-command-group-label"] {
6417
+ padding-inline: var(--_command-group-label-padding-inline-start) var(--_command-group-label-padding-inline);
6418
+ padding-block: var(--noctis-space-3) var(--noctis-space-1);
6419
+ font-size: var(--noctis-text-micro);
6420
+ line-height: var(--noctis-leading-micro);
6421
+ color: var(--noctis-color-subtle);
6422
+ }
6423
+
6424
+ /*
6425
+ * item — one command row. Dimensions/colours flow through the `--_command-item-*` internals. The
6426
+ * highlight (neutral, signal-free) appears instantly on the keyboard- or pointer-active row and
6427
+ * fades out over the quick stop; the accent stays a signal, never a hover.
6428
+ */
6429
+ [data-slot="noctis-command-item"] {
6430
+ display: flex;
6431
+ min-block-size: var(--_command-item-height);
6432
+ cursor: pointer;
6433
+ align-items: center;
6434
+ gap: var(--_command-item-gap);
6435
+ border-radius: var(--_command-item-border-radius);
6436
+ padding-block: var(--_command-item-padding-block);
6437
+ padding-inline: var(--_command-item-padding-inline);
6438
+ font-size: var(--noctis-text-small);
6439
+ line-height: var(--noctis-leading-small);
6440
+ color: var(--_command-item-color);
6441
+ outline: none;
6442
+ user-select: none;
6443
+ transition-property: color, background-color;
6444
+ transition-timing-function: var(--noctis-ease-out);
6445
+ transition-duration: var(--noctis-duration-quick);
6446
+ }
6447
+
6448
+ [data-slot="noctis-command-item"][data-highlighted] {
6449
+ transition-duration: 0s;
6450
+ }
6451
+
6452
+ [data-slot="noctis-command-item"]:not([data-disabled]):hover,
6453
+ [data-slot="noctis-command-item"]:not([data-disabled])[data-highlighted] {
6454
+ background-color: var(--_command-item-background-color-highlighted);
6455
+ color: var(--_command-item-color-highlighted);
6456
+ }
6457
+
6458
+ /* a firmer fill while a row is pressed, as tactile feedback before it commits. */
6459
+ [data-slot="noctis-command-item"]:not([data-disabled]):active {
6460
+ background-color: var(--_command-item-background-color-active);
6461
+ }
6462
+
6463
+ [data-slot="noctis-command-item"][data-disabled] {
6464
+ cursor: not-allowed;
6465
+ opacity: var(--noctis-opacity-disabled);
6466
+ }
6467
+
6468
+ /* item-icon — the leading command glyph, quieter than the label. Also catches a bare `Icon`. */
6469
+ [data-slot="noctis-command-item-icon"],
6470
+ [data-slot="noctis-command-item"] > [data-slot="noctis-icon"] {
6471
+ display: inline-flex;
6472
+ flex-shrink: 0;
6473
+ color: var(--noctis-color-muted);
6474
+ }
6475
+
6476
+ /* item-label — the row's text column; truncates rather than wrapping. Its `flex` also claims the
6477
+ * row's free space, so any trailing content the consumer adds after it (a `Kbd` shortcut, a badge, a
6478
+ * count) is pushed to the row's end — the component itself owns no trailing slot. */
6479
+ [data-slot="noctis-command-item-label"] {
6480
+ min-inline-size: 0;
6481
+ flex: 1;
6482
+ overflow: hidden;
6483
+ text-overflow: ellipsis;
6484
+ white-space: nowrap;
6485
+ color: inherit;
6486
+ }
6487
+
6488
+ /* separator — a thin neutral divider between sections (the menu separator idiom). */
6489
+ [data-slot="noctis-command-separator"] {
6490
+ block-size: 1px;
6491
+ margin-block: var(--noctis-space-1);
6492
+ background-color: var(--noctis-color-border);
6493
+ }
6494
+
6495
+ /*
6496
+ * empty — the centred no-results message. Base UI keeps the element mounted as a live region but
6497
+ * renders its children only while the list is empty, so the styling is gated on `:not(:empty)` —
6498
+ * otherwise the childless element would reserve its block padding and open a gap above the list.
6499
+ */
6500
+ [data-slot="noctis-command-empty"]:not(:empty) {
6501
+ display: flex;
6502
+ flex: 1;
6503
+ align-items: center;
6504
+ justify-content: center;
6505
+ padding-block: var(--_command-empty-padding-block);
6506
+ padding-inline: var(--noctis-space-2);
6507
+ text-align: center;
6508
+ font-size: var(--noctis-text-small);
6509
+ line-height: var(--noctis-leading-small);
6510
+ color: var(--noctis-color-muted);
6511
+ }
6512
+
6513
+ /* loading — the centred async progress shell shown before any results settle; the spinner glyph
6514
+ * rides the shared spin token. */
6515
+ [data-slot="noctis-command-loading"] {
6516
+ display: flex;
6517
+ align-items: center;
6518
+ justify-content: center;
6519
+ gap: var(--noctis-space-2);
6520
+ padding-block: var(--_command-empty-padding-block);
6521
+ font-size: var(--noctis-text-small);
6522
+ line-height: var(--noctis-leading-small);
6523
+ color: var(--noctis-color-muted);
6524
+ }
6525
+
6526
+ [data-slot="noctis-command-loading"] > svg {
6527
+ flex-shrink: 0;
6528
+ animation: var(--noctis-animate-spin);
6529
+ color: var(--noctis-color-muted);
6530
+ }
6531
+
6532
+ /* footer — a bare, end-aligned region below the list (a quiet, bordered strip). Unopinionated about
6533
+ * its contents: the consumer composes whatever status or hints go here. */
6534
+ [data-slot="noctis-command-footer"] {
6535
+ display: flex;
6536
+ flex-shrink: 0;
6537
+ align-items: center;
6538
+ justify-content: flex-end;
6539
+ gap: var(--noctis-space-3);
6540
+ border-block-start: 1px solid var(--noctis-color-border);
6541
+ padding-inline: var(--noctis-space-4);
6542
+ padding-block: var(--noctis-space-2);
6543
+ font-size: var(--noctis-text-mini);
6544
+ line-height: var(--noctis-leading-mini);
6545
+ color: var(--noctis-color-muted);
6546
+ }
6547
+
6548
+ @media (prefers-reduced-motion: reduce) {
6549
+ [data-slot="noctis-command-backdrop"],
6550
+ [data-slot="noctis-command"][data-modal],
6551
+ [data-slot="noctis-command-list"],
6552
+ [data-slot="noctis-command-breadcrumb"] > button,
6553
+ [data-slot="noctis-command-item"] {
6554
+ transition: none;
6555
+ }
6556
+
6557
+ [data-slot="noctis-command-list-view"] {
6558
+ animation: none;
6559
+ }
6560
+
6561
+ [data-slot="noctis-command-loading"] > svg {
6562
+ animation: none;
6563
+ }
6564
+ }
6565
+ }
6566
+
5985
6567
  @layer noctis.components {
5986
6568
  /*
5987
6569
  * ContextMenu is precompiled: every rule keys off the prefixed `data-slot` anchor (no `:where()`, so
@@ -10403,6 +10985,139 @@
10403
10985
  }
10404
10986
  }
10405
10987
 
10988
+ @layer noctis.components {
10989
+ /*
10990
+ * Skeleton is self-contained and accent-independent, so every rule keys off the element's own
10991
+ * `data-slot`. The shared placeholder fill, the shimmer band colour, and the sweep duration flow
10992
+ * through the `--_skeleton-fill-*`/`--_skeleton-shimmer-*` internals, which the generated component
10993
+ * layer (`tokens.css`) declares on all three shape slots at once, so a standalone shape paints with
10994
+ * no `Skeleton.Root` ancestor. Per-shape metrics (box height/radius, circle diameter, line
10995
+ * height/radius) ride each shape's own slot; the root and text wrappers are layout only.
10996
+ *
10997
+ * Animation is keyed off each shape's own `data-variant`: `shimmer` (a light band masked over the
10998
+ * fill and translated across), `pulse` (an opacity fade on the shape), or `none` (static). Every
10999
+ * variant degrades to a calm static fill under `prefers-reduced-motion: reduce` (the foot of the file).
11000
+ */
11001
+
11002
+ /* root — the accessible loading group. A vertical stack by default; restyle via className for any
11003
+ * other arrangement. Not a painted placeholder itself. */
11004
+ [data-slot="noctis-skeleton"] {
11005
+ display: flex;
11006
+ flex-direction: column;
11007
+ gap: var(--_skeleton-root-gap);
11008
+ }
11009
+
11010
+ /* text — a column of line bars. Not painted itself; its `Skeleton.Line`s carry the fill + animation. */
11011
+ [data-slot="noctis-skeleton-text"] {
11012
+ display: flex;
11013
+ flex-direction: column;
11014
+ gap: var(--_skeleton-text-gap);
11015
+ }
11016
+
11017
+ /* The last line of a multi-line block is drawn short so the paragraph reads ragged, not as a box.
11018
+ * `:not(:first-child)` keeps a lone line full width. */
11019
+ [data-slot="noctis-skeleton-text"] > [data-slot="noctis-skeleton-line"]:last-child:not(:first-child) {
11020
+ inline-size: 60%;
11021
+ }
11022
+
11023
+ /* ─── the painted shapes ─────────────────────────────────────────────────────────────────────── */
11024
+
11025
+ /* Shared base: a neutral well-coloured fill, clipped so the shimmer band rides inside the shape. */
11026
+ [data-slot="noctis-skeleton-box"],
11027
+ [data-slot="noctis-skeleton-circle"],
11028
+ [data-slot="noctis-skeleton-line"] {
11029
+ position: relative;
11030
+ overflow: hidden;
11031
+ background-color: var(--_skeleton-fill-background-color);
11032
+ }
11033
+
11034
+ /* box — a rectangle. Fills its inline space; its height is the override seam (a default at md). */
11035
+ [data-slot="noctis-skeleton-box"] {
11036
+ inline-size: 100%;
11037
+ block-size: var(--_skeleton-box-block-size);
11038
+ border-radius: var(--_skeleton-box-border-radius);
11039
+ }
11040
+
11041
+ /* circle — an avatar/icon disc. A square footprint rounded to a full circle; never shrinks in a row. */
11042
+ [data-slot="noctis-skeleton-circle"] {
11043
+ flex-shrink: 0;
11044
+ inline-size: var(--_skeleton-circle-size);
11045
+ block-size: var(--_skeleton-circle-size);
11046
+ border-radius: var(--noctis-radius-full);
11047
+ }
11048
+
11049
+ /* line — a single text-line bar. Fills its inline space; the height tracks the surrounding text. */
11050
+ [data-slot="noctis-skeleton-line"] {
11051
+ inline-size: 100%;
11052
+ block-size: var(--_skeleton-line-block-size);
11053
+ border-radius: var(--_skeleton-line-border-radius);
11054
+ }
11055
+
11056
+ /* ─── shimmer: a light band masked over the fill, swept across ───────────────────────────────────
11057
+ * The overlay paints the brighter shimmer colour, masked to a soft transparent→opaque→transparent
11058
+ * band (black/transparent are mask alpha, not brand colour), then translated its full width so the
11059
+ * highlight sweeps the shape. At each loop end the band sits fully off-shape, so the reset is unseen. */
11060
+ [data-slot="noctis-skeleton-box"][data-variant="shimmer"]::after,
11061
+ [data-slot="noctis-skeleton-circle"][data-variant="shimmer"]::after,
11062
+ [data-slot="noctis-skeleton-line"][data-variant="shimmer"]::after {
11063
+ content: "";
11064
+ position: absolute;
11065
+ inset: 0;
11066
+ background-color: var(--_skeleton-shimmer-background-color);
11067
+ mask-image: linear-gradient(to right, transparent, black, transparent);
11068
+ animation: noctis-skeleton-shimmer var(--_skeleton-shimmer-animation-duration) var(--noctis-ease-in-out) infinite;
11069
+ }
11070
+
11071
+ /* The sweep follows the reading direction — reversed under RTL. */
11072
+ [dir="rtl"] [data-slot="noctis-skeleton-box"][data-variant="shimmer"]::after,
11073
+ [dir="rtl"] [data-slot="noctis-skeleton-circle"][data-variant="shimmer"]::after,
11074
+ [dir="rtl"] [data-slot="noctis-skeleton-line"][data-variant="shimmer"]::after {
11075
+ animation-direction: reverse;
11076
+ }
11077
+
11078
+ @keyframes noctis-skeleton-shimmer {
11079
+ from {
11080
+ transform: translateX(-100%);
11081
+ }
11082
+
11083
+ to {
11084
+ transform: translateX(100%);
11085
+ }
11086
+ }
11087
+
11088
+ /* ─── pulse: a gentle opacity fade on the shape itself ───────────────────────────────────────── */
11089
+ [data-slot="noctis-skeleton-box"][data-variant="pulse"],
11090
+ [data-slot="noctis-skeleton-circle"][data-variant="pulse"],
11091
+ [data-slot="noctis-skeleton-line"][data-variant="pulse"] {
11092
+ animation: noctis-skeleton-pulse var(--_skeleton-shimmer-animation-duration) var(--noctis-ease-in-out) infinite alternate;
11093
+ }
11094
+
11095
+ @keyframes noctis-skeleton-pulse {
11096
+ from {
11097
+ opacity: 0.45;
11098
+ }
11099
+
11100
+ to {
11101
+ opacity: 1;
11102
+ }
11103
+ }
11104
+
11105
+ /* Reduced motion: hold every placeholder as a calm static fill — drop the swept band, freeze the fade. */
11106
+ @media (prefers-reduced-motion: reduce) {
11107
+ [data-slot="noctis-skeleton-box"][data-variant="shimmer"]::after,
11108
+ [data-slot="noctis-skeleton-circle"][data-variant="shimmer"]::after,
11109
+ [data-slot="noctis-skeleton-line"][data-variant="shimmer"]::after {
11110
+ display: none;
11111
+ }
11112
+
11113
+ [data-slot="noctis-skeleton-box"][data-variant="pulse"],
11114
+ [data-slot="noctis-skeleton-circle"][data-variant="pulse"],
11115
+ [data-slot="noctis-skeleton-line"][data-variant="pulse"] {
11116
+ animation: none;
11117
+ }
11118
+ }
11119
+ }
11120
+
10406
11121
  @layer noctis.components {
10407
11122
  /* root — the vertical stack: an optional label/value header above the control */
10408
11123
  [data-slot="noctis-slider"] {