@apliteni/apliteni-ui 0.132.0 → 0.133.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.
@@ -6,13 +6,13 @@
6
6
 
7
7
  **Rule:** At rest, a file drop is one row: the button that opens the picker and the accepted types beside it.
8
8
 
9
- **Why:** A tall box holds the page's best space open for a file that arrives once a month.
9
+ **Why:** A tall box holds the page's best space for a file that arrives once a month.
10
10
 
11
11
  **Do:** Put an Upload button and “PDF or CSV, up to 10 MB” on one line above the list they feed.
12
12
 
13
13
  **Don't:** Draw a tall dashed box with a glyph, an invitation and a button stacked inside it.
14
14
 
15
- **Except:** A page whose whole purpose is receiving one file may use the field-sized picker, which carries a label of its own.
15
+ **Except:** A page that exists to receive one file may use the field-sized picker, which has its own label. An empty page may show a dashed drop area until the first file arrives. See #458 for this exception.
16
16
 
17
17
  ## Paint the target only while a file is over it
18
18
 
@@ -26,15 +26,17 @@
26
26
 
27
27
  **Don't:** Cover the whole window when one panel accepts the file.
28
28
 
29
+ **Except:** A page that takes files anywhere may cover the whole page, slightly transparent and blurred over a table. See #458 for this exception.
30
+
29
31
  ## Keep the file in the row it arrived in
30
32
 
31
33
  <!-- rule: in-the-row -->
32
34
 
33
35
  **Rule:** After the drop, the file's name, size and progress take the place the button was in, with remove on the name's line.
34
36
 
35
- **Why:** A row that changes in place keeps the page still while the file uploads.
37
+ **Why:** A row that changes in place keeps the page still during the upload.
36
38
 
37
- **Do:** Give the name a line with remove at its end, put the size under it, and run the progress track the full width below both.
39
+ **Do:** Put remove at the end of the name's line, the size under it, and the progress track below both.
38
40
 
39
41
  **Don't:** Push the list down with a progress card, or open a dialog over the page.
40
42
 
@@ -44,7 +46,7 @@
44
46
 
45
47
  **Rule:** A refused file keeps its place, says what it did wrong under its name, and offers Retry beside Remove.
46
48
 
47
- **Why:** A row that drops the file sends the reader back to choosing one. The reason is the one string here that has to be read in full, so it gets a line rather than a share of one.
49
+ **Why:** A row that drops the file sends the reader back to choosing one.
48
50
 
49
51
  **Do:** Keep the name on its line and say “Larger than 10 MB” under it, with Retry.
50
52
 
@@ -80,8 +82,8 @@
80
82
 
81
83
  **Rule:** Use a row where the file joins a list on the page, a region when one panel owns the file, and a dialog only when the upload needs answers of its own.
82
84
 
83
- **Why:** A dialog that asks nothing is one more thing to dismiss. The rule picks between three containers, so a drawing would show one of them and not the choice.
85
+ **Why:** A dialog that asks nothing is one more thing to dismiss. A drawing would show one container, not the choice.
84
86
 
85
- **Do:** Take a statement into the row under the statements it joins, and a replacement logo into the panel that shows it.
87
+ **Do:** Take a statement into the row under the statements it joins.
86
88
 
87
89
  **Don't:** Open a dialog for one file that the row behind it could have taken.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.132.0",
3
+ "version": "0.133.0",
4
4
 
5
5
  "workspaces": [
6
6
  "react"
@@ -847,6 +847,43 @@
847
847
  .ui-drop.is-disabled .ui-drop__name {
848
848
  color: var(--disabled-ink);
849
849
  }
850
+ .ui-drop--area {
851
+ border: 1px dashed var(--border-strong);
852
+ border-radius: var(--radius-lg);
853
+ padding: var(--space-4);
854
+ }
855
+ .ui-drop--area.is-dragging {
856
+ border-color: var(--accent);
857
+ }
858
+ @media (max-width: 560px) {
859
+ .ui-drop--area {
860
+ padding: var(--space-3);
861
+ }
862
+ }
863
+ .ui-drop--page {
864
+ position: fixed;
865
+ inset: calc(var(--ui-app-band, 0px) + var(--ui-app-top, 0px) + var(--space-4)) var(--space-4) var(--space-4);
866
+ z-index: var(--z-overlay);
867
+ display: flex;
868
+ align-items: center;
869
+ justify-content: center;
870
+ pointer-events: none;
871
+ background: var(--bg);
872
+ }
873
+ @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
874
+ .ui-drop--page {
875
+ background: color-mix(in srgb, var(--bg) 80%, transparent);
876
+ -webkit-backdrop-filter: blur(var(--space-2));
877
+ backdrop-filter: blur(var(--space-2));
878
+ }
879
+ }
880
+ @media (prefers-reduced-transparency: reduce) {
881
+ .ui-drop--page {
882
+ background: var(--bg);
883
+ -webkit-backdrop-filter: none;
884
+ backdrop-filter: none;
885
+ }
886
+ }
850
887
 
851
888
  /* src/Toast.css */
852
889
  .rx-toast-stack {
@@ -907,6 +944,9 @@
907
944
  gap: var(--space-1);
908
945
  padding-inline: var(--space-2);
909
946
  }
947
+ .ui-react-app .ui-app__bar:not(:has(> .ui-app__menu:first-child)) {
948
+ padding-inline-start: var(--space-4);
949
+ }
910
950
  .ui-react-app .ui-app__bar-gap {
911
951
  display: none;
912
952
  }
@@ -759,6 +759,12 @@ type FileDropProps = {
759
759
  label?: string;
760
760
  /** The accepted types and the size limit, stated once beside the button. */
761
761
  note?: string;
762
+ /** Makes the drop an empty page's area: a dashed box around a first-run block with this title,
763
+ * the note and the button. The block stands in for the row and `children`, and the page
764
+ * leaves the area when the first file arrives, so it never shows a file in hand. */
765
+ title?: string;
766
+ /** Lets the picker take several files. */
767
+ multiple?: boolean;
762
768
  /** Filters the system picker only; the consumer still validates the file. */
763
769
  accept?: string;
764
770
  /** The words on the drop target. */
@@ -771,14 +777,17 @@ type FileDropProps = {
771
777
  /** Drives the target from a parent that owns the drag region. */
772
778
  dragging?: boolean;
773
779
  disabled?: boolean;
780
+ /** The first file chosen or dropped. */
774
781
  onFile?: (file: File) => void;
782
+ /** Every file chosen or dropped, together. A consumer may pass both props. */
783
+ onFiles?: (files: File[]) => void;
775
784
  onRemove?: () => void;
776
785
  onRetry?: () => void;
777
786
  /** What the region accepts the file for; the target covers it while dragging. */
778
787
  children?: ReactNode;
779
788
  className?: string;
780
789
  };
781
- declare function FileDrop({ label, note, accept, dropLabel, removeLabel, retryLabel, file, dragging, disabled, onFile, onRemove, onRetry, children, className, }: FileDropProps): react.JSX.Element;
790
+ declare function FileDrop({ label, note, title, multiple, accept, dropLabel, removeLabel, retryLabel, file, dragging, disabled, onFile, onFiles, onRemove, onRetry, children, className, }: FileDropProps): react.JSX.Element;
782
791
 
783
792
  type SearchFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'aria-label'> & {
784
793
  /** The control's name. A toolbar search box shows no label, so this is the
@@ -219,8 +219,7 @@ var Button = forwardRef3(function Button2({
219
219
  // src/primitives/Badge.tsx
220
220
  import { jsx as jsx5 } from "react/jsx-runtime";
221
221
  function Badge({ variant = "neutral", children }) {
222
- const cls = ["ui-badge", variant !== "neutral" && `ui-badge--${variant}`].filter(Boolean).join(" ");
223
- return /* @__PURE__ */ jsx5("span", { className: cls, children });
222
+ return /* @__PURE__ */ jsx5("span", { className: `ui-badge ui-badge--${variant}`, children });
224
223
  }
225
224
 
226
225
  // src/primitives/Avatar.tsx
@@ -3313,7 +3312,31 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
3313
3312
 
3314
3313
  // src/FileDrop.tsx
3315
3314
  import { useRef as useRef17, useState as useState12 } from "react";
3316
- import { Fragment as Fragment10, jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
3315
+
3316
+ // src/EmptyState.tsx
3317
+ import { icon as kitIcon2, illo } from "@apliteni/apliteni-ui";
3318
+ import { jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
3319
+ var cases = {
3320
+ "first-run": { title: "Nothing needs you yet", sub: "New items will appear here when they need your attention.", icon: "check" },
3321
+ "no-matches": { title: "No results in this view", sub: "Try another search or clear your filters.", icon: "search" },
3322
+ "not-found": { title: "We can't find that page", sub: "Check the address or return to the home page.", icon: "folder" },
3323
+ "not-yet-built": { title: "This ships later", sub: "This screen is not available yet. Check back later.", icon: "clock" }
3324
+ };
3325
+ function EmptyState({ variant = "first-run", title, sub, icon: icon8, art, actions }) {
3326
+ const preset = cases[variant];
3327
+ const heading = title ?? preset.title;
3328
+ const guidance = sub ?? preset.sub;
3329
+ const glyph = icon8 ?? preset.icon;
3330
+ return /* @__PURE__ */ jsxs26("div", { className: "ui-empty", children: [
3331
+ art ? typeof art === "string" ? /* @__PURE__ */ jsx31("div", { className: "ui-empty__art", dangerouslySetInnerHTML: { __html: /<svg/.test(art) ? art : illo(art) } }) : /* @__PURE__ */ jsx31("div", { className: "ui-empty__art", "aria-hidden": "true", children: art }) : glyph ? /* @__PURE__ */ jsx31("div", { className: "ui-empty__icon", dangerouslySetInnerHTML: { __html: kitIcon2(glyph) } }) : null,
3332
+ heading && /* @__PURE__ */ jsx31("div", { className: "ui-empty__title", children: heading }),
3333
+ guidance && /* @__PURE__ */ jsx31("div", { className: "ui-empty__sub", children: guidance }),
3334
+ actions && /* @__PURE__ */ jsx31("div", { className: "ui-empty__actions", children: actions })
3335
+ ] });
3336
+ }
3337
+
3338
+ // src/FileDrop.tsx
3339
+ import { Fragment as Fragment10, jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
3317
3340
  var MARKS = {
3318
3341
  uploading: "clock",
3319
3342
  done: "circleCheck",
@@ -3333,6 +3356,8 @@ var cx7 = (...a) => a.filter(Boolean).join(" ");
3333
3356
  function FileDrop({
3334
3357
  label = "Upload",
3335
3358
  note,
3359
+ title,
3360
+ multiple,
3336
3361
  accept,
3337
3362
  dropLabel = "Drop to upload",
3338
3363
  removeLabel = "Remove",
@@ -3341,6 +3366,7 @@ function FileDrop({
3341
3366
  dragging,
3342
3367
  disabled,
3343
3368
  onFile,
3369
+ onFiles,
3344
3370
  onRemove,
3345
3371
  onRetry,
3346
3372
  children,
@@ -3356,118 +3382,155 @@ function FileDrop({
3356
3382
  const showSize = Boolean(file?.size) && status !== "error";
3357
3383
  const showTarget = (dragging ?? over) && !disabled;
3358
3384
  const take = (chosen) => {
3359
- if (chosen && !disabled) onFile?.(chosen);
3385
+ if (!chosen.length || disabled) return;
3386
+ onFile?.(chosen[0]);
3387
+ onFiles?.(chosen);
3388
+ };
3389
+ const pick = () => {
3390
+ if (!disabled) input.current?.click();
3391
+ };
3392
+ const picker = /* @__PURE__ */ jsx32(
3393
+ "input",
3394
+ {
3395
+ ref: input,
3396
+ type: "file",
3397
+ accept,
3398
+ multiple,
3399
+ disabled,
3400
+ hidden: true,
3401
+ tabIndex: -1,
3402
+ onClick: (event) => event.stopPropagation(),
3403
+ onChange: (event) => {
3404
+ take(Array.from(event.currentTarget.files ?? []));
3405
+ event.currentTarget.value = "";
3406
+ }
3407
+ }
3408
+ );
3409
+ const drag = {
3410
+ onDragOver: (event) => {
3411
+ if (disabled || !carriesFiles(event)) return;
3412
+ event.preventDefault();
3413
+ setOver(true);
3414
+ },
3415
+ onDragLeave: (event) => {
3416
+ if (!event.currentTarget.contains(event.relatedTarget)) setOver(false);
3417
+ },
3418
+ onDrop: (event) => {
3419
+ if (disabled || !carriesFiles(event)) return;
3420
+ event.preventDefault();
3421
+ setOver(false);
3422
+ take(Array.from(event.dataTransfer.files));
3423
+ }
3360
3424
  };
3361
- return /* @__PURE__ */ jsxs26(
3425
+ if (title !== void 0) return /* @__PURE__ */ jsxs27(
3362
3426
  "div",
3363
3427
  {
3364
- className: cx7("ui-drop", disabled && "is-disabled", className),
3365
- onDragOver: (event) => {
3366
- if (disabled || !carriesFiles(event)) return;
3367
- event.preventDefault();
3368
- setOver(true);
3369
- },
3370
- onDragLeave: (event) => {
3371
- if (!event.currentTarget.contains(event.relatedTarget)) setOver(false);
3372
- },
3373
- onDrop: (event) => {
3374
- if (disabled || !carriesFiles(event)) return;
3375
- event.preventDefault();
3376
- setOver(false);
3377
- take(event.dataTransfer.files[0]);
3378
- },
3428
+ className: cx7("ui-drop", "ui-drop--area", showTarget && "is-dragging", disabled && "is-disabled", className),
3429
+ onClick: pick,
3430
+ ...drag,
3379
3431
  children: [
3380
- children,
3381
- /* @__PURE__ */ jsxs26("div", { className: "ui-drop__row", children: [
3382
- file ? /* @__PURE__ */ jsxs26("div", { className: cx7("ui-drop__file", status === "error" && "ui-drop__file--failed"), children: [
3383
- /* @__PURE__ */ jsxs26("div", { className: "ui-drop__line", children: [
3384
- /* @__PURE__ */ jsxs26("span", { className: "ui-drop__name", title: file.name, children: [
3385
- /* @__PURE__ */ jsx31("span", { className: "ui-drop__stem", children: stem }),
3386
- /* @__PURE__ */ jsx31("span", { className: "ui-drop__ext", children: ext })
3387
- ] }),
3388
- (onRemove || status === "error" && onRetry) && /* @__PURE__ */ jsxs26("span", { className: "ui-drop__actions", children: [
3389
- status === "error" && onRetry && /* @__PURE__ */ jsx31(Button, { size: "sm", icon: "refresh", onClick: onRetry, disabled, children: retryLabel }),
3390
- onRemove && /* @__PURE__ */ jsx31(
3391
- Button,
3392
- {
3393
- variant: "ghost",
3394
- size: "sm",
3395
- onClick: onRemove,
3396
- disabled,
3397
- children: removeLabel
3398
- }
3399
- )
3400
- ] })
3401
- ] }),
3402
- (showSize || showWord) && /* @__PURE__ */ jsxs26(
3403
- "div",
3404
- {
3405
- className: cx7("ui-drop__facts", status === "error" && "ui-drop__error"),
3406
- role: status === "error" ? "alert" : void 0,
3407
- children: [
3408
- showSize && /* @__PURE__ */ jsx31("span", { className: "ui-drop__size", children: file.size }),
3409
- showWord && /* @__PURE__ */ jsxs26("span", { className: "ui-drop__state", children: [
3410
- /* @__PURE__ */ jsx31(Icon, { name: MARKS[status] }),
3411
- /* @__PURE__ */ jsx31("span", { className: "ui-drop__word", title: word, children: word })
3412
- ] })
3413
- ]
3414
- }
3415
- ),
3416
- hasBar && /* @__PURE__ */ jsx31(
3417
- "span",
3418
- {
3419
- className: "ui-drop__bar",
3420
- role: "progressbar",
3421
- "aria-label": `${word} ${file.name}`,
3422
- "aria-valuenow": file.progress,
3423
- "aria-valuemin": 0,
3424
- "aria-valuemax": 100,
3425
- children: /* @__PURE__ */ jsx31("span", { style: { width: `${file.progress}%` } })
3426
- }
3427
- )
3428
- ] }) : /* @__PURE__ */ jsxs26(Fragment10, { children: [
3429
- /* @__PURE__ */ jsx31(
3432
+ /* @__PURE__ */ jsx32(
3433
+ EmptyState,
3434
+ {
3435
+ icon: "upload",
3436
+ title,
3437
+ sub: note ?? "",
3438
+ actions: /* @__PURE__ */ jsx32(
3430
3439
  Button,
3431
3440
  {
3441
+ variant: "primary",
3432
3442
  size: "sm",
3433
3443
  icon: "upload",
3434
3444
  disabled,
3435
- onClick: () => input.current?.click(),
3445
+ onClick: (event) => {
3446
+ event.stopPropagation();
3447
+ pick();
3448
+ },
3436
3449
  children: label
3437
3450
  }
3438
- ),
3439
- note && /* @__PURE__ */ jsx31("p", { className: "ui-drop__note", children: note })
3440
- ] }),
3441
- /* @__PURE__ */ jsx31(
3442
- "input",
3443
- {
3444
- ref: input,
3445
- type: "file",
3446
- accept,
3447
- disabled,
3448
- hidden: true,
3449
- tabIndex: -1,
3450
- onChange: (event) => {
3451
- take(event.currentTarget.files?.[0]);
3452
- event.currentTarget.value = "";
3453
- }
3454
- }
3455
- )
3456
- ] }),
3457
- showTarget && /* @__PURE__ */ jsx31("div", { className: "ui-drop__target", "aria-hidden": "true", children: dropLabel })
3451
+ )
3452
+ }
3453
+ ),
3454
+ picker
3458
3455
  ]
3459
3456
  }
3460
3457
  );
3458
+ return /* @__PURE__ */ jsxs27("div", { className: cx7("ui-drop", disabled && "is-disabled", className), ...drag, children: [
3459
+ children,
3460
+ /* @__PURE__ */ jsxs27("div", { className: "ui-drop__row", children: [
3461
+ file ? /* @__PURE__ */ jsxs27("div", { className: cx7("ui-drop__file", status === "error" && "ui-drop__file--failed"), children: [
3462
+ /* @__PURE__ */ jsxs27("div", { className: "ui-drop__line", children: [
3463
+ /* @__PURE__ */ jsxs27("span", { className: "ui-drop__name", title: file.name, children: [
3464
+ /* @__PURE__ */ jsx32("span", { className: "ui-drop__stem", children: stem }),
3465
+ /* @__PURE__ */ jsx32("span", { className: "ui-drop__ext", children: ext })
3466
+ ] }),
3467
+ (onRemove || status === "error" && onRetry) && /* @__PURE__ */ jsxs27("span", { className: "ui-drop__actions", children: [
3468
+ status === "error" && onRetry && /* @__PURE__ */ jsx32(Button, { size: "sm", icon: "refresh", onClick: onRetry, disabled, children: retryLabel }),
3469
+ onRemove && /* @__PURE__ */ jsx32(
3470
+ Button,
3471
+ {
3472
+ variant: "ghost",
3473
+ size: "sm",
3474
+ onClick: onRemove,
3475
+ disabled,
3476
+ children: removeLabel
3477
+ }
3478
+ )
3479
+ ] })
3480
+ ] }),
3481
+ (showSize || showWord) && /* @__PURE__ */ jsxs27(
3482
+ "div",
3483
+ {
3484
+ className: cx7("ui-drop__facts", status === "error" && "ui-drop__error"),
3485
+ role: status === "error" ? "alert" : void 0,
3486
+ children: [
3487
+ showSize && /* @__PURE__ */ jsx32("span", { className: "ui-drop__size", children: file.size }),
3488
+ showWord && /* @__PURE__ */ jsxs27("span", { className: "ui-drop__state", children: [
3489
+ /* @__PURE__ */ jsx32(Icon, { name: MARKS[status] }),
3490
+ /* @__PURE__ */ jsx32("span", { className: "ui-drop__word", title: word, children: word })
3491
+ ] })
3492
+ ]
3493
+ }
3494
+ ),
3495
+ hasBar && /* @__PURE__ */ jsx32(
3496
+ "span",
3497
+ {
3498
+ className: "ui-drop__bar",
3499
+ role: "progressbar",
3500
+ "aria-label": `${word} ${file.name}`,
3501
+ "aria-valuenow": file.progress,
3502
+ "aria-valuemin": 0,
3503
+ "aria-valuemax": 100,
3504
+ children: /* @__PURE__ */ jsx32("span", { style: { width: `${file.progress}%` } })
3505
+ }
3506
+ )
3507
+ ] }) : /* @__PURE__ */ jsxs27(Fragment10, { children: [
3508
+ /* @__PURE__ */ jsx32(
3509
+ Button,
3510
+ {
3511
+ size: "sm",
3512
+ icon: "upload",
3513
+ disabled,
3514
+ onClick: pick,
3515
+ children: label
3516
+ }
3517
+ ),
3518
+ note && /* @__PURE__ */ jsx32("p", { className: "ui-drop__note", children: note })
3519
+ ] }),
3520
+ picker
3521
+ ] }),
3522
+ showTarget && /* @__PURE__ */ jsx32("div", { className: "ui-drop__target", "aria-hidden": "true", children: dropLabel })
3523
+ ] });
3461
3524
  }
3462
3525
 
3463
3526
  // src/SearchField.tsx
3464
3527
  import { forwardRef as forwardRef7 } from "react";
3465
3528
  import { icon as icon6 } from "@apliteni/apliteni-ui";
3466
- import { jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
3529
+ import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
3467
3530
  var SearchField = forwardRef7(function SearchField2({ ariaLabel, className, ...props }, ref) {
3468
- return /* @__PURE__ */ jsxs27("div", { className: "ui-input-group", children: [
3469
- /* @__PURE__ */ jsx32("span", { className: "ui-input-group__icon", dangerouslySetInnerHTML: { __html: icon6("search") } }),
3470
- /* @__PURE__ */ jsx32(
3531
+ return /* @__PURE__ */ jsxs28("div", { className: "ui-input-group", children: [
3532
+ /* @__PURE__ */ jsx33("span", { className: "ui-input-group__icon", dangerouslySetInnerHTML: { __html: icon6("search") } }),
3533
+ /* @__PURE__ */ jsx33(
3471
3534
  "input",
3472
3535
  {
3473
3536
  ref,
@@ -3483,7 +3546,7 @@ var SearchField = forwardRef7(function SearchField2({ ariaLabel, className, ...p
3483
3546
  // src/Toast.tsx
3484
3547
  import { createContext as createContext3, useCallback as useCallback6, useContext as useContext3, useEffect as useEffect12, useLayoutEffect as useLayoutEffect4, useRef as useRef18, useState as useState13 } from "react";
3485
3548
  import { createPortal as createPortal4 } from "react-dom";
3486
- import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
3549
+ import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
3487
3550
  var Context = createContext3(null);
3488
3551
  var glyphs = { success: "circleCheck", danger: "circleX", warn: "circleAlert", info: "info", neutral: "bolt" };
3489
3552
  function useToast() {
@@ -3531,7 +3594,7 @@ function Notice({ notice, remove }) {
3531
3594
  remaining.current = Math.max(0, remaining.current - (Date.now() - started));
3532
3595
  };
3533
3596
  }, [dismiss, leaving2, paused, timed]);
3534
- return /* @__PURE__ */ jsxs28(
3597
+ return /* @__PURE__ */ jsxs29(
3535
3598
  "div",
3536
3599
  {
3537
3600
  ref: root,
@@ -3545,12 +3608,12 @@ function Notice({ notice, remove }) {
3545
3608
  if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);
3546
3609
  },
3547
3610
  children: [
3548
- /* @__PURE__ */ jsx33("span", { className: "ui-toast__icon", children: /* @__PURE__ */ jsx33(Icon, { name: glyphs[tone] }) }),
3549
- /* @__PURE__ */ jsxs28("div", { className: "ui-toast__body", children: [
3550
- /* @__PURE__ */ jsx33("div", { className: "ui-toast__title", children: notice.title }),
3551
- !notice.compact && notice.text && /* @__PURE__ */ jsx33("div", { className: "ui-toast__text", children: notice.text })
3611
+ /* @__PURE__ */ jsx34("span", { className: "ui-toast__icon", children: /* @__PURE__ */ jsx34(Icon, { name: glyphs[tone] }) }),
3612
+ /* @__PURE__ */ jsxs29("div", { className: "ui-toast__body", children: [
3613
+ /* @__PURE__ */ jsx34("div", { className: "ui-toast__title", children: notice.title }),
3614
+ !notice.compact && notice.text && /* @__PURE__ */ jsx34("div", { className: "ui-toast__text", children: notice.text })
3552
3615
  ] }),
3553
- notice.action && /* @__PURE__ */ jsx33(
3616
+ notice.action && /* @__PURE__ */ jsx34(
3554
3617
  "button",
3555
3618
  {
3556
3619
  type: "button",
@@ -3565,8 +3628,8 @@ function Notice({ notice, remove }) {
3565
3628
  children: notice.action.label
3566
3629
  }
3567
3630
  ),
3568
- notice.dismissible !== false && /* @__PURE__ */ jsx33("button", { type: "button", className: "ui-toast__close", "aria-label": "Dismiss", disabled: leaving2, onClick: dismiss, children: /* @__PURE__ */ jsx33(Icon, { name: "x" }) }),
3569
- timed && /* @__PURE__ */ jsx33(
3631
+ notice.dismissible !== false && /* @__PURE__ */ jsx34("button", { type: "button", className: "ui-toast__close", "aria-label": "Dismiss", disabled: leaving2, onClick: dismiss, children: /* @__PURE__ */ jsx34(Icon, { name: "x" }) }),
3632
+ timed && /* @__PURE__ */ jsx34(
3570
3633
  "span",
3571
3634
  {
3572
3635
  className: "ui-toast__timer is-running",
@@ -3608,10 +3671,10 @@ function Toast({ children }) {
3608
3671
  setEntries((current) => [...current, { id, notice, remove }]);
3609
3672
  }, []);
3610
3673
  usePublishedReach(stack2, entries.length);
3611
- return /* @__PURE__ */ jsxs28(Context.Provider, { value: push, children: [
3674
+ return /* @__PURE__ */ jsxs29(Context.Provider, { value: push, children: [
3612
3675
  children,
3613
3676
  typeof document !== "undefined" && createPortal4(
3614
- /* @__PURE__ */ jsx33("div", { className: "ui-toast-stack rx-toast-stack", ref: stack2, children: entries.map((entry) => /* @__PURE__ */ jsx33(Notice, { notice: entry.notice, remove: entry.remove }, entry.id)) }),
3677
+ /* @__PURE__ */ jsx34("div", { className: "ui-toast-stack rx-toast-stack", ref: stack2, children: entries.map((entry) => /* @__PURE__ */ jsx34(Notice, { notice: entry.notice, remove: entry.remove }, entry.id)) }),
3615
3678
  document.body
3616
3679
  )
3617
3680
  ] });
@@ -3624,7 +3687,7 @@ import { paletteHotkey, prism, railCollapsed } from "@apliteni/apliteni-ui";
3624
3687
  // src/SidebarNav.tsx
3625
3688
  import { forwardRef as forwardRef8, useEffect as useEffect13, useId as useId11, useState as useState14 } from "react";
3626
3689
  import { ENTRANCE_FALLBACK_MS } from "@apliteni/apliteni-ui/motion";
3627
- import { Fragment as Fragment11, jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
3690
+ import { Fragment as Fragment11, jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
3628
3691
  var badgeText = (badge) => typeof badge === "object" ? badge.text : badge;
3629
3692
  function Row({ item, active, activeIs, collapsed, renderLink, sub }) {
3630
3693
  const uid = useId11();
@@ -3650,15 +3713,15 @@ function Row({ item, active, activeIs, collapsed, renderLink, sub }) {
3650
3713
  item.disabled && "is-disabled",
3651
3714
  item.danger && "is-danger"
3652
3715
  ].filter(Boolean).join(" ");
3653
- const children = /* @__PURE__ */ jsxs29(Fragment11, { children: [
3654
- (item.leading ?? item.icon) && /* @__PURE__ */ jsx34("span", { className: "ui-nav__ic", children: item.leading ?? /* @__PURE__ */ jsx34(Icon, { name: item.icon }) }),
3655
- /* @__PURE__ */ jsx34("span", { className: "ui-nav__label", children: item.label }),
3656
- count != null && count !== "" && /* @__PURE__ */ jsx34("span", { className: `ui-nav__badge is-${typeof item.badge === "object" ? item.badge.tone ?? "neutral" : "neutral"}`, children: count }),
3657
- group && /* @__PURE__ */ jsx34("span", { className: "ui-nav__caret", "aria-hidden": "true" })
3716
+ const children = /* @__PURE__ */ jsxs30(Fragment11, { children: [
3717
+ (item.leading ?? item.icon) && /* @__PURE__ */ jsx35("span", { className: "ui-nav__ic", children: item.leading ?? /* @__PURE__ */ jsx35(Icon, { name: item.icon }) }),
3718
+ /* @__PURE__ */ jsx35("span", { className: "ui-nav__label", children: item.label }),
3719
+ count != null && count !== "" && /* @__PURE__ */ jsx35("span", { className: `ui-nav__badge is-${typeof item.badge === "object" ? item.badge.tone ?? "neutral" : "neutral"}`, children: count }),
3720
+ group && /* @__PURE__ */ jsx35("span", { className: "ui-nav__caret", "aria-hidden": "true" })
3658
3721
  ] });
3659
3722
  const common = { className, "aria-label": name, title: collapsed ? name : void 0, children };
3660
- if (group) return /* @__PURE__ */ jsxs29("li", { className: `ui-nav__group${open ? " is-open" : ""}`, children: [
3661
- /* @__PURE__ */ jsx34(
3723
+ if (group) return /* @__PURE__ */ jsxs30("li", { className: `ui-nav__group${open ? " is-open" : ""}`, children: [
3724
+ /* @__PURE__ */ jsx35(
3662
3725
  "button",
3663
3726
  {
3664
3727
  ...common,
@@ -3672,22 +3735,22 @@ function Row({ item, active, activeIs, collapsed, renderLink, sub }) {
3672
3735
  }
3673
3736
  }
3674
3737
  ),
3675
- /* @__PURE__ */ jsx34("ul", { id: uid, className: `ui-nav__sub${entering ? " is-entering" : ""}`, hidden: !open, onAnimationEnd: () => setEntering(false), children: item.items.map((child) => /* @__PURE__ */ jsx34(Row, { item: child, active, activeIs, collapsed, renderLink, sub: true }, child.id)) })
3738
+ /* @__PURE__ */ jsx35("ul", { id: uid, className: `ui-nav__sub${entering ? " is-entering" : ""}`, hidden: !open, onAnimationEnd: () => setEntering(false), children: item.items.map((child) => /* @__PURE__ */ jsx35(Row, { item: child, active, activeIs, collapsed, renderLink, sub: true }, child.id)) })
3676
3739
  ] });
3677
- if (item.disabled) return /* @__PURE__ */ jsx34("li", { children: /* @__PURE__ */ jsx34("span", { ...common, role: "link", "aria-disabled": "true" }) });
3740
+ if (item.disabled) return /* @__PURE__ */ jsx35("li", { children: /* @__PURE__ */ jsx35("span", { ...common, role: "link", "aria-disabled": "true" }) });
3678
3741
  const props = {
3679
3742
  ...common,
3680
3743
  href: item.href ?? `#${item.id}`,
3681
3744
  target: item.target,
3682
3745
  "aria-current": isActive ? activeIs === "section" ? "true" : "page" : void 0
3683
3746
  };
3684
- return /* @__PURE__ */ jsx34("li", { children: renderLink ? renderLink(item, props) : /* @__PURE__ */ jsx34("a", { ...props }) });
3747
+ return /* @__PURE__ */ jsx35("li", { children: renderLink ? renderLink(item, props) : /* @__PURE__ */ jsx35("a", { ...props }) });
3685
3748
  }
3686
3749
  function Section({ section, ...props }) {
3687
3750
  const uid = useId11();
3688
- return /* @__PURE__ */ jsxs29("div", { className: "ui-nav__section", children: [
3689
- section.label && /* @__PURE__ */ jsx34("div", { className: "ui-nav__cap", id: uid, "aria-hidden": props.collapsed || void 0, children: section.label }),
3690
- /* @__PURE__ */ jsx34("ul", { className: "ui-nav__list", "aria-labelledby": section.label ? uid : void 0, children: section.items.map((item) => /* @__PURE__ */ jsx34(Row, { item, ...props }, item.id)) })
3751
+ return /* @__PURE__ */ jsxs30("div", { className: "ui-nav__section", children: [
3752
+ section.label && /* @__PURE__ */ jsx35("div", { className: "ui-nav__cap", id: uid, "aria-hidden": props.collapsed || void 0, children: section.label }),
3753
+ /* @__PURE__ */ jsx35("ul", { className: "ui-nav__list", "aria-labelledby": section.label ? uid : void 0, children: section.items.map((item) => /* @__PURE__ */ jsx35(Row, { item, ...props }, item.id)) })
3691
3754
  ] });
3692
3755
  }
3693
3756
  var SidebarNav = forwardRef8(function SidebarNav2({
@@ -3702,8 +3765,8 @@ var SidebarNav = forwardRef8(function SidebarNav2({
3702
3765
  "aria-label": label = "Sidebar",
3703
3766
  ...rest
3704
3767
  }, ref) {
3705
- return /* @__PURE__ */ jsxs29("nav", { ...rest, ref, "aria-label": label, className: ["ui-nav", "ui-nav--side", collapsed && "is-collapsed", className].filter(Boolean).join(" "), children: [
3706
- (sections?.length ? sections : [{ items }]).map((section, index) => /* @__PURE__ */ jsx34(
3768
+ return /* @__PURE__ */ jsxs30("nav", { ...rest, ref, "aria-label": label, className: ["ui-nav", "ui-nav--side", collapsed && "is-collapsed", className].filter(Boolean).join(" "), children: [
3769
+ (sections?.length ? sections : [{ items }]).map((section, index) => /* @__PURE__ */ jsx35(
3707
3770
  Section,
3708
3771
  {
3709
3772
  section,
@@ -3714,14 +3777,14 @@ var SidebarNav = forwardRef8(function SidebarNav2({
3714
3777
  },
3715
3778
  index
3716
3779
  )),
3717
- footer && /* @__PURE__ */ jsx34("div", { className: "ui-nav__foot", children: footer })
3780
+ footer && /* @__PURE__ */ jsx35("div", { className: "ui-nav__foot", children: footer })
3718
3781
  ] });
3719
3782
  });
3720
3783
 
3721
3784
  // src/ThemeToggle.tsx
3722
3785
  import { useSyncExternalStore } from "react";
3723
3786
  import { themeIcon } from "@apliteni/apliteni-ui";
3724
- import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
3787
+ import { jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
3725
3788
  var STORAGE_KEY = "apliteni-strategy-theme";
3726
3789
  var CHOICE_EVENT = "apliteni-theme-choice";
3727
3790
  var THEME_INIT_SCRIPT = `(()=>{let t;try{t=localStorage.getItem('apliteni-strategy-theme')}catch{}document.documentElement.setAttribute('data-theme',t==='dark'||t==='light'?t:window.matchMedia?.('(prefers-color-scheme: light)').matches?'light':'dark')})()`;
@@ -3779,10 +3842,10 @@ function ThemeToggle({ labelled = false }) {
3779
3842
  }
3780
3843
  window.dispatchEvent(new Event(CHOICE_EVENT));
3781
3844
  };
3782
- const button = /* @__PURE__ */ jsx35("button", { type: "button", className: "toggle", "aria-label": name, title: name, onClick: toggle, children: /* @__PURE__ */ jsx35("span", { className: "ic", "aria-hidden": "true", dangerouslySetInnerHTML: { __html: themeIcon(theme) } }) });
3783
- return labelled ? /* @__PURE__ */ jsxs30("span", { className: "ui-theme-toggle", children: [
3845
+ const button = /* @__PURE__ */ jsx36("button", { type: "button", className: "toggle", "aria-label": name, title: name, onClick: toggle, children: /* @__PURE__ */ jsx36("span", { className: "ic", "aria-hidden": "true", dangerouslySetInnerHTML: { __html: themeIcon(theme) } }) });
3846
+ return labelled ? /* @__PURE__ */ jsxs31("span", { className: "ui-theme-toggle", children: [
3784
3847
  button,
3785
- /* @__PURE__ */ jsxs30("span", { children: [
3848
+ /* @__PURE__ */ jsxs31("span", { children: [
3786
3849
  label,
3787
3850
  " theme"
3788
3851
  ] })
@@ -3790,7 +3853,7 @@ function ThemeToggle({ labelled = false }) {
3790
3853
  }
3791
3854
 
3792
3855
  // src/AppShell.tsx
3793
- import { Fragment as Fragment12, jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
3856
+ import { Fragment as Fragment12, jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
3794
3857
  var path = (value) => value.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
3795
3858
  function AppShell({
3796
3859
  sections,
@@ -3855,7 +3918,7 @@ function AppShell({
3855
3918
  } catch {
3856
3919
  }
3857
3920
  };
3858
- const sidebar = (label) => /* @__PURE__ */ jsx36(
3921
+ const sidebar = (label) => /* @__PURE__ */ jsx37(
3859
3922
  SidebarNav,
3860
3923
  {
3861
3924
  "aria-label": label,
@@ -3867,33 +3930,33 @@ function AppShell({
3867
3930
  const linkProps = { ...props, onClick: (event) => {
3868
3931
  if (!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) setMenu(false);
3869
3932
  } };
3870
- return renderLink ? renderLink(section, linkProps) : /* @__PURE__ */ jsx36("a", { ...linkProps });
3933
+ return renderLink ? renderLink(section, linkProps) : /* @__PURE__ */ jsx37("a", { ...linkProps });
3871
3934
  }
3872
3935
  }
3873
3936
  );
3874
3937
  const foldLabel = collapsed ? "Expand sidebar" : "Collapse sidebar";
3875
3938
  const navigable = sections.length > 1 || Boolean(back);
3876
- const lockup = /* @__PURE__ */ jsxs31("a", { className: "ui-app__brand", href: brandHref, "aria-label": word, children: [
3877
- brand ?? /* @__PURE__ */ jsx36("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(uid, 24) } }),
3878
- /* @__PURE__ */ jsx36("span", { children: word })
3939
+ const lockup = /* @__PURE__ */ jsxs32("a", { className: "ui-app__brand", href: brandHref, "aria-label": word, children: [
3940
+ brand ?? /* @__PURE__ */ jsx37("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(uid, 24) } }),
3941
+ /* @__PURE__ */ jsx37("span", { children: word })
3879
3942
  ] });
3880
- return /* @__PURE__ */ jsxs31("div", { className: `ui-app ui-app--topbar ui-react-app${collapsed ? " is-collapsed" : ""}`, children: [
3881
- navigable && /* @__PURE__ */ jsxs31("div", { className: "ui-app__rail", role: "region", "aria-label": "Sidebar", children: [
3882
- /* @__PURE__ */ jsx36("div", { className: "ui-app__head", children: lockup }),
3943
+ return /* @__PURE__ */ jsxs32("div", { className: `ui-app ui-app--topbar ui-react-app${collapsed ? " is-collapsed" : ""}`, children: [
3944
+ navigable && /* @__PURE__ */ jsxs32("div", { className: "ui-app__rail", role: "region", "aria-label": "Sidebar", children: [
3945
+ /* @__PURE__ */ jsx37("div", { className: "ui-app__head", children: lockup }),
3883
3946
  sidebar("Sections"),
3884
- /* @__PURE__ */ jsx36("div", { className: "ui-app__foot", children: /* @__PURE__ */ jsx36("div", { className: "ui-app__fold-row", children: /* @__PURE__ */ jsxs31("button", { type: "button", className: "ui-nav__item ui-app__fold", "aria-expanded": !collapsed, "aria-label": foldLabel, onClick: fold, children: [
3885
- /* @__PURE__ */ jsx36("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsxs31("svg", { className: "ui-icon", viewBox: "0 0 24 24", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3886
- /* @__PURE__ */ jsx36("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
3887
- /* @__PURE__ */ jsx36("path", { className: "ui-app__fold-seam", d: "M9 3v18" }),
3888
- /* @__PURE__ */ jsx36("path", { className: "ui-app__fold-arrow", d: "m16 15-3-3 3-3" })
3947
+ /* @__PURE__ */ jsx37("div", { className: "ui-app__foot", children: /* @__PURE__ */ jsx37("div", { className: "ui-app__fold-row", children: /* @__PURE__ */ jsxs32("button", { type: "button", className: "ui-nav__item ui-app__fold", "aria-expanded": !collapsed, "aria-label": foldLabel, onClick: fold, children: [
3948
+ /* @__PURE__ */ jsx37("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsxs32("svg", { className: "ui-icon", viewBox: "0 0 24 24", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3949
+ /* @__PURE__ */ jsx37("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
3950
+ /* @__PURE__ */ jsx37("path", { className: "ui-app__fold-seam", d: "M9 3v18" }),
3951
+ /* @__PURE__ */ jsx37("path", { className: "ui-app__fold-arrow", d: "m16 15-3-3 3-3" })
3889
3952
  ] }) }),
3890
- /* @__PURE__ */ jsx36("span", { className: "ui-nav__label", children: foldLabel })
3953
+ /* @__PURE__ */ jsx37("span", { className: "ui-nav__label", children: foldLabel })
3891
3954
  ] }) }) })
3892
3955
  ] }),
3893
- /* @__PURE__ */ jsxs31("div", { className: "ui-app__well", children: [
3894
- /* @__PURE__ */ jsxs31("header", { className: "ui-app__bar", children: [
3895
- navigable && /* @__PURE__ */ jsxs31(Fragment12, { children: [
3896
- /* @__PURE__ */ jsx36(
3956
+ /* @__PURE__ */ jsxs32("div", { className: "ui-app__well", children: [
3957
+ /* @__PURE__ */ jsxs32("header", { className: "ui-app__bar", children: [
3958
+ navigable && /* @__PURE__ */ jsxs32(Fragment12, { children: [
3959
+ /* @__PURE__ */ jsx37(
3897
3960
  Button,
3898
3961
  {
3899
3962
  ref: menuButton,
@@ -3907,19 +3970,19 @@ function AppShell({
3907
3970
  onClick: () => setMenu(true)
3908
3971
  }
3909
3972
  ),
3910
- /* @__PURE__ */ jsx36("a", { className: "ui-app__brand ui-app__brand--band", href: brandHref, "aria-label": word, children: brand ?? /* @__PURE__ */ jsx36("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(`${uid}-band`, 24) } }) })
3973
+ /* @__PURE__ */ jsx37("a", { className: "ui-app__brand ui-app__brand--band", href: brandHref, "aria-label": word, children: brand ?? /* @__PURE__ */ jsx37("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(`${uid}-band`, 24) } }) })
3911
3974
  ] }),
3912
3975
  !navigable && lockup,
3913
- /* @__PURE__ */ jsxs31("button", { type: "button", className: "ui-app__search", "aria-haspopup": "dialog", onClick: () => setSearch(true), children: [
3914
- /* @__PURE__ */ jsx36("span", { className: "ui-app__search-ic", children: /* @__PURE__ */ jsx36(Icon, { name: "search" }) }),
3915
- /* @__PURE__ */ jsx36("span", { className: "ui-app__search-txt", children: searchLabel }),
3916
- /* @__PURE__ */ jsx36("span", { className: "ui-app__search-short", "aria-hidden": "true", children: searchShortLabel }),
3917
- /* @__PURE__ */ jsx36("kbd", { className: "ui-cmdk__key", children: paletteHotkey() })
3976
+ /* @__PURE__ */ jsxs32("button", { type: "button", className: "ui-app__search", "aria-haspopup": "dialog", onClick: () => setSearch(true), children: [
3977
+ /* @__PURE__ */ jsx37("span", { className: "ui-app__search-ic", children: /* @__PURE__ */ jsx37(Icon, { name: "search" }) }),
3978
+ /* @__PURE__ */ jsx37("span", { className: "ui-app__search-txt", children: searchLabel }),
3979
+ /* @__PURE__ */ jsx37("span", { className: "ui-app__search-short", "aria-hidden": "true", children: searchShortLabel }),
3980
+ /* @__PURE__ */ jsx37("kbd", { className: "ui-cmdk__key", children: paletteHotkey() })
3918
3981
  ] }),
3919
- /* @__PURE__ */ jsx36("span", { className: "ui-app__bar-gap" }),
3920
- bandControl && /* @__PURE__ */ jsx36("span", { className: "ui-react-app__band-control", children: bandControl }),
3921
- /* @__PURE__ */ jsx36(ThemeToggle, {}),
3922
- /* @__PURE__ */ jsx36("div", { className: "ui-app__user ui-app__user--bar", children: /* @__PURE__ */ jsx36(
3982
+ /* @__PURE__ */ jsx37("span", { className: "ui-app__bar-gap" }),
3983
+ bandControl && /* @__PURE__ */ jsx37("span", { className: "ui-react-app__band-control", children: bandControl }),
3984
+ /* @__PURE__ */ jsx37(ThemeToggle, {}),
3985
+ /* @__PURE__ */ jsx37("div", { className: "ui-app__user ui-app__user--bar", children: /* @__PURE__ */ jsx37(
3923
3986
  Dropdown,
3924
3987
  {
3925
3988
  variant: "menu",
@@ -3928,48 +3991,26 @@ function AppShell({
3928
3991
  ariaLabel: `Signed in as ${account.name}, ${account.email}`,
3929
3992
  triggerClass: "ui-app__user-trigger",
3930
3993
  panelClass: "ui-app__user-panel",
3931
- triggerContent: /* @__PURE__ */ jsx36("span", { className: "ui-app__av", "aria-hidden": "true", children: /* @__PURE__ */ jsx36(Avatar, { personKey: account.email || account.name, name: account.name, size: 32 }) }),
3932
- header: /* @__PURE__ */ jsxs31("div", { className: "ui-dropdown__head", children: [
3933
- /* @__PURE__ */ jsx36("b", { children: account.name }),
3934
- /* @__PURE__ */ jsx36("span", { children: account.email })
3994
+ triggerContent: /* @__PURE__ */ jsx37("span", { className: "ui-app__av", "aria-hidden": "true", children: /* @__PURE__ */ jsx37(Avatar, { personKey: account.email || account.name, name: account.name, size: 32 }) }),
3995
+ header: /* @__PURE__ */ jsxs32("div", { className: "ui-dropdown__head", children: [
3996
+ /* @__PURE__ */ jsx37("b", { children: account.name }),
3997
+ /* @__PURE__ */ jsx37("span", { children: account.email })
3935
3998
  ] }),
3936
3999
  items: [{ label: "Sign out", icon: "logout", danger: true }],
3937
4000
  onSelect: onSignOut
3938
4001
  }
3939
4002
  ) })
3940
4003
  ] }),
3941
- /* @__PURE__ */ jsxs31("main", { className: `ui-app__main${width === "wide" ? " ui-app__main--wide" : ""}`, children: [
3942
- back && /* @__PURE__ */ jsx36(BackLink, { ...back }),
3943
- /* @__PURE__ */ jsx36("h1", { children: title }),
3944
- lede && /* @__PURE__ */ jsx36("p", { className: "ui-app__sub", children: lede }),
3945
- actions && /* @__PURE__ */ jsx36("div", { className: "ui-react-app__actions", children: actions }),
3946
- /* @__PURE__ */ jsx36("div", { className: "ui-app__body", children })
4004
+ /* @__PURE__ */ jsxs32("main", { className: `ui-app__main${width === "wide" ? " ui-app__main--wide" : ""}`, children: [
4005
+ back && /* @__PURE__ */ jsx37(BackLink, { ...back }),
4006
+ /* @__PURE__ */ jsx37("h1", { children: title }),
4007
+ lede && /* @__PURE__ */ jsx37("p", { className: "ui-app__sub", children: lede }),
4008
+ actions && /* @__PURE__ */ jsx37("div", { className: "ui-react-app__actions", children: actions }),
4009
+ /* @__PURE__ */ jsx37("div", { className: "ui-app__body", children })
3947
4010
  ] })
3948
4011
  ] }),
3949
- navigable && /* @__PURE__ */ jsx36(Drawer, { open: menu, onClose: () => setMenu(false), title: word, side: "left", size: "sm", children: /* @__PURE__ */ jsx36("div", { className: "ui-react-app__menu", children: sidebar("Menu sections") }) }),
3950
- /* @__PURE__ */ jsx36(CommandPalette, { ...palette ?? { groups: [{ items: sections.map((section) => ({ id: section.href, ...section })) }] }, open: search, onClose: () => setSearch(false) })
3951
- ] });
3952
- }
3953
-
3954
- // src/EmptyState.tsx
3955
- import { icon as kitIcon2, illo } from "@apliteni/apliteni-ui";
3956
- import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
3957
- var cases = {
3958
- "first-run": { title: "Nothing needs you yet", sub: "New items will appear here when they need your attention.", icon: "check" },
3959
- "no-matches": { title: "No results in this view", sub: "Try another search or clear your filters.", icon: "search" },
3960
- "not-found": { title: "We can't find that page", sub: "Check the address or return to the home page.", icon: "folder" },
3961
- "not-yet-built": { title: "This ships later", sub: "This screen is not available yet. Check back later.", icon: "clock" }
3962
- };
3963
- function EmptyState({ variant = "first-run", title, sub, icon: icon8, art, actions }) {
3964
- const preset = cases[variant];
3965
- const heading = title ?? preset.title;
3966
- const guidance = sub ?? preset.sub;
3967
- const glyph = icon8 ?? preset.icon;
3968
- return /* @__PURE__ */ jsxs32("div", { className: "ui-empty", children: [
3969
- art ? typeof art === "string" ? /* @__PURE__ */ jsx37("div", { className: "ui-empty__art", dangerouslySetInnerHTML: { __html: /<svg/.test(art) ? art : illo(art) } }) : /* @__PURE__ */ jsx37("div", { className: "ui-empty__art", "aria-hidden": "true", children: art }) : glyph ? /* @__PURE__ */ jsx37("div", { className: "ui-empty__icon", dangerouslySetInnerHTML: { __html: kitIcon2(glyph) } }) : null,
3970
- heading && /* @__PURE__ */ jsx37("div", { className: "ui-empty__title", children: heading }),
3971
- guidance && /* @__PURE__ */ jsx37("div", { className: "ui-empty__sub", children: guidance }),
3972
- actions && /* @__PURE__ */ jsx37("div", { className: "ui-empty__actions", children: actions })
4012
+ navigable && /* @__PURE__ */ jsx37(Drawer, { open: menu, onClose: () => setMenu(false), title: word, side: "left", size: "sm", children: /* @__PURE__ */ jsx37("div", { className: "ui-react-app__menu", children: sidebar("Menu sections") }) }),
4013
+ /* @__PURE__ */ jsx37(CommandPalette, { ...palette ?? { groups: [{ items: sections.map((section) => ({ id: section.href, ...section })) }] }, open: search, onClose: () => setSearch(false) })
3973
4014
  ] });
3974
4015
  }
3975
4016
 
@@ -48,9 +48,11 @@ export function button({
48
48
  }
49
49
 
50
50
  // ---- Badge / Pill --------------------------------------------------------
51
+ // Neutral is written out rather than left implied: a status column reading its tone off the
52
+ // class list found nothing on two of four rows, and the tone a chip is in is not a default
53
+ // the markup should have to know. The base class carries the same fill either way. #459
51
54
  export function badge(label, variant = 'neutral') {
52
- const v = variant === 'neutral' ? '' : `ui-badge--${variant}`;
53
- return `<span class="${esc(cx('ui-badge', v))}">${esc(label)}</span>`;
55
+ return `<span class="${esc(cx('ui-badge', `ui-badge--${variant}`))}">${esc(label)}</span>`;
54
56
  }
55
57
  export function pill(label, variant) {
56
58
  return `<span class="${esc(cx('ui-pill', variant && `ui-pill--${variant}`))}">${esc(label)}</span>`;
@@ -93,6 +93,10 @@
93
93
  /* Title to description: the step, not the 5px it was. why: docs/foundations.md#spacing-and-rhythm */
94
94
  margin: 0 0 var(--space-2);
95
95
  }
96
+ /* The title of a card that supports its neighbour, such as a document beside the
97
+ data read from it. The size, weight and rank stay the card title's.
98
+ why: docs/foundations.md#text-ink */
99
+ .ui-card__title--muted { /* muted-ink: chrome — a supporting card ranks below its neighbour; the title is also the card's accessible name */ color: var(--muted); }
96
100
  .ui-card__sub {
97
101
  color: var(--text);
98
102
  font-size: var(--text-base);
@@ -81,3 +81,42 @@
81
81
 
82
82
  .ui-drop.is-disabled .ui-drop__row { /* muted-ink: state — the drop accepts nothing */ color: var(--disabled-ink); }
83
83
  .ui-drop.is-disabled .ui-drop__name { /* muted-ink: state — the drop accepts nothing */ color: var(--disabled-ink); }
84
+
85
+ /* The empty page's drop area: the whole region is the target, drawn as a dashed
86
+ box around the first-run block. A file over it turns the edge to the accent,
87
+ the screen's one accent line, the way .ui-drop__target draws its own. */
88
+ .ui-drop--area { border: 1px dashed var(--border-strong); border-radius: var(--radius-lg); padding: var(--space-4); }
89
+ .ui-drop--area.is-dragging { border-color: var(--accent); }
90
+ @media (max-width: 560px) { .ui-drop--area { padding: var(--space-3); } }
91
+
92
+ /* The whole page as a target, drawn only while a file is dragged over the page. It is the
93
+ empty page's drop area made large: the same dashed accent edge and radius, holding the upload
94
+ glyph, a line that says to drop the files and the accepted types, laid over the page content below
95
+ the topbar. Its ground is the page ground, slightly transparent and blurred, so the table shows
96
+ through softly and no row stays sharp under the words. Where the engine cannot blur the backdrop,
97
+ or the reader asks for less transparency, the ground is solid. The layer takes no pointer events,
98
+ so the drop lands on what is under it. */
99
+ .ui-drop--page {
100
+ position: fixed;
101
+ inset: calc(var(--ui-app-band, 0px) + var(--ui-app-top, 0px) + var(--space-4)) var(--space-4) var(--space-4);
102
+ z-index: var(--z-overlay);
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ pointer-events: none;
107
+ background: var(--bg); /* code-bg: inherit — the layer holds no focus target and takes no pointer. */
108
+ }
109
+ @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
110
+ .ui-drop--page {
111
+ background: color-mix(in srgb, var(--bg) 80%, transparent); /* code-bg: inherit — the layer holds no focus target and takes no pointer. */
112
+ -webkit-backdrop-filter: blur(var(--space-2));
113
+ backdrop-filter: blur(var(--space-2));
114
+ }
115
+ }
116
+ @media (prefers-reduced-transparency: reduce) {
117
+ .ui-drop--page {
118
+ background: var(--bg); /* code-bg: inherit — the layer holds no focus target and takes no pointer. */
119
+ -webkit-backdrop-filter: none;
120
+ backdrop-filter: none;
121
+ }
122
+ }
@@ -332,7 +332,7 @@
332
332
  the three heights below are for. Level, not one continuous stroke: the rail insets
333
333
  its own rule, and in light that half is 1.009:1 on the rail's ground.
334
334
  why: docs/components.md#the-second-layout */
335
- .ui-app { --ui-app-band: 52px; }
335
+ .ui-app--topbar { --ui-app-band: 52px; }
336
336
  .ui-app__well { display: flex; flex-direction: column; min-width: 0; }
337
337
  .ui-app__bar {
338
338
  display: flex;
@@ -516,15 +516,17 @@
516
516
  @media (max-width: 720px) {
517
517
  /* A column, not a grid: the band sticks to the top of the shell, and a sticky grid
518
518
  item sticks only inside its own row. */
519
- .ui-app { display: flex; flex-direction: column; }
519
+ .ui-app { display: flex; flex-direction: column; --ui-app-band: 52px; }
520
520
  .ui-app > .ui-app__rail { display: none; }
521
521
  .ui-app__well { flex: 1 1 auto; }
522
522
  .ui-app > .ui-app__main { flex: 1 1 auto; }
523
523
  .ui-app > .ui-app__bar--phone { display: flex; }
524
524
  .ui-btn.ui-app__menu { display: inline-flex; }
525
525
  .ui-app__brand--band { display: flex; }
526
- /* The menu control stands at the band's start with the band's inset beside it. */
527
- .ui-app__bar { padding-inline: var(--space-2) var(--space-4); }
526
+ /* The first visible item lines up with the page. A leading menu uses the smaller
527
+ inset to keep its phone position in both shells. */
528
+ .ui-app__bar { padding-inline: var(--space-4); }
529
+ .ui-app__bar:has(> .ui-app__menu:first-child) { padding-inline-start: var(--space-2); }
528
530
  /* The drawer paints itself and sits out of the flow, so its wrapper takes no height. */
529
531
  .ui-app__drawer { display: block; }
530
532
  /* The rail's rows are the drawer's rows: the column fills the panel, and the foot