@duro-app/cli 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/registry.json CHANGED
@@ -30,6 +30,7 @@
30
30
  ]
31
31
  },
32
32
  "name": "ActionBar",
33
+ "native": false,
33
34
  "props": [
34
35
  {
35
36
  "description": "Number of selected items, or 'all'. Hidden when 0.",
@@ -112,6 +113,7 @@
112
113
  ]
113
114
  },
114
115
  "name": "ActionBarProvider",
116
+ "native": false,
115
117
  "props": [
116
118
  {
117
119
  "name": "children",
@@ -146,6 +148,7 @@
146
148
  ]
147
149
  },
148
150
  "name": "Alert",
151
+ "native": true,
149
152
  "props": [
150
153
  {
151
154
  "default": "'info'",
@@ -187,7 +190,7 @@
187
190
  "Main"
188
191
  ]
189
192
  },
190
- "description": "An application's frame: a navigation rail (a labelled nav, the brand at its top, an optional footer at its foot) beside the main content (main), with an optional Header. Below collapseBelow (sm default, md or lg), measured on the shell's own width, the rail is hidden and one sticky bar (a banner, appBarH tall) holds a Menu button that opens the same navigation in a left Drawer, the brand, and the Header's content when there is one — a shell without a Header still has its navigation on a phone. Above it the Header is a row that scrolls with the page and the rail stays sticky. CSS makes the switch: the server renders one HTML for every width with the brand in both places, and display: none hides the inactive copy, so nothing moves after hydration. A skip link (the first tab stop) focuses Main; an Aside inside Main sticks below the bar. Labels come from props. Compound component — Root is required.",
193
+ "description": "An application's frame: a navigation rail (a labelled nav, the brand at its top, an optional footer at its foot) beside the main content (main), with an optional Header. Below collapseBelow (sm default, md or lg), measured on the shell's own width, the rail is hidden and one sticky bar (a banner, appBarH tall) holds a Menu button that opens the same navigation in a left Drawer, the brand, and the Header's content when there is one — a shell without a Header still has its navigation on a phone. Above it the Header is a row that scrolls with the page and the rail stays sticky. CSS makes the switch: the server renders one HTML for every width with the brand in both places, and display: none hides the inactive copy, so nothing moves after hydration. A skip link (the first tab stop) focuses Main; an Aside inside Main sticks below the bar. Labels come from props. Compound component — Root is required. On React Native it is always the narrow shape: the bar below the status bar, Main under it, and the Rail in a left Drawer; there is no skip link.",
191
194
  "example": "<AppShell.Root\n menuLabel=\"Menu\"\n closeLabel=\"Close navigation\"\n skipLabel=\"Skip to content\"\n brand={<TextLink href=\"/\">Acme</TextLink>}\n collapseBelow=\"md\"\n>\n <AppShell.Rail aria-label=\"Navigation\" footer={<Text variant=\"bodySm\">{user.name}</Text>}>\n {({close}) => (\n <SideNav.Root value={pathname} onValueChange={(v) => { close(); navigate(v) }}>\n <SideNav.Item value=\"/docs\">Docs</SideNav.Item>\n </SideNav.Root>\n )}\n </AppShell.Rail>\n <AppShell.Header>{search}</AppShell.Header>\n <AppShell.Main>{children}</AppShell.Main>\n</AppShell.Root>",
192
195
  "relatedTo": [
193
196
  {
@@ -222,6 +225,7 @@
222
225
  ]
223
226
  },
224
227
  "name": "AppShell",
228
+ "native": true,
225
229
  "parts": {
226
230
  "Header": {
227
231
  "name": "Header",
@@ -412,6 +416,7 @@
412
416
  ]
413
417
  },
414
418
  "name": "Aside",
419
+ "native": false,
415
420
  "props": [
416
421
  {
417
422
  "description": "Names the landmark (\"Page outline\"); required, from your text catalog.",
@@ -467,6 +472,7 @@
467
472
  ]
468
473
  },
469
474
  "name": "Badge",
475
+ "native": true,
470
476
  "props": [
471
477
  {
472
478
  "default": "'default'",
@@ -535,6 +541,7 @@
535
541
  ]
536
542
  },
537
543
  "name": "Breadcrumb",
544
+ "native": false,
538
545
  "parts": {
539
546
  "Item": {
540
547
  "name": "Item",
@@ -615,6 +622,7 @@
615
622
  ]
616
623
  },
617
624
  "name": "Button",
625
+ "native": true,
618
626
  "props": [
619
627
  {
620
628
  "default": "'primary'",
@@ -681,7 +689,7 @@
681
689
  "type": "string"
682
690
  },
683
691
  {
684
- "description": "The underlying <button>, e.g. to anchor a Popover or return focus to it. Web only: on native the ref is not attached.",
692
+ "description": "The underlying <button>, e.g. to anchor a Popover or return focus to it. On native it is react-strict-dom's element (a Pressable host), which measures with `measureInWindow` and takes accessibility focus.",
685
693
  "name": "ref",
686
694
  "required": false,
687
695
  "type": "Ref<HTMLButtonElement>"
@@ -735,6 +743,7 @@
735
743
  ]
736
744
  },
737
745
  "name": "ButtonGroup",
746
+ "native": false,
738
747
  "props": [
739
748
  {
740
749
  "name": "children",
@@ -810,6 +819,7 @@
810
819
  ]
811
820
  },
812
821
  "name": "Callout",
822
+ "native": true,
813
823
  "props": [
814
824
  {
815
825
  "default": "'info'",
@@ -869,6 +879,7 @@
869
879
  ]
870
880
  },
871
881
  "name": "Card",
882
+ "native": true,
872
883
  "props": [
873
884
  {
874
885
  "default": "'outlined'",
@@ -939,6 +950,7 @@
939
950
  ]
940
951
  },
941
952
  "name": "Checkbox",
953
+ "native": true,
942
954
  "props": [
943
955
  {
944
956
  "name": "name",
@@ -1020,6 +1032,7 @@
1020
1032
  ]
1021
1033
  },
1022
1034
  "name": "CheckboxGroup",
1035
+ "native": false,
1023
1036
  "parts": {
1024
1037
  "Item": {
1025
1038
  "name": "Item",
@@ -1127,6 +1140,7 @@
1127
1140
  ]
1128
1141
  },
1129
1142
  "name": "Cluster",
1143
+ "native": true,
1130
1144
  "props": [
1131
1145
  {
1132
1146
  "default": "'sm'",
@@ -1192,6 +1206,7 @@
1192
1206
  ]
1193
1207
  },
1194
1208
  "name": "CodeBlock",
1209
+ "native": false,
1195
1210
  "props": [
1196
1211
  {
1197
1212
  "description": "The code: text, or the `<code>` element a Markdown renderer produced (highlighted spans included). What is copied is its text content.",
@@ -1255,6 +1270,7 @@
1255
1270
  ]
1256
1271
  },
1257
1272
  "name": "ColorInput",
1273
+ "native": false,
1258
1274
  "props": [
1259
1275
  {
1260
1276
  "description": "Hex color, controlled (e.g. \"#cfc8ba\").",
@@ -1350,6 +1366,7 @@
1350
1366
  ]
1351
1367
  },
1352
1368
  "name": "ColorModeProvider",
1369
+ "native": false,
1353
1370
  "props": [
1354
1371
  {
1355
1372
  "name": "children",
@@ -1437,6 +1454,7 @@
1437
1454
  ]
1438
1455
  },
1439
1456
  "name": "ColorModeToggle",
1457
+ "native": false,
1440
1458
  "props": [
1441
1459
  {
1442
1460
  "default": "'md'",
@@ -1512,6 +1530,7 @@
1512
1530
  ]
1513
1531
  },
1514
1532
  "name": "Combobox",
1533
+ "native": false,
1515
1534
  "parts": {
1516
1535
  "Empty": {
1517
1536
  "name": "Empty",
@@ -1680,6 +1699,7 @@
1680
1699
  ]
1681
1700
  },
1682
1701
  "name": "ConfirmDialog",
1702
+ "native": true,
1683
1703
  "props": [
1684
1704
  {
1685
1705
  "name": "open",
@@ -1805,6 +1825,7 @@
1805
1825
  ]
1806
1826
  },
1807
1827
  "name": "DetailPanel",
1828
+ "native": false,
1808
1829
  "parts": {
1809
1830
  "Body": {
1810
1831
  "name": "Body",
@@ -2056,6 +2077,7 @@
2056
2077
  ]
2057
2078
  },
2058
2079
  "name": "Dialog",
2080
+ "native": true,
2059
2081
  "parts": {
2060
2082
  "Body": {
2061
2083
  "name": "Body",
@@ -2066,7 +2088,7 @@
2066
2088
  "type": "ReactNode"
2067
2089
  }
2068
2090
  ],
2069
- "sourcePath": "packages/ui/src/components/Dialog/Dialog.tsx"
2091
+ "sourcePath": "packages/ui/src/components/Dialog/parts.tsx"
2070
2092
  },
2071
2093
  "Close": {
2072
2094
  "name": "Close",
@@ -2094,7 +2116,7 @@
2094
2116
  "type": "ReactNode"
2095
2117
  }
2096
2118
  ],
2097
- "sourcePath": "packages/ui/src/components/Dialog/Dialog.tsx"
2119
+ "sourcePath": "packages/ui/src/components/Dialog/parts.tsx"
2098
2120
  },
2099
2121
  "Footer": {
2100
2122
  "name": "Footer",
@@ -2105,7 +2127,7 @@
2105
2127
  "type": "ReactNode"
2106
2128
  }
2107
2129
  ],
2108
- "sourcePath": "packages/ui/src/components/Dialog/Dialog.tsx"
2130
+ "sourcePath": "packages/ui/src/components/Dialog/parts.tsx"
2109
2131
  },
2110
2132
  "Header": {
2111
2133
  "name": "Header",
@@ -2116,7 +2138,7 @@
2116
2138
  "type": "ReactNode"
2117
2139
  }
2118
2140
  ],
2119
- "sourcePath": "packages/ui/src/components/Dialog/Dialog.tsx"
2141
+ "sourcePath": "packages/ui/src/components/Dialog/parts.tsx"
2120
2142
  },
2121
2143
  "Portal": {
2122
2144
  "name": "Portal",
@@ -2215,7 +2237,7 @@
2215
2237
  "type": "ReactNode"
2216
2238
  }
2217
2239
  ],
2218
- "sourcePath": "packages/ui/src/components/Dialog/Dialog.tsx"
2240
+ "sourcePath": "packages/ui/src/components/Dialog/parts.tsx"
2219
2241
  },
2220
2242
  "Trigger": {
2221
2243
  "name": "Trigger",
@@ -2272,6 +2294,7 @@
2272
2294
  ]
2273
2295
  },
2274
2296
  "name": "DragDrop",
2297
+ "native": false,
2275
2298
  "parts": {
2276
2299
  "Handle": {
2277
2300
  "name": "Handle",
@@ -2480,7 +2503,7 @@
2480
2503
  "Portal"
2481
2504
  ]
2482
2505
  },
2483
- "description": "Modal sliding panel from a screen edge (right, left, or bottom). Slides in with backdrop overlay. Supports swipe-to-dismiss gestures on mobile, backdrop click dismiss, and Escape key. Swipe yields to an inner Duro ScrollArea: a drag there scrolls it, and dismisses only toward the edge from the scroll start (a native overflow:auto child is not covered — use ScrollArea). Use for secondary workflows, settings panels, or mobile-friendly detail views.",
2506
+ "description": "Modal sliding panel from a screen edge (right, left, or bottom). Slides in with backdrop overlay. Supports swipe-to-dismiss gestures on mobile, backdrop click dismiss, and Escape key. Swipe yields to an inner Duro ScrollArea: a drag there scrolls it, and dismisses only toward the edge from the scroll start (a native overflow:auto child is not covered — use ScrollArea). A bottom drawer with snapPoints (medium and large) is a phone sheet: it rests at its detents, a grabber steps through them on a tap, a drag moves between them and dismisses below the lowest; its Body is the scroll the drag defers to. Header titleAlign=\"center\" centres the title with a text close at the end. Use for secondary workflows, settings panels, or mobile-friendly detail views.",
2484
2507
  "example": "<Drawer.Root anchor=\"right\" open={open} onOpenChange={setOpen}>\n <Drawer.Trigger>\n <Button>Open drawer</Button>\n </Drawer.Trigger>\n <Drawer.Portal size=\"md\">\n <Drawer.Header>\n <Drawer.Title>Settings</Drawer.Title>\n <Drawer.Close />\n </Drawer.Header>\n <Drawer.Body>\n <Stack gap=\"md\">\n <Text>Drawer content goes here.</Text>\n </Stack>\n </Drawer.Body>\n <Drawer.Footer>\n <Button variant=\"secondary\" onClick={() => setOpen(false)}>Done</Button>\n </Drawer.Footer>\n </Drawer.Portal>\n</Drawer.Root>",
2485
2508
  "relatedTo": [
2486
2509
  {
@@ -2508,11 +2531,13 @@
2508
2531
  "whenToUse": [
2509
2532
  "Settings or configuration panels that slide in from the side",
2510
2533
  "Mobile-friendly detail views (bottom sheet pattern)",
2534
+ "Phone pickers and lists that need more room on demand (snapPoints)",
2511
2535
  "Secondary workflows that need more space than a dialog",
2512
2536
  "Filters, navigation drawers, or form panels"
2513
2537
  ]
2514
2538
  },
2515
2539
  "name": "Drawer",
2540
+ "native": true,
2516
2541
  "parts": {
2517
2542
  "Body": {
2518
2543
  "name": "Body",
@@ -2551,7 +2576,7 @@
2551
2576
  "type": "ReactNode"
2552
2577
  }
2553
2578
  ],
2554
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2579
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2555
2580
  },
2556
2581
  "Footer": {
2557
2582
  "name": "Footer",
@@ -2562,7 +2587,7 @@
2562
2587
  "type": "ReactNode"
2563
2588
  }
2564
2589
  ],
2565
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2590
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2566
2591
  },
2567
2592
  "Header": {
2568
2593
  "name": "Header",
@@ -2571,9 +2596,16 @@
2571
2596
  "name": "children",
2572
2597
  "required": true,
2573
2598
  "type": "ReactNode"
2599
+ },
2600
+ {
2601
+ "default": "'start'",
2602
+ "description": "'center' puts the Title in the middle and the other children at the end, as a phone sheet does (a text close button on the right). Default: 'start'",
2603
+ "name": "titleAlign",
2604
+ "required": false,
2605
+ "type": "'start' | 'center'"
2574
2606
  }
2575
2607
  ],
2576
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2608
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2577
2609
  },
2578
2610
  "Portal": {
2579
2611
  "name": "Portal",
@@ -2593,6 +2625,13 @@
2593
2625
  "md",
2594
2626
  "lg"
2595
2627
  ]
2628
+ },
2629
+ {
2630
+ "default": "DEFAULT_GRABBER_LABELS",
2631
+ "description": "The grabber's accessible names, by what a press does next. Snap sheets only",
2632
+ "name": "grabberLabels",
2633
+ "required": false,
2634
+ "type": "GrabberLabels"
2596
2635
  }
2597
2636
  ],
2598
2637
  "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
@@ -2612,7 +2651,6 @@
2612
2651
  "type": "boolean"
2613
2652
  },
2614
2653
  {
2615
- "default": "false",
2616
2654
  "description": "Default open state (uncontrolled)",
2617
2655
  "name": "defaultOpen",
2618
2656
  "required": false,
@@ -2625,7 +2663,6 @@
2625
2663
  "type": "(open: boolean) => void"
2626
2664
  },
2627
2665
  {
2628
- "default": "'right'",
2629
2666
  "description": "Which edge the drawer slides from. Default: 'right'",
2630
2667
  "name": "anchor",
2631
2668
  "required": false,
@@ -2637,21 +2674,18 @@
2637
2674
  ]
2638
2675
  },
2639
2676
  {
2640
- "default": "true",
2641
- "description": "Whether clicking backdrop or pressing Escape closes the drawer. Default: true",
2677
+ "description": "Whether clicking backdrop or pressing Escape (Android's back button on native) closes the drawer. Default: true",
2642
2678
  "name": "dismissable",
2643
2679
  "required": false,
2644
2680
  "type": "boolean"
2645
2681
  },
2646
2682
  {
2647
- "default": "true",
2648
2683
  "description": "Whether swipe gesture dismisses the drawer. Default: true",
2649
2684
  "name": "swipeDismiss",
2650
2685
  "required": false,
2651
2686
  "type": "boolean"
2652
2687
  },
2653
2688
  {
2654
- "default": "'fast'",
2655
2689
  "description": "Motion token for the close animation. Default: 'fast' (150ms)",
2656
2690
  "name": "closeAnimationDuration",
2657
2691
  "required": false,
@@ -2665,6 +2699,22 @@
2665
2699
  "base",
2666
2700
  "slow"
2667
2701
  ]
2702
+ },
2703
+ {
2704
+ "description": "Bottom anchor only: the heights the sheet rests at. 'medium' is half the window, 'large' the window less its top safe-area inset. A drag moves between them and dismisses below the lowest; the grabber steps through them on a tap. Without it, a bottom drawer is as tall as its content.",
2705
+ "name": "snapPoints",
2706
+ "required": false,
2707
+ "type": "readonly SnapPoint[]"
2708
+ },
2709
+ {
2710
+ "description": "The detent the sheet opens at. Default: the lowest of snapPoints",
2711
+ "name": "defaultSnapPoint",
2712
+ "required": false,
2713
+ "type": "SnapPoint",
2714
+ "union": [
2715
+ "medium",
2716
+ "large"
2717
+ ]
2668
2718
  }
2669
2719
  ],
2670
2720
  "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
@@ -2678,7 +2728,7 @@
2678
2728
  "type": "ReactNode"
2679
2729
  }
2680
2730
  ],
2681
- "sourcePath": "packages/ui/src/components/Drawer/Drawer.tsx"
2731
+ "sourcePath": "packages/ui/src/components/Drawer/parts.tsx"
2682
2732
  },
2683
2733
  "Trigger": {
2684
2734
  "name": "Trigger",
@@ -2712,6 +2762,7 @@
2712
2762
  ]
2713
2763
  },
2714
2764
  "name": "EmptyState",
2765
+ "native": true,
2715
2766
  "props": [
2716
2767
  {
2717
2768
  "name": "message",
@@ -2794,6 +2845,7 @@
2794
2845
  ]
2795
2846
  },
2796
2847
  "name": "Field",
2848
+ "native": true,
2797
2849
  "parts": {
2798
2850
  "Description": {
2799
2851
  "name": "Description",
@@ -2918,6 +2970,7 @@
2918
2970
  ]
2919
2971
  },
2920
2972
  "name": "Fieldset",
2973
+ "native": false,
2921
2974
  "parts": {
2922
2975
  "Legend": {
2923
2976
  "name": "Legend",
@@ -2990,6 +3043,7 @@
2990
3043
  ]
2991
3044
  },
2992
3045
  "name": "FileTrigger",
3046
+ "native": false,
2993
3047
  "props": [
2994
3048
  {
2995
3049
  "description": "Accepted types, as the native `accept` attribute (e.g. `image/*,.pdf`).",
@@ -3163,6 +3217,7 @@
3163
3217
  ]
3164
3218
  },
3165
3219
  "name": "Grid",
3220
+ "native": true,
3166
3221
  "props": [
3167
3222
  {
3168
3223
  "default": "'md'",
@@ -3248,6 +3303,7 @@
3248
3303
  ]
3249
3304
  },
3250
3305
  "name": "Heading",
3306
+ "native": true,
3251
3307
  "props": [
3252
3308
  {
3253
3309
  "name": "level",
@@ -3317,6 +3373,7 @@
3317
3373
  ]
3318
3374
  },
3319
3375
  "name": "Icon",
3376
+ "native": true,
3320
3377
  "props": [
3321
3378
  {
3322
3379
  "name": "name",
@@ -3355,6 +3412,7 @@
3355
3412
  "plug",
3356
3413
  "search",
3357
3414
  "mic",
3415
+ "x",
3358
3416
  "sun",
3359
3417
  "moon",
3360
3418
  "monitor",
@@ -3386,6 +3444,12 @@
3386
3444
  "xl",
3387
3445
  "xxl"
3388
3446
  ]
3447
+ },
3448
+ {
3449
+ "description": "Colour token. Default: the surrounding text colour on the web (`currentColor`), the `text` token on native, where an icon cannot inherit a colour.",
3450
+ "name": "color",
3451
+ "required": false,
3452
+ "type": "ColorToken"
3389
3453
  }
3390
3454
  ],
3391
3455
  "sourcePath": "packages/ui/src/components/Icon/Icon.tsx"
@@ -3420,6 +3484,7 @@
3420
3484
  ]
3421
3485
  },
3422
3486
  "name": "Inline",
3487
+ "native": true,
3423
3488
  "props": [
3424
3489
  {
3425
3490
  "default": "'sm'",
@@ -3491,6 +3556,7 @@
3491
3556
  ]
3492
3557
  },
3493
3558
  "name": "Input",
3559
+ "native": true,
3494
3560
  "props": [
3495
3561
  {
3496
3562
  "default": "'default'",
@@ -3589,6 +3655,18 @@
3589
3655
  "required": false,
3590
3656
  "type": "| 'on' | 'off' | 'email' | 'username' | 'current-password' | 'new-password' | 'name' | 'tel' | 'url'"
3591
3657
  },
3658
+ {
3659
+ "description": "Phone keyboards: how the typed text is capitalised.",
3660
+ "name": "autoCapitalize",
3661
+ "required": false,
3662
+ "type": "'none' | 'sentences' | 'words' | 'characters'"
3663
+ },
3664
+ {
3665
+ "description": "Whether the platform checks (and on phones, autocorrects) the spelling.",
3666
+ "name": "spellCheck",
3667
+ "required": false,
3668
+ "type": "boolean"
3669
+ },
3592
3670
  {
3593
3671
  "name": "value",
3594
3672
  "required": false,
@@ -3692,6 +3770,7 @@
3692
3770
  ]
3693
3771
  },
3694
3772
  "name": "InputGroup",
3773
+ "native": true,
3695
3774
  "parts": {
3696
3775
  "Addon": {
3697
3776
  "name": "Addon",
@@ -3799,6 +3878,7 @@
3799
3878
  ]
3800
3879
  },
3801
3880
  "name": "LinkButton",
3881
+ "native": false,
3802
3882
  "props": [
3803
3883
  {
3804
3884
  "name": "href",
@@ -3903,6 +3983,7 @@
3903
3983
  ]
3904
3984
  },
3905
3985
  "name": "List",
3986
+ "native": true,
3906
3987
  "parts": {
3907
3988
  "Actions": {
3908
3989
  "name": "Actions",
@@ -4059,6 +4140,7 @@
4059
4140
  ]
4060
4141
  },
4061
4142
  "name": "Listbox",
4143
+ "native": false,
4062
4144
  "parts": {
4063
4145
  "Empty": {
4064
4146
  "name": "Empty",
@@ -4241,6 +4323,7 @@
4241
4323
  ]
4242
4324
  },
4243
4325
  "name": "LiveRegion",
4326
+ "native": false,
4244
4327
  "props": [
4245
4328
  {
4246
4329
  "description": "What to announce. Change it to announce; leave it empty when there is nothing to say — never unmount the region to clear it.",
@@ -4290,7 +4373,7 @@
4290
4373
  "Item"
4291
4374
  ]
4292
4375
  },
4293
- "description": "Dropdown action menu. Triggers actions (not value selection). Compound component — Root is required (throws without it). Menu.Trigger renders the button itself: give it the label (text, an Icon), never a Button; an icon-only trigger takes aria-label, and variant=\"ghost\" suits toolbars; size=\"small\" matches small Toggles and Buttons (a small ghost trigger is iconButtonSm). The popup is portalled and placed against the trigger (never clipped by a sticky or overflow ancestor), capped at sizes.listMaxH with scrolling, and takes focus on open (WAI-ARIA menu button); Escape closes only the menu, not an enclosing Popover or Dialog.",
4376
+ "description": "Dropdown action menu. Triggers actions (not value selection). Compound component — Root is required (throws without it). Menu.Trigger renders the button itself: give it the label (text, an Icon), never a Button; an icon-only trigger takes aria-label, and variant=\"ghost\" suits toolbars; size=\"small\" matches small Toggles and Buttons (a small ghost trigger is iconButtonSm). The popup is portalled and placed against the trigger (never clipped by a sticky or overflow ancestor), capped at sizes.listMaxH with scrolling, and takes focus on open (WAI-ARIA menu button); Escape closes only the menu, not an enclosing Popover or Dialog. On React Native the popup is placed against the trigger in a modal overlay; a press outside or the Android back button closes it, and an item runs its onClick after the menu has gone (so an item can open a Dialog). Menu.LinkItem takes onNavigate(href, event) on both platforms (ADR-0002); on native, without it or without preventDefault, the link opens with the system. Native has no arrow keys: each item is its own screen-reader stop.",
4294
4377
  "example": "<Menu.Root>\n <Menu.Trigger>Actions</Menu.Trigger>\n <Menu.Popup>\n <Menu.Item onClick={() => console.log('edit')}>Edit</Menu.Item>\n <Menu.Item onClick={() => console.log('duplicate')}>Duplicate</Menu.Item>\n <Menu.Separator />\n <Menu.LinkItem href=\"/settings\">Settings</Menu.LinkItem>\n </Menu.Popup>\n</Menu.Root>",
4295
4378
  "relatedTo": [
4296
4379
  {
@@ -4317,6 +4400,7 @@
4317
4400
  ]
4318
4401
  },
4319
4402
  "name": "Menu",
4403
+ "native": true,
4320
4404
  "parts": {
4321
4405
  "Item": {
4322
4406
  "name": "Item",
@@ -4342,6 +4426,12 @@
4342
4426
  "required": true,
4343
4427
  "type": "string"
4344
4428
  },
4429
+ {
4430
+ "description": "Called for a plain primary click (ADR-0002): call `event.preventDefault()`, then your router's navigate. Without it the browser follows the link.",
4431
+ "name": "onNavigate",
4432
+ "required": false,
4433
+ "type": "OnNavigate"
4434
+ },
4345
4435
  {
4346
4436
  "name": "children",
4347
4437
  "required": true,
@@ -4469,6 +4559,7 @@
4469
4559
  ]
4470
4560
  },
4471
4561
  "name": "Meter",
4562
+ "native": true,
4472
4563
  "props": [
4473
4564
  {
4474
4565
  "name": "value",
@@ -4627,6 +4718,7 @@
4627
4718
  ]
4628
4719
  },
4629
4720
  "name": "PageNav",
4721
+ "native": false,
4630
4722
  "parts": {
4631
4723
  "Next": {
4632
4724
  "name": "Next",
@@ -4746,6 +4838,7 @@
4746
4838
  ]
4747
4839
  },
4748
4840
  "name": "PageShell",
4841
+ "native": true,
4749
4842
  "props": [
4750
4843
  {
4751
4844
  "default": "'lg'",
@@ -4825,6 +4918,7 @@
4825
4918
  ]
4826
4919
  },
4827
4920
  "name": "Panel",
4921
+ "native": false,
4828
4922
  "parts": {
4829
4923
  "Body": {
4830
4924
  "name": "Body",
@@ -4902,7 +4996,7 @@
4902
4996
  "Popup"
4903
4997
  ]
4904
4998
  },
4905
- "description": "Non-modal anchored overlay for small interactive content. Compound component — Root is required (throws without it). Anchored to Popover.Trigger, or to a virtual anchor (a () => DOMRect) when there is no Trigger; call reposition() through a ref when the anchor moves. Popover.Trigger takes size=\"small\" beside small Toggles. Popover.Popup raised draws it over another floating surface (floatingRaised, popoverRaised in a modal), never over a modal.",
4999
+ "description": "Non-modal anchored overlay for small interactive content. Compound component — Root is required (throws without it). Anchored to Popover.Trigger, or to a virtual anchor (a () => DOMRect) when there is no Trigger; call reposition() through a ref when the anchor moves. Popover.Trigger takes size=\"small\" beside small Toggles. Popover.Popup raised draws it over another floating surface (floatingRaised, popoverRaised in a modal), never over a modal. On React Native the popup is modal: it is placed against Popover.Trigger in an overlay, a press outside or the Android back button closes it, and screen-reader focus moves in on open and back to the trigger on close; anchor, ignore and raised are web only.",
4906
5000
  "example": "const ref = useRef<PopoverHandle>(null)\nconst anchor = () => new DOMRect(point.x, point.y, 0, 0)\n\n<Popover.Root ref={ref} open={open} onOpenChange={setOpen} anchor={anchor} side=\"bottom\" align=\"start\">\n <Popover.Popup label=\"Edit label\">\n <Field.Root>\n <Field.Label>Label</Field.Label>\n <Input value={label} onChange={(e) => setLabel(e.target.value)} />\n </Field.Root>\n <Popover.Close>Done</Popover.Close>\n </Popover.Popup>\n</Popover.Root>\n// after the anchor moves: ref.current?.reposition()",
4907
5001
  "relatedTo": [
4908
5002
  {
@@ -4956,6 +5050,7 @@
4956
5050
  ]
4957
5051
  },
4958
5052
  "name": "Popover",
5053
+ "native": true,
4959
5054
  "parts": {
4960
5055
  "Close": {
4961
5056
  "name": "Close",
@@ -5113,6 +5208,7 @@
5113
5208
  ]
5114
5209
  },
5115
5210
  "name": "Prose",
5211
+ "native": false,
5116
5212
  "props": [
5117
5213
  {
5118
5214
  "description": "HTML Duro does not author: the output of a Markdown renderer.",
@@ -5134,7 +5230,7 @@
5134
5230
  "kind": "compound",
5135
5231
  "meta": {
5136
5232
  "description": "Radio button group for single-select from a list of options. Compound component with Root container and Item children.",
5137
- "example": "<RadioGroup.Root defaultValue=\"email\" onValueChange={(v) => console.log(v)}>\n <RadioGroup.Item value=\"email\">Email</RadioGroup.Item>\n <RadioGroup.Item value=\"sms\">SMS</RadioGroup.Item>\n <RadioGroup.Item value=\"push\">Push notification</RadioGroup.Item>\n</RadioGroup.Root>",
5233
+ "example": "<RadioGroup.Root name=\"channel\" defaultValue=\"email\" onValueChange={(v) => console.log(v)}>\n <RadioGroup.Item value=\"email\">Email</RadioGroup.Item>\n <RadioGroup.Item value=\"sms\">SMS</RadioGroup.Item>\n <RadioGroup.Item value=\"push\">Push notification</RadioGroup.Item>\n</RadioGroup.Root>",
5138
5234
  "relatedTo": [
5139
5235
  {
5140
5236
  "component": "CheckboxGroup",
@@ -5163,6 +5259,7 @@
5163
5259
  ]
5164
5260
  },
5165
5261
  "name": "RadioGroup",
5262
+ "native": true,
5166
5263
  "parts": {
5167
5264
  "Item": {
5168
5265
  "name": "Item",
@@ -5217,6 +5314,12 @@
5217
5314
  "required": false,
5218
5315
  "type": "boolean"
5219
5316
  },
5317
+ {
5318
+ "description": "Form field name: every item's radio carries it, so the selection is submitted with a native form. Generated when omitted (the radios still form one group).",
5319
+ "name": "name",
5320
+ "required": false,
5321
+ "type": "string"
5322
+ },
5220
5323
  {
5221
5324
  "description": "Accessible name when the group is not inside a Field.Root (inside one, the Field.Label names it).",
5222
5325
  "name": "aria-label",
@@ -5235,7 +5338,7 @@
5235
5338
  "type": "ReactNode"
5236
5339
  }
5237
5340
  ],
5238
- "sourcePath": "packages/ui/src/components/RadioGroup/RadioGroup.tsx"
5341
+ "sourcePath": "packages/ui/src/components/RadioGroup/group.tsx"
5239
5342
  }
5240
5343
  },
5241
5344
  "props": null,
@@ -5266,6 +5369,7 @@
5266
5369
  ]
5267
5370
  },
5268
5371
  "name": "ScrollArea",
5372
+ "native": false,
5269
5373
  "parts": {
5270
5374
  "Content": {
5271
5375
  "name": "Content",
@@ -5398,6 +5502,7 @@
5398
5502
  ]
5399
5503
  },
5400
5504
  "name": "Select",
5505
+ "native": false,
5401
5506
  "parts": {
5402
5507
  "Icon": {
5403
5508
  "name": "Icon",
@@ -5540,7 +5645,7 @@
5540
5645
  "Item"
5541
5646
  ]
5542
5647
  },
5543
- "description": "Vertical side navigation. `Section` is a labelled block that is always open — the default for primary navigation. `Group` is the same block behind a chevron (a disclosure) — reach for it only for a rare/advanced region, an unbounded data-driven list, or a rail long enough that flat stops reading as an overview; mixing the two in one rail is the healthy shape. `Item`s take an optional `icon` and show an active left-marker. Supports controlled and uncontrolled active item. Compound component — Root is required.",
5648
+ "description": "Vertical side navigation. `Section` is a labelled block that is always open — the default for primary navigation. `Group` is the same block behind a chevron (a disclosure) — reach for it only for a rare/advanced region, an unbounded data-driven list, or a rail long enough that flat stops reading as an overview; mixing the two in one rail is the healthy shape. `Item`s take an optional `icon` and show an active left-marker. Supports controlled and uncontrolled active item. Compound component — Root is required. On React Native the same parts render, and a Group knows whether it holds the active item from its Items, not from the DOM.",
5544
5649
  "example": "<SideNav.Root defaultValue=\"identities\">\n {/* Default: always-open sections. Nothing is a click away from being seen. */}\n <SideNav.Section label=\"People & access\">\n <SideNav.Item value=\"identities\" icon={<Icon name=\"users\" size=\"md\" />}>Identities</SideNav.Item>\n <SideNav.Item value=\"grants\" icon={<Icon name=\"key\" size=\"md\" />}>Grants</SideNav.Item>\n </SideNav.Section>\n {/* Disclosure, earned: rarely visited, so it starts collapsed. */}\n <SideNav.Group label=\"Advanced\" defaultExpanded={false}>\n <SideNav.Item value=\"plugins\" icon={<Icon name=\"plug\" size=\"md\" />}>Plugins</SideNav.Item>\n </SideNav.Group>\n</SideNav.Root>",
5545
5650
  "relatedTo": [
5546
5651
  {
@@ -5573,6 +5678,7 @@
5573
5678
  ]
5574
5679
  },
5575
5680
  "name": "SideNav",
5681
+ "native": true,
5576
5682
  "parts": {
5577
5683
  "Group": {
5578
5684
  "name": "Group",
@@ -5691,6 +5797,7 @@
5691
5797
  ]
5692
5798
  },
5693
5799
  "name": "Spinner",
5800
+ "native": true,
5694
5801
  "props": [
5695
5802
  {
5696
5803
  "default": "'md'",
@@ -5712,6 +5819,130 @@
5712
5819
  ],
5713
5820
  "sourcePath": "packages/ui/src/components/Spinner/Spinner.tsx"
5714
5821
  },
5822
+ "SplitPane": {
5823
+ "importPath": "@duro-app/ui",
5824
+ "kind": "compound",
5825
+ "meta": {
5826
+ "anatomy": {
5827
+ "optional": [
5828
+ "Handle"
5829
+ ],
5830
+ "required": [
5831
+ "Root",
5832
+ "Pane"
5833
+ ]
5834
+ },
5835
+ "description": "Two panes side by side with a resizable separator: the first Pane is sized by the Handle (pointer drag, arrow keys, Shift for larger steps, Home and End), the second takes the rest. Min, max and starting widths are size tokens. Below collapseBelow, measured on its own width, the panes stack in one column and the handle goes; on native they always stack. The Handle is a hairline that thickens to the accent on hover, keyboard focus or drag.",
5836
+ "example": "<SplitPane.Root aria-label=\"Resize the session list\" minSize=\"sidebarW\" maxSize=\"panelLg\">\n <SplitPane.Pane>\n <List.Root aria-label=\"Sessions\">…</List.Root>\n </SplitPane.Pane>\n <SplitPane.Handle />\n <SplitPane.Pane>\n <Panel.Root>…</Panel.Root>\n </SplitPane.Pane>\n</SplitPane.Root>",
5837
+ "relatedTo": [
5838
+ {
5839
+ "component": "Grid",
5840
+ "kind": "contrast",
5841
+ "relationship": "Grid layout=\"split\" is a fixed split; SplitPane lets people resize it"
5842
+ },
5843
+ {
5844
+ "component": "DetailPanel",
5845
+ "kind": "contrast",
5846
+ "relationship": "DetailPanel overlays the list; SplitPane keeps both side by side"
5847
+ }
5848
+ ],
5849
+ "whenNotToUse": [
5850
+ "A fixed list/detail layout nobody resizes — use Grid layout=\"split\"",
5851
+ "Navigation beside content — use AppShell or the page-with-sidenav recipe",
5852
+ "A short-lived inspection over a list — use DetailPanel"
5853
+ ],
5854
+ "whenToUse": [
5855
+ "A list beside its detail where people need more room on either side (a session list and its transcript)",
5856
+ "An editor beside its preview"
5857
+ ]
5858
+ },
5859
+ "name": "SplitPane",
5860
+ "native": true,
5861
+ "parts": {
5862
+ "Handle": {
5863
+ "name": "Handle",
5864
+ "props": [],
5865
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5866
+ },
5867
+ "Pane": {
5868
+ "name": "Pane",
5869
+ "props": [
5870
+ {
5871
+ "name": "children",
5872
+ "required": true,
5873
+ "type": "ReactNode"
5874
+ }
5875
+ ],
5876
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5877
+ },
5878
+ "Root": {
5879
+ "name": "Root",
5880
+ "props": [
5881
+ {
5882
+ "name": "children",
5883
+ "required": true,
5884
+ "type": "ReactNode"
5885
+ },
5886
+ {
5887
+ "default": "'sidebarW'",
5888
+ "description": "The primary (first) pane's starting width, a size token. Default: `sidebarW`",
5889
+ "name": "defaultSize",
5890
+ "required": false,
5891
+ "type": "SizeToken"
5892
+ },
5893
+ {
5894
+ "description": "Controlled width of the primary pane, in px (from `onSizeChange`)",
5895
+ "name": "size",
5896
+ "required": false,
5897
+ "type": "number"
5898
+ },
5899
+ {
5900
+ "description": "Called with the primary pane's new width while the handle moves",
5901
+ "name": "onSizeChange",
5902
+ "required": false,
5903
+ "type": "(px: number) => void"
5904
+ },
5905
+ {
5906
+ "default": "'sidebarW'",
5907
+ "description": "The primary pane's narrowest width, a size token. Default: `sidebarW`",
5908
+ "name": "minSize",
5909
+ "required": false,
5910
+ "type": "SizeToken"
5911
+ },
5912
+ {
5913
+ "default": "'panelLg'",
5914
+ "description": "The primary pane's widest width, a size token. Default: `panelLg`",
5915
+ "name": "maxSize",
5916
+ "required": false,
5917
+ "type": "SizeToken"
5918
+ },
5919
+ {
5920
+ "default": "'sm'",
5921
+ "description": "Below this breakpoint, measured on the split's own width, the panes stack. Default: `sm`",
5922
+ "name": "collapseBelow",
5923
+ "required": false,
5924
+ "type": "Breakpoint",
5925
+ "union": [
5926
+ "xs",
5927
+ "sm",
5928
+ "md",
5929
+ "lg",
5930
+ "xl"
5931
+ ]
5932
+ },
5933
+ {
5934
+ "description": "The handle's accessible name (\"Resize list\").",
5935
+ "name": "aria-label",
5936
+ "required": true,
5937
+ "type": "string"
5938
+ }
5939
+ ],
5940
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5941
+ }
5942
+ },
5943
+ "props": null,
5944
+ "sourcePath": "packages/ui/src/components/SplitPane/SplitPane.tsx"
5945
+ },
5715
5946
  "Stack": {
5716
5947
  "importPath": "@duro-app/ui",
5717
5948
  "kind": "component",
@@ -5740,6 +5971,7 @@
5740
5971
  ]
5741
5972
  },
5742
5973
  "name": "Stack",
5974
+ "native": true,
5743
5975
  "props": [
5744
5976
  {
5745
5977
  "default": "'md'",
@@ -5800,6 +6032,7 @@
5800
6032
  ]
5801
6033
  },
5802
6034
  "name": "StatusIcon",
6035
+ "native": true,
5803
6036
  "props": [
5804
6037
  {
5805
6038
  "name": "name",
@@ -5870,6 +6103,7 @@
5870
6103
  ]
5871
6104
  },
5872
6105
  "name": "Switch",
6106
+ "native": true,
5873
6107
  "props": [
5874
6108
  {
5875
6109
  "description": "Controlled checked state.",
@@ -5951,6 +6185,7 @@
5951
6185
  ]
5952
6186
  },
5953
6187
  "name": "Table",
6188
+ "native": false,
5954
6189
  "parts": {
5955
6190
  "Body": {
5956
6191
  "name": "Body",
@@ -6498,6 +6733,7 @@
6498
6733
  ]
6499
6734
  },
6500
6735
  "name": "TableOfContents",
6736
+ "native": false,
6501
6737
  "parts": {
6502
6738
  "Item": {
6503
6739
  "name": "Item",
@@ -6600,7 +6836,7 @@
6600
6836
  "Panel"
6601
6837
  ]
6602
6838
  },
6603
- "description": "Tabbed interface with keyboard navigation. Supports horizontal and vertical orientation. Compound component — Root is required.",
6839
+ "description": "Tabbed interface with keyboard navigation. Supports horizontal and vertical orientation. Compound component — Root is required. On React Native the indicator follows each tab layout; there is no arrow-key navigation there, each tab is its own screen-reader stop.",
6604
6840
  "example": "<Tabs.Root defaultValue=\"general\">\n <Tabs.List>\n <Tabs.Tab value=\"general\">General</Tabs.Tab>\n <Tabs.Tab value=\"security\">Security</Tabs.Tab>\n <Tabs.Tab value=\"billing\" disabled>Billing</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panel value=\"general\">General settings content</Tabs.Panel>\n <Tabs.Panel value=\"security\">Security settings content</Tabs.Panel>\n <Tabs.Panel value=\"billing\">Billing content</Tabs.Panel>\n</Tabs.Root>",
6605
6841
  "relatedTo": [
6606
6842
  {
@@ -6625,6 +6861,7 @@
6625
6861
  ]
6626
6862
  },
6627
6863
  "name": "Tabs",
6864
+ "native": true,
6628
6865
  "parts": {
6629
6866
  "List": {
6630
6867
  "name": "List",
@@ -6715,7 +6952,7 @@
6715
6952
  "importPath": "@duro-app/ui",
6716
6953
  "kind": "component",
6717
6954
  "meta": {
6718
- "description": "Interactive tag/chip with optional remove button. Works standalone or inside TagGroup for collection management. Visually similar to Badge but supports interaction.",
6955
+ "description": "Interactive tag/chip with optional remove button. Works standalone or inside TagGroup for collection management. Visually similar to Badge but supports interaction. On React Native a Tag stands alone (TagGroup is web only): the label and, when removable, a remove button named from a string label.",
6719
6956
  "example": "<Tag removable onRemove={() => console.log('removed')}>\n user@example.com\n</Tag>",
6720
6957
  "relatedTo": [
6721
6958
  {
@@ -6740,6 +6977,7 @@
6740
6977
  ]
6741
6978
  },
6742
6979
  "name": "Tag",
6980
+ "native": true,
6743
6981
  "props": [
6744
6982
  {
6745
6983
  "description": "Value identifier — required when used inside TagGroup",
@@ -6838,6 +7076,7 @@
6838
7076
  ]
6839
7077
  },
6840
7078
  "name": "TagGroup",
7079
+ "native": false,
6841
7080
  "parts": {
6842
7081
  "Input": {
6843
7082
  "name": "Input",
@@ -6944,6 +7183,7 @@
6944
7183
  ]
6945
7184
  },
6946
7185
  "name": "Text",
7186
+ "native": true,
6947
7187
  "props": [
6948
7188
  {
6949
7189
  "default": "'bodyMd'",
@@ -7090,6 +7330,7 @@
7090
7330
  ]
7091
7331
  },
7092
7332
  "name": "TextLink",
7333
+ "native": false,
7093
7334
  "props": [
7094
7335
  {
7095
7336
  "name": "href",
@@ -7164,6 +7405,7 @@
7164
7405
  ]
7165
7406
  },
7166
7407
  "name": "Textarea",
7408
+ "native": true,
7167
7409
  "props": [
7168
7410
  {
7169
7411
  "default": "'default'",
@@ -7265,6 +7507,7 @@
7265
7507
  ]
7266
7508
  },
7267
7509
  "name": "ThemeProvider",
7510
+ "native": true,
7268
7511
  "props": [
7269
7512
  {
7270
7513
  "default": "'dark'",
@@ -7301,6 +7544,7 @@
7301
7544
  ]
7302
7545
  },
7303
7546
  "name": "Time",
7547
+ "native": false,
7304
7548
  "props": [
7305
7549
  {
7306
7550
  "description": "The instant, machine-readable: a `Date` (written as its ISO string) or an ISO 8601 string, which is passed through unchanged.",
@@ -7359,6 +7603,7 @@
7359
7603
  ]
7360
7604
  },
7361
7605
  "name": "Timeline",
7606
+ "native": false,
7362
7607
  "parts": {
7363
7608
  "Bar": {
7364
7609
  "name": "Bar",
@@ -7532,7 +7777,7 @@
7532
7777
  "importPath": "@duro-app/ui",
7533
7778
  "kind": "provider",
7534
7779
  "meta": {
7535
- "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).",
7780
+ "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it). On React Native the same API draws through the portal host above every open overlay and the keyboard, as a card with a status-coloured edge, its action and its close in one row; a sideways swipe dismisses, a press holds the countdown, and so does an active screen reader (WCAG 2.2.1).",
7536
7781
  "example": "function Example() {\n const {toast} = useToast()\n return (\n <Button onClick={() => toast({variant: 'success', message: 'Grant revoked', action: {label: 'Undo', onClick: restore}})}>\n Revoke\n </Button>\n )\n}",
7537
7782
  "relatedTo": [
7538
7783
  {
@@ -7559,6 +7804,7 @@
7559
7804
  ]
7560
7805
  },
7561
7806
  "name": "ToastProvider",
7807
+ "native": true,
7562
7808
  "props": [
7563
7809
  {
7564
7810
  "default": "'Dismiss'",
@@ -7609,6 +7855,7 @@
7609
7855
  ]
7610
7856
  },
7611
7857
  "name": "Toggle",
7858
+ "native": true,
7612
7859
  "props": [
7613
7860
  {
7614
7861
  "description": "Controlled pressed state (standalone usage).",
@@ -7696,6 +7943,7 @@
7696
7943
  ]
7697
7944
  },
7698
7945
  "name": "ToggleGroup",
7946
+ "native": false,
7699
7947
  "props": [
7700
7948
  {
7701
7949
  "description": "Controlled value — array of pressed item values.",
@@ -7829,6 +8077,7 @@
7829
8077
  ]
7830
8078
  },
7831
8079
  "name": "Toolbar",
8080
+ "native": false,
7832
8081
  "props": [
7833
8082
  {
7834
8083
  "description": "Names the toolbar (\"Formatting\"). Required: a toolbar is a landmark-like widget.",
@@ -7861,7 +8110,7 @@
7861
8110
  "Trigger"
7862
8111
  ]
7863
8112
  },
7864
- "description": "Hover/focus tooltip that shows supplementary content. Compound component — Root is required (throws without it).",
8113
+ "description": "Hover/focus tooltip that shows supplementary content. Compound component — Root is required (throws without it). On touch (React Native) no bubble is drawn: a string content becomes the accessibility hint of the Duro trigger inside (Button, Toggle, Menu.Trigger, Popover.Trigger), read after its name. Keep tooltip text non-essential, and keep aria-label on icon-only triggers.",
7865
8114
  "example": "<Tooltip.Root content=\"Delete this item permanently\" placement=\"top\">\n <Tooltip.Trigger>\n <Button variant=\"danger\">Delete</Button>\n </Tooltip.Trigger>\n</Tooltip.Root>",
7866
8115
  "whenNotToUse": [
7867
8116
  "Critical information that must always be visible — use Text or Callout",
@@ -7873,6 +8122,7 @@
7873
8122
  ]
7874
8123
  },
7875
8124
  "name": "Tooltip",
8125
+ "native": true,
7876
8126
  "parts": {
7877
8127
  "Root": {
7878
8128
  "name": "Root",
@@ -7927,7 +8177,7 @@
7927
8177
  "Item"
7928
8178
  ]
7929
8179
  },
7930
- "description": "Hierarchy of items with single selection and expandable branches (the WAI-ARIA tree pattern): one tab stop, arrow keys, Home/End, typeahead. An Item with href renders its row as a real link (cmd-click opens a tab) outside the tab order; Enter/Space follows it, through onNavigate(href, event) when given. Compound component — Root is required.",
8180
+ "description": "Hierarchy of items with single selection and expandable branches (the WAI-ARIA tree pattern): one tab stop, arrow keys, Home/End, typeahead. An Item with href renders its row as a real link (cmd-click opens a tab) outside the tab order; Enter/Space follows it, through onNavigate(href, event) when given. Compound component — Root is required. On React Native selection, expansion and aria-level are the same; the keyboard model (arrows, Home/End, typeahead) is web only.",
7931
8181
  "example": "<Tree.Root aria-label=\"Blocs\" defaultExpanded={['energy']} onValueChange={setSelected}>\n <Tree.Item value=\"energy\" label=\"Core business — Energy\">\n <Tree.Item value=\"generation\" label=\"Generation\" meta=\"4 apps\" />\n <Tree.Item value=\"network\" label=\"Network operations\">\n <Tree.Item value=\"scada\" label=\"SCADA supervision\" />\n </Tree.Item>\n </Tree.Item>\n <Tree.Item value=\"finance\" label=\"Finance & steering\" />\n</Tree.Root>",
7932
8182
  "relatedTo": [
7933
8183
  {
@@ -7953,6 +8203,7 @@
7953
8203
  ]
7954
8204
  },
7955
8205
  "name": "Tree",
8206
+ "native": true,
7956
8207
  "parts": {
7957
8208
  "Item": {
7958
8209
  "name": "Item",
@@ -8062,6 +8313,92 @@
8062
8313
  "props": null,
8063
8314
  "sourcePath": "packages/ui/src/components/Tree/Tree.tsx"
8064
8315
  },
8316
+ "VirtualList": {
8317
+ "importPath": "@duro-app/ui",
8318
+ "kind": "component",
8319
+ "meta": {
8320
+ "anatomy": {
8321
+ "optional": [],
8322
+ "required": []
8323
+ },
8324
+ "description": "A windowed list on @tanstack/react-virtual: only the rows in view (plus overscan) are mounted, and rows are measured so they may differ in height. role=\"list\" with listitem rows carrying aria-setsize and aria-posinset. anchor=\"end\" opens at the last item and stays pinned to the bottom while new items arrive and the reader is at the bottom (a transcript, a log). It fills its parent, or stops at maxHeight. Web only: on native, host FlashList or Legend List behind your own .native.tsx leaf.",
8325
+ "example": "<VirtualList\n aria-label=\"Transcript\"\n items={messages}\n getKey={(m) => m.id}\n estimateSize=\"controlLg\"\n anchor=\"end\"\n renderItem={(m) => <Text>{m.text}</Text>}\n/>",
8326
+ "relatedTo": [
8327
+ {
8328
+ "component": "VirtualTable",
8329
+ "kind": "contrast",
8330
+ "relationship": "VirtualTable windows table rows with columns; VirtualList windows any row"
8331
+ },
8332
+ {
8333
+ "component": "List",
8334
+ "kind": "composition",
8335
+ "relationship": "Render a List.Item-like row in renderItem for the list look"
8336
+ }
8337
+ ],
8338
+ "whenNotToUse": [
8339
+ "Tabular data with columns and sorting — use VirtualTable",
8340
+ "A few dozen rows — use List; windowing only adds cost there",
8341
+ "Selectable options in a picker — use Listbox"
8342
+ ],
8343
+ "whenToUse": [
8344
+ "Hundreds to tens of thousands of rows: sessions, a chat transcript, log lines",
8345
+ "A list that grows at the bottom while the reader follows it (anchor=\"end\")"
8346
+ ]
8347
+ },
8348
+ "name": "VirtualList",
8349
+ "native": false,
8350
+ "props": [
8351
+ {
8352
+ "name": "items",
8353
+ "required": true,
8354
+ "type": "readonly T[]"
8355
+ },
8356
+ {
8357
+ "description": "Each row's starting height estimate, a size token; rows are then measured.",
8358
+ "name": "estimateSize",
8359
+ "required": true,
8360
+ "type": "SizeToken"
8361
+ },
8362
+ {
8363
+ "name": "renderItem",
8364
+ "required": true,
8365
+ "type": "(item: T, index: number) => ReactNode"
8366
+ },
8367
+ {
8368
+ "description": "A stable key per item (an id), so rows keep their measurement as items change.",
8369
+ "name": "getKey",
8370
+ "required": true,
8371
+ "type": "(item: T, index: number) => string | number"
8372
+ },
8373
+ {
8374
+ "default": "5",
8375
+ "description": "Rows rendered beyond each edge of the view. Default 5",
8376
+ "name": "overscan",
8377
+ "required": false,
8378
+ "type": "number"
8379
+ },
8380
+ {
8381
+ "default": "'start'",
8382
+ "description": "`end` starts at the last item and stays pinned to the bottom while new items arrive, as long as the reader is at the bottom (a transcript, a log). Default `start`.",
8383
+ "name": "anchor",
8384
+ "required": false,
8385
+ "type": "'start' | 'end'"
8386
+ },
8387
+ {
8388
+ "description": "Cap the list's height; without it, it fills its parent.",
8389
+ "name": "maxHeight",
8390
+ "required": false,
8391
+ "type": "Length"
8392
+ },
8393
+ {
8394
+ "description": "The list's accessible name.",
8395
+ "name": "aria-label",
8396
+ "required": true,
8397
+ "type": "string"
8398
+ }
8399
+ ],
8400
+ "sourcePath": "packages/ui/src/components/VirtualList/VirtualList.tsx"
8401
+ },
8065
8402
  "VirtualTable": {
8066
8403
  "importPath": "@duro-app/ui/table",
8067
8404
  "kind": "component",
@@ -8222,6 +8559,7 @@
8222
8559
  ]
8223
8560
  },
8224
8561
  "name": "VisuallyHidden",
8562
+ "native": false,
8225
8563
  "props": [
8226
8564
  {
8227
8565
  "name": "children",
@@ -8253,6 +8591,7 @@
8253
8591
  ]
8254
8592
  },
8255
8593
  "name": "relative",
8594
+ "native": false,
8256
8595
  "props": [],
8257
8596
  "sourcePath": "packages/ui/src/components/Time/relative.ts"
8258
8597
  },
@@ -8294,6 +8633,7 @@
8294
8633
  ]
8295
8634
  },
8296
8635
  "name": "useColorMode",
8636
+ "native": false,
8297
8637
  "props": [],
8298
8638
  "sourcePath": "packages/ui/src/components/ColorMode/ColorMode.tsx"
8299
8639
  },
@@ -8302,7 +8642,15 @@
8302
8642
  "kind": "hook",
8303
8643
  "meta": null,
8304
8644
  "name": "useContainerBelow",
8305
- "props": [],
8645
+ "native": false,
8646
+ "props": [
8647
+ {
8648
+ "description": "The current value of the ref.",
8649
+ "name": "current",
8650
+ "required": true,
8651
+ "type": "T"
8652
+ }
8653
+ ],
8306
8654
  "sourcePath": "packages/ui/src/hooks/useContainerBelow.ts"
8307
8655
  },
8308
8656
  "useContainerQuery": {
@@ -8310,6 +8658,7 @@
8310
8658
  "kind": "hook",
8311
8659
  "meta": null,
8312
8660
  "name": "useContainerQuery",
8661
+ "native": false,
8313
8662
  "props": [
8314
8663
  {
8315
8664
  "name": "compactBelow",
@@ -8400,6 +8749,7 @@
8400
8749
  ]
8401
8750
  },
8402
8751
  "name": "useDragDrop",
8752
+ "native": false,
8403
8753
  "props": [],
8404
8754
  "sourcePath": "packages/ui/src/components/DragDrop/DragDrop.tsx"
8405
8755
  },
@@ -8408,6 +8758,7 @@
8408
8758
  "kind": "hook",
8409
8759
  "meta": null,
8410
8760
  "name": "useInModal",
8761
+ "native": false,
8411
8762
  "props": [],
8412
8763
  "sourcePath": "packages/ui/src/shared/ModalContext.ts"
8413
8764
  },
@@ -8426,6 +8777,7 @@
8426
8777
  ]
8427
8778
  },
8428
8779
  "name": "usePortalMount",
8780
+ "native": true,
8429
8781
  "props": [],
8430
8782
  "sourcePath": "packages/ui/src/components/ThemeProvider/ThemeProvider.tsx"
8431
8783
  },
@@ -8433,7 +8785,7 @@
8433
8785
  "importPath": "@duro-app/ui",
8434
8786
  "kind": "hook",
8435
8787
  "meta": {
8436
- "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).",
8788
+ "description": "Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it). On React Native the same API draws through the portal host above every open overlay and the keyboard, as a card with a status-coloured edge, its action and its close in one row; a sideways swipe dismisses, a press holds the countdown, and so does an active screen reader (WCAG 2.2.1).",
8437
8789
  "example": "function Example() {\n const {toast} = useToast()\n return (\n <Button onClick={() => toast({variant: 'success', message: 'Grant revoked', action: {label: 'Undo', onClick: restore}})}>\n Revoke\n </Button>\n )\n}",
8438
8790
  "relatedTo": [
8439
8791
  {
@@ -8460,6 +8812,7 @@
8460
8812
  ]
8461
8813
  },
8462
8814
  "name": "useToast",
8815
+ "native": true,
8463
8816
  "props": [],
8464
8817
  "sourcePath": "packages/ui/src/components/Toast/ToastProvider.tsx"
8465
8818
  }
@@ -8520,7 +8873,8 @@
8520
8873
  "label": "Input / action glyphs",
8521
8874
  "names": [
8522
8875
  "search",
8523
- "mic"
8876
+ "mic",
8877
+ "x"
8524
8878
  ]
8525
8879
  },
8526
8880
  {
@@ -8593,6 +8947,7 @@
8593
8947
  "plug",
8594
8948
  "search",
8595
8949
  "mic",
8950
+ "x",
8596
8951
  "sun",
8597
8952
  "moon",
8598
8953
  "monitor",
@@ -8850,7 +9205,7 @@
8850
9205
  "split-pane": {
8851
9206
  "export": "SplitPaneRecipe",
8852
9207
  "meta": {
8853
- "description": "List/detail split: a selectable List beside a Panel showing the selection, on Grid layout=\"split\" (list ≥ 240px, one column when the board is narrower than sm).",
9208
+ "description": "List/detail split: a selectable List beside a Panel showing the selection, in a SplitPane (the list starts at sidebarW, resizes up to panelLg with the handle, and the panes stack when the split is narrower than sm).",
8854
9209
  "example": "<SplitPaneRecipe />",
8855
9210
  "whenNotToUse": [
8856
9211
  "The detail is a short-lived inspection — use DetailPanel over the list instead",
@@ -8863,18 +9218,18 @@
8863
9218
  },
8864
9219
  "name": "split-pane",
8865
9220
  "peerDeps": [],
8866
- "source": "import {useState} from 'react'\nimport {Grid, List, Panel, Stack, Inline, Heading, Text, Badge, Button, EmptyState} from '@duro-app/ui'\n\nconst ITEMS = [\n {id: 'grafana', name: 'Grafana', owner: 'platform', status: 'active'},\n {id: 'vault', name: 'Vault', owner: 'security', status: 'active'},\n {id: 'legacy-crm', name: 'Legacy CRM', owner: 'sales', status: 'retiring'},\n] as const\n\nexport function SplitPaneRecipe() {\n const [selected, setSelected] = useState<string | null>(ITEMS[0].id)\n const item = ITEMS.find((candidate) => candidate.id === selected)\n\n return (\n <Grid layout=\"split\" gap=\"lg\">\n <List.Root selectionMode=\"single\" aria-label=\"Applications\">\n {ITEMS.map((candidate) => (\n <List.Item\n key={candidate.id}\n selected={candidate.id === selected}\n onClick={() => setSelected(candidate.id)}\n >\n <List.Content>\n <List.Text>{candidate.name}</List.Text>\n <List.Description>Owned by {candidate.owner}</List.Description>\n </List.Content>\n <List.Actions>\n <Badge variant={candidate.status === 'active' ? 'success' : 'warning'}>\n {candidate.status}\n </Badge>\n </List.Actions>\n </List.Item>\n ))}\n <List.Empty>No applications.</List.Empty>\n </List.Root>\n\n <Panel.Root bordered>\n {item ? (\n <>\n <Panel.Header>\n <Heading level={2}>{item.name}</Heading>\n <Inline gap=\"sm\">\n <Button variant=\"secondary\" size=\"small\">\n Edit\n </Button>\n <Button variant=\"danger\" size=\"small\">\n Retire\n </Button>\n </Inline>\n </Panel.Header>\n <Panel.Body>\n <Stack gap=\"md\">\n <Text>\n Owner: <Text weight=\"medium\">{item.owner}</Text>\n </Text>\n <Text color=\"muted\">\n Everything about the selection lives here: fields, history, related records.\n </Text>\n </Stack>\n </Panel.Body>\n </>\n ) : (\n <Panel.Body>\n <EmptyState message=\"Select an application to see its details.\" />\n </Panel.Body>\n )}\n </Panel.Root>\n </Grid>\n )\n}\n",
9221
+ "source": "import {useState} from 'react'\nimport {SplitPane, List, Panel, Stack, Inline, Heading, Text, Badge, Button, EmptyState} from '@duro-app/ui'\n\nconst ITEMS = [\n {id: 'grafana', name: 'Grafana', owner: 'platform', status: 'active'},\n {id: 'vault', name: 'Vault', owner: 'security', status: 'active'},\n {id: 'legacy-crm', name: 'Legacy CRM', owner: 'sales', status: 'retiring'},\n] as const\n\nexport function SplitPaneRecipe() {\n const [selected, setSelected] = useState<string | null>(ITEMS[0].id)\n const item = ITEMS.find((candidate) => candidate.id === selected)\n\n return (\n <SplitPane.Root aria-label=\"Resize the application list\" minSize=\"sidebarW\" maxSize=\"panelLg\">\n <SplitPane.Pane>\n <List.Root selectionMode=\"single\" aria-label=\"Applications\">\n {ITEMS.map((candidate) => (\n <List.Item\n key={candidate.id}\n selected={candidate.id === selected}\n onClick={() => setSelected(candidate.id)}\n >\n <List.Content>\n <List.Text>{candidate.name}</List.Text>\n <List.Description>Owned by {candidate.owner}</List.Description>\n </List.Content>\n <List.Actions>\n <Badge variant={candidate.status === 'active' ? 'success' : 'warning'}>\n {candidate.status}\n </Badge>\n </List.Actions>\n </List.Item>\n ))}\n <List.Empty>No applications.</List.Empty>\n </List.Root>\n </SplitPane.Pane>\n <SplitPane.Handle />\n <SplitPane.Pane>\n <Panel.Root bordered>\n {item ? (\n <>\n <Panel.Header>\n <Heading level={2}>{item.name}</Heading>\n <Inline gap=\"sm\">\n <Button variant=\"secondary\" size=\"small\">\n Edit\n </Button>\n <Button variant=\"danger\" size=\"small\">\n Retire\n </Button>\n </Inline>\n </Panel.Header>\n <Panel.Body>\n <Stack gap=\"md\">\n <Text>\n Owner: <Text weight=\"medium\">{item.owner}</Text>\n </Text>\n <Text color=\"muted\">\n Everything about the selection lives here: fields, history, related records.\n </Text>\n </Stack>\n </Panel.Body>\n </>\n ) : (\n <Panel.Body>\n <EmptyState message=\"Select an application to see its details.\" />\n </Panel.Body>\n )}\n </Panel.Root>\n </SplitPane.Pane>\n </SplitPane.Root>\n )\n}\n",
8867
9222
  "sourcePath": "docs/ai/recipes/split-pane.recipe.tsx",
8868
9223
  "title": "Split Pane",
8869
9224
  "usesComponents": [
8870
9225
  "Badge",
8871
9226
  "Button",
8872
9227
  "EmptyState",
8873
- "Grid",
8874
9228
  "Heading",
8875
9229
  "Inline",
8876
9230
  "List",
8877
9231
  "Panel",
9232
+ "SplitPane",
8878
9233
  "Stack",
8879
9234
  "Text"
8880
9235
  ]
@@ -9976,6 +10331,14 @@
9976
10331
  "key": "barH",
9977
10332
  "value": "4px"
9978
10333
  },
10334
+ {
10335
+ "key": "sheetGrabberW",
10336
+ "value": "40px"
10337
+ },
10338
+ {
10339
+ "key": "sheetGrabberH",
10340
+ "value": "4px"
10341
+ },
9979
10342
  {
9980
10343
  "key": "readoutW",
9981
10344
  "value": "40px"
@@ -10165,7 +10528,7 @@
10165
10528
  "importPath": "@duro-app/tokens/tokens/typography.css"
10166
10529
  }
10167
10530
  },
10168
- "mockupCss": "/*\n * GENERATED by scripts/generate-mockup-css.mjs — do not edit.\n *\n * The design tokens as resolved values, for HTML that loads no stylesheet\n * (mockup artboards, static previews). Same names as vars.css; where vars.css\n * follows the live theme, this file carries every theme and switches on\n * <html data-theme=\"light|high-contrast\">.\n *\n * Inside the component library and the apps, never use this: go through\n * css.create() and the token imports, or vars.css from plain CSS.\n */\n:root {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #6aaffc;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #0f0f0f;\n --duro-color-bg-card: #1a1a1a;\n --duro-color-bg-card-hover: #242424;\n --duro-color-border: #333333;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #f5f5f5;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.1);\n --duro-color-error-border: rgba(248, 113, 113, 0.3);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #c084fc;\n --duro-color-highlight-bg: rgba(192, 132, 252, 0.1);\n --duro-color-highlight-border: rgba(192, 132, 252, 0.3);\n --duro-color-highlight-text: #d8b4fe;\n --duro-color-info: #6aaffc;\n --duro-color-info-bg: rgba(106, 175, 252, 0.1);\n --duro-color-info-border: rgba(106, 175, 252, 0.3);\n --duro-color-info-text: #93c5fd;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #1a1a1a;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #22c55e;\n --duro-color-success-bg: rgba(34, 197, 94, 0.1);\n --duro-color-success-border: rgba(34, 197, 94, 0.3);\n --duro-color-success-text: #86efac;\n --duro-color-text: #e5e5e5;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fbbf24;\n --duro-color-warning-bg: rgba(251, 191, 36, 0.1);\n --duro-color-warning-border: rgba(251, 191, 36, 0.3);\n --duro-color-warning-text: #fde68a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #6aaffc;\n --duro-shadow-drop-ready: inset 0 0 0 1px #333333;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);\n /* spacing */\n --duro-spacing-lg: 24px;\n --duro-spacing-md: 16px;\n --duro-spacing-ms: 12px;\n --duro-spacing-sm: 8px;\n --duro-spacing-xl: 32px;\n --duro-spacing-xs: 4px;\n --duro-spacing-xxl: 48px;\n --duro-spacing-xxxl: 64px;\n /* microSpacing */\n --duro-micro-spacing-px1: 1px;\n --duro-micro-spacing-px2: 2px;\n --duro-micro-spacing-px3: 3px;\n --duro-micro-spacing-px5: 5px;\n --duro-micro-spacing-px6: 6px;\n /* radii */\n --duro-radius-full: 9999px;\n --duro-radius-lg: 16px;\n --duro-radius-md: 12px;\n --duro-radius-px6: 6px;\n --duro-radius-sm: 8px;\n --duro-radius-xs: 4px;\n /* sizes */\n --duro-size-app-bar-h: 61px;\n --duro-size-aside-w: 320px;\n --duro-size-bar-h: 4px;\n --duro-size-canvas-min-h: 480px;\n --duro-size-check-mark-h: 9px;\n --duro-size-check-mark-w: 5px;\n --duro-size-chip: 24px;\n --duro-size-color-area-h: 150px;\n --duro-size-color-picker-w: 196px;\n --duro-size-color-preview-h: 22px;\n --duro-size-color-swatch: 20px;\n --duro-size-color-track-h: 12px;\n --duro-size-control-lg: 40px;\n --duro-size-control-md: 39px;\n --duro-size-control-sm: 28px;\n --duro-size-day-header-h: 46px;\n --duro-size-device-bar-w: 96px;\n --duro-size-dialog-lg: 680px;\n --duro-size-dialog-md: 520px;\n --duro-size-dialog-sm: 400px;\n --duro-size-divider: 1px;\n --duro-size-drag-thumb-h: 99px;\n --duro-size-drag-thumb-w: 132px;\n --duro-size-drop-zone-min-h: 128px;\n --duro-size-edge-fade: 32px;\n --duro-size-editor-min-h: 160px;\n --duro-size-embed-w: 550px;\n --duro-size-field-min-w: 160px;\n --duro-size-field-min-w-sm: 80px;\n --duro-size-glyph-clamp-max: 160px;\n --duro-size-glyph-md: 16px;\n --duro-size-glyph-sm: 12px;\n --duro-size-glyph-xs: 10px;\n --duro-size-grid-col-md: 280px;\n --duro-size-grid-col-sm: 240px;\n --duro-size-grid-col-xs: 200px;\n --duro-size-handle: 16px;\n --duro-size-icon-button: 32px;\n --duro-size-icon-button-sm: 28px;\n --duro-size-icon-lg: 24px;\n --duro-size-icon-md: 18px;\n --duro-size-icon-sm: 16px;\n --duro-size-icon-xl: 36px;\n --duro-size-icon-xxl: 48px;\n --duro-size-indicator: 18px;\n --duro-size-indicator-dot: 8px;\n --duro-size-label-min-w: 120px;\n --duro-size-list-max-h: 280px;\n --duro-size-list-max-h-sm: 200px;\n --duro-size-media-tile-min-w: 108px;\n --duro-size-meter-h: 6px;\n --duro-size-nav-marker-h: 18px;\n --duro-size-nav-marker-w: 3px;\n --duro-size-page-lg: 1200px;\n --duro-size-page-md: 800px;\n --duro-size-page-sm: 600px;\n --duro-size-page-xl: 1440px;\n --duro-size-page-xs: 480px;\n --duro-size-palette-min-w: 96px;\n --duro-size-panel-lg: 640px;\n --duro-size-panel-md: 480px;\n --duro-size-panel-sm: 360px;\n --duro-size-placeholder-min-h: 80px;\n --duro-size-popover-w: 320px;\n --duro-size-popover-w-sm: 240px;\n --duro-size-popup-max-w: 280px;\n --duro-size-popup-min-w: 160px;\n --duro-size-preview-max-h: 320px;\n --duro-size-readout-w: 40px;\n --duro-size-scrollbar: 8px;\n --duro-size-sidebar-w: 240px;\n --duro-size-skeleton-chip-w: 96px;\n --duro-size-slider-w: 96px;\n --duro-size-spinner-lg: 40px;\n --duro-size-spinner-md: 24px;\n --duro-size-spinner-sm: 16px;\n --duro-size-swatch-h: 34px;\n --duro-size-swatch-w: 44px;\n --duro-size-switch-thumb: 16px;\n --duro-size-switch-track-h: 20px;\n --duro-size-switch-track-w: 36px;\n --duro-size-tab-indicator: 2px;\n --duro-size-thumb-sm: 40px;\n --duro-size-time-gutter-w: 64px;\n --duro-size-timeline-label-w: 180px;\n --duro-size-time-readout-w: 74px;\n --duro-size-toast-max-w: 440px;\n --duro-size-toolbar-h: 36px;\n --duro-size-touch-target: 44px;\n /* borders */\n --duro-border-accent: 3px;\n --duro-border-focus-offset: 2px;\n --duro-border-focus-offset-sm: 1px;\n --duro-border-focus-ring: 2px;\n --duro-border-hairline: 1px;\n --duro-border-strong: 2px;\n /* layers */\n --duro-layer-floating: 50;\n --duro-layer-floating-raised: 60;\n --duro-layer-modal: 1001;\n --duro-layer-modal-raised: 1002;\n --duro-layer-overlay: 1000;\n --duro-layer-popover: 1040;\n --duro-layer-popover-raised: 1041;\n --duro-layer-popup: 1050;\n --duro-layer-popup-backdrop: 1049;\n --duro-layer-portal: 1100;\n --duro-layer-raised: 1;\n --duro-layer-toast: 1060;\n /* effects */\n --duro-effect-overlay-blur: blur(2px);\n --duro-effect-surface-blur: blur(6px);\n /* layoutSpacing */\n --duro-layout-spacing-container-lg: 32px;\n --duro-layout-spacing-container-md: 24px;\n --duro-layout-spacing-container-sm: 16px;\n --duro-layout-spacing-inline-lg: 24px;\n --duro-layout-spacing-inline-md: 16px;\n --duro-layout-spacing-inline-sm: 8px;\n --duro-layout-spacing-inline-xs: 4px;\n --duro-layout-spacing-stack-lg: 24px;\n --duro-layout-spacing-stack-md: 16px;\n --duro-layout-spacing-stack-sm: 8px;\n --duro-layout-spacing-stack-xl: 48px;\n --duro-layout-spacing-stack-xs: 4px;\n /* typography */\n --duro-typography-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n --duro-typography-font-family-mono: \"SF Mono\", \"Fira Code\", \"Fira Mono\", \"Roboto Mono\", \"Courier New\", monospace;\n --duro-typography-font-size-heading: 1.5rem;\n --duro-typography-font-size-lg: 1.125rem;\n --duro-typography-font-size-md: 1rem;\n --duro-typography-font-size-sm: 0.875rem;\n --duro-typography-font-size-xl: 1.25rem;\n --duro-typography-font-size-xs: 0.75rem;\n --duro-typography-font-weight-bold: 700;\n --duro-typography-font-weight-medium: 500;\n --duro-typography-font-weight-normal: 400;\n --duro-typography-font-weight-semibold: 600;\n --duro-typography-line-height: 1.5;\n /* typeScale */\n --duro-type-scale-display-lg: clamp(3.5rem, 2rem + 3.5vw, 4.5rem);\n --duro-type-scale-display-md: clamp(2.75rem, 1.75rem + 2.5vw, 3.75rem);\n --duro-type-scale-display-sm: clamp(2.25rem, 1.5rem + 2vw, 3rem);\n --duro-type-scale-font-size1: 0.75rem;\n --duro-type-scale-font-size2: 0.8125rem;\n --duro-type-scale-font-size3: 0.875rem;\n --duro-type-scale-font-size4: 1rem;\n --duro-type-scale-font-size5: 1.125rem;\n --duro-type-scale-font-size6: 1.25rem;\n --duro-type-scale-font-size7: 1.5rem;\n --duro-type-scale-font-size8: 1.875rem;\n --duro-type-scale-font-size9: 2.25rem;\n --duro-type-scale-letter-spacing-normal: 0;\n --duro-type-scale-letter-spacing-tight: -0.02em;\n --duro-type-scale-letter-spacing-wide: 0.04em;\n --duro-type-scale-line-height1: 1rem;\n --duro-type-scale-line-height2: 1.25rem;\n --duro-type-scale-line-height3: 1.25rem;\n --duro-type-scale-line-height4: 1.5rem;\n --duro-type-scale-line-height5: 1.5rem;\n --duro-type-scale-line-height6: 1.75rem;\n --duro-type-scale-line-height7: 2rem;\n --duro-type-scale-line-height8: 2.25rem;\n --duro-type-scale-line-height9: 2.75rem;\n /* duration */\n --duro-duration-base: 200ms;\n --duro-duration-brisk: 160ms;\n --duro-duration-fast: 150ms;\n --duro-duration-instant: 0ms;\n --duro-duration-minimal: 1ms;\n --duro-duration-quick: 120ms;\n --duro-duration-slow: 280ms;\n /* easing */\n --duro-easing-ease-in: cubic-bezier(0.72, 0, 0.68, 0.28);\n --duro-easing-ease-out: cubic-bezier(0.32, 0.72, 0, 1);\n --duro-easing-linear: linear;\n --duro-easing-standard: ease;\n /* breakpoints */\n --duro-breakpoint-lg: 1024px;\n --duro-breakpoint-md: 768px;\n --duro-breakpoint-sm: 640px;\n --duro-breakpoint-xl: 1280px;\n --duro-breakpoint-xs: 480px;\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n /* colors */\n --duro-color-accent: #1e40af;\n --duro-color-accent-contrast: #ffffff;\n --duro-color-accent-hover: #1a3799;\n --duro-color-bg: #ffffff;\n --duro-color-bg-card: #f5f5f5;\n --duro-color-bg-card-hover: #ebebeb;\n --duro-color-border: #d4d4d4;\n --duro-color-contrast-border: rgba(255, 255, 255, 0.33);\n --duro-color-contrast-surface: #1a1a1a;\n --duro-color-error: #991b1b;\n --duro-color-error-bg: rgba(153, 27, 27, 0.08);\n --duro-color-error-border: rgba(153, 27, 27, 0.3);\n --duro-color-error-contrast: #ffffff;\n --duro-color-error-hover: #7f1d1d;\n --duro-color-error-text: #7f1d1d;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #6b21a8;\n --duro-color-highlight-bg: rgba(107, 33, 168, 0.08);\n --duro-color-highlight-border: rgba(107, 33, 168, 0.3);\n --duro-color-highlight-text: #581c87;\n --duro-color-info: #1e40af;\n --duro-color-info-bg: rgba(30, 64, 175, 0.08);\n --duro-color-info-border: rgba(30, 64, 175, 0.3);\n --duro-color-info-text: #1e40af;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #e5e5e5;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #166534;\n --duro-color-success-bg: rgba(22, 101, 52, 0.08);\n --duro-color-success-border: rgba(22, 101, 52, 0.3);\n --duro-color-success-text: #14532d;\n --duro-color-text: #1a1a1a;\n --duro-color-text-muted: #4a4a4a;\n --duro-color-warning: #92400e;\n --duro-color-warning-bg: rgba(146, 64, 14, 0.08);\n --duro-color-warning-border: rgba(146, 64, 14, 0.3);\n --duro-color-warning-text: #78350f;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #1e40af;\n --duro-shadow-drop-ready: inset 0 0 0 1px #d4d4d4;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08);\n}\n\n:root[data-theme=\"high-contrast\"] {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #60a5fa;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #000000;\n --duro-color-bg-card: #111111;\n --duro-color-bg-card-hover: #1a1a1a;\n --duro-color-border: #555555;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #ffffff;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.15);\n --duro-color-error-border: rgba(248, 113, 113, 0.5);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #d8b4fe;\n --duro-color-highlight-bg: rgba(216, 180, 254, 0.15);\n --duro-color-highlight-border: rgba(216, 180, 254, 0.5);\n --duro-color-highlight-text: #e9d5ff;\n --duro-color-info: #60a5fa;\n --duro-color-info-bg: rgba(96, 165, 250, 0.15);\n --duro-color-info-border: rgba(96, 165, 250, 0.5);\n --duro-color-info-text: #bfdbfe;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #000000;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #4ade80;\n --duro-color-success-bg: rgba(74, 222, 128, 0.15);\n --duro-color-success-border: rgba(74, 222, 128, 0.5);\n --duro-color-success-text: #86efac;\n --duro-color-text: #ffffff;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fcd34d;\n --duro-color-warning-bg: rgba(252, 211, 77, 0.15);\n --duro-color-warning-border: rgba(252, 211, 77, 0.5);\n --duro-color-warning-text: #fef08a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #60a5fa;\n --duro-shadow-drop-ready: inset 0 0 0 1px #555555;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.8);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.7);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.6);\n}\n"
10531
+ "mockupCss": "/*\n * GENERATED by scripts/generate-mockup-css.mjs — do not edit.\n *\n * The design tokens as resolved values, for HTML that loads no stylesheet\n * (mockup artboards, static previews). Same names as vars.css; where vars.css\n * follows the live theme, this file carries every theme and switches on\n * <html data-theme=\"light|high-contrast\">.\n *\n * Inside the component library and the apps, never use this: go through\n * css.create() and the token imports, or vars.css from plain CSS.\n */\n:root {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #6aaffc;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #0f0f0f;\n --duro-color-bg-card: #1a1a1a;\n --duro-color-bg-card-hover: #242424;\n --duro-color-border: #333333;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #f5f5f5;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.1);\n --duro-color-error-border: rgba(248, 113, 113, 0.3);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #c084fc;\n --duro-color-highlight-bg: rgba(192, 132, 252, 0.1);\n --duro-color-highlight-border: rgba(192, 132, 252, 0.3);\n --duro-color-highlight-text: #d8b4fe;\n --duro-color-info: #6aaffc;\n --duro-color-info-bg: rgba(106, 175, 252, 0.1);\n --duro-color-info-border: rgba(106, 175, 252, 0.3);\n --duro-color-info-text: #93c5fd;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #1a1a1a;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #22c55e;\n --duro-color-success-bg: rgba(34, 197, 94, 0.1);\n --duro-color-success-border: rgba(34, 197, 94, 0.3);\n --duro-color-success-text: #86efac;\n --duro-color-text: #e5e5e5;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fbbf24;\n --duro-color-warning-bg: rgba(251, 191, 36, 0.1);\n --duro-color-warning-border: rgba(251, 191, 36, 0.3);\n --duro-color-warning-text: #fde68a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #6aaffc;\n --duro-shadow-drop-ready: inset 0 0 0 1px #333333;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);\n /* spacing */\n --duro-spacing-lg: 24px;\n --duro-spacing-md: 16px;\n --duro-spacing-ms: 12px;\n --duro-spacing-sm: 8px;\n --duro-spacing-xl: 32px;\n --duro-spacing-xs: 4px;\n --duro-spacing-xxl: 48px;\n --duro-spacing-xxxl: 64px;\n /* microSpacing */\n --duro-micro-spacing-px1: 1px;\n --duro-micro-spacing-px2: 2px;\n --duro-micro-spacing-px3: 3px;\n --duro-micro-spacing-px5: 5px;\n --duro-micro-spacing-px6: 6px;\n /* radii */\n --duro-radius-full: 9999px;\n --duro-radius-lg: 16px;\n --duro-radius-md: 12px;\n --duro-radius-px6: 6px;\n --duro-radius-sm: 8px;\n --duro-radius-xs: 4px;\n /* sizes */\n --duro-size-app-bar-h: 61px;\n --duro-size-aside-w: 320px;\n --duro-size-bar-h: 4px;\n --duro-size-canvas-min-h: 480px;\n --duro-size-check-mark-h: 9px;\n --duro-size-check-mark-w: 5px;\n --duro-size-chip: 24px;\n --duro-size-color-area-h: 150px;\n --duro-size-color-picker-w: 196px;\n --duro-size-color-preview-h: 22px;\n --duro-size-color-swatch: 20px;\n --duro-size-color-track-h: 12px;\n --duro-size-control-lg: 40px;\n --duro-size-control-md: 39px;\n --duro-size-control-sm: 28px;\n --duro-size-day-header-h: 46px;\n --duro-size-device-bar-w: 96px;\n --duro-size-dialog-lg: 680px;\n --duro-size-dialog-md: 520px;\n --duro-size-dialog-sm: 400px;\n --duro-size-divider: 1px;\n --duro-size-drag-thumb-h: 99px;\n --duro-size-drag-thumb-w: 132px;\n --duro-size-drop-zone-min-h: 128px;\n --duro-size-edge-fade: 32px;\n --duro-size-editor-min-h: 160px;\n --duro-size-embed-w: 550px;\n --duro-size-field-min-w: 160px;\n --duro-size-field-min-w-sm: 80px;\n --duro-size-glyph-clamp-max: 160px;\n --duro-size-glyph-md: 16px;\n --duro-size-glyph-sm: 12px;\n --duro-size-glyph-xs: 10px;\n --duro-size-grid-col-md: 280px;\n --duro-size-grid-col-sm: 240px;\n --duro-size-grid-col-xs: 200px;\n --duro-size-handle: 16px;\n --duro-size-icon-button: 32px;\n --duro-size-icon-button-sm: 28px;\n --duro-size-icon-lg: 24px;\n --duro-size-icon-md: 18px;\n --duro-size-icon-sm: 16px;\n --duro-size-icon-xl: 36px;\n --duro-size-icon-xxl: 48px;\n --duro-size-indicator: 18px;\n --duro-size-indicator-dot: 8px;\n --duro-size-label-min-w: 120px;\n --duro-size-list-max-h: 280px;\n --duro-size-list-max-h-sm: 200px;\n --duro-size-media-tile-min-w: 108px;\n --duro-size-meter-h: 6px;\n --duro-size-nav-marker-h: 18px;\n --duro-size-nav-marker-w: 3px;\n --duro-size-page-lg: 1200px;\n --duro-size-page-md: 800px;\n --duro-size-page-sm: 600px;\n --duro-size-page-xl: 1440px;\n --duro-size-page-xs: 480px;\n --duro-size-palette-min-w: 96px;\n --duro-size-panel-lg: 640px;\n --duro-size-panel-md: 480px;\n --duro-size-panel-sm: 360px;\n --duro-size-placeholder-min-h: 80px;\n --duro-size-popover-w: 320px;\n --duro-size-popover-w-sm: 240px;\n --duro-size-popup-max-w: 280px;\n --duro-size-popup-min-w: 160px;\n --duro-size-preview-max-h: 320px;\n --duro-size-readout-w: 40px;\n --duro-size-scrollbar: 8px;\n --duro-size-sheet-grabber-h: 4px;\n --duro-size-sheet-grabber-w: 40px;\n --duro-size-sidebar-w: 240px;\n --duro-size-skeleton-chip-w: 96px;\n --duro-size-slider-w: 96px;\n --duro-size-spinner-lg: 40px;\n --duro-size-spinner-md: 24px;\n --duro-size-spinner-sm: 16px;\n --duro-size-swatch-h: 34px;\n --duro-size-swatch-w: 44px;\n --duro-size-switch-thumb: 16px;\n --duro-size-switch-track-h: 20px;\n --duro-size-switch-track-w: 36px;\n --duro-size-tab-indicator: 2px;\n --duro-size-thumb-sm: 40px;\n --duro-size-time-gutter-w: 64px;\n --duro-size-timeline-label-w: 180px;\n --duro-size-time-readout-w: 74px;\n --duro-size-toast-max-w: 440px;\n --duro-size-toolbar-h: 36px;\n --duro-size-touch-target: 44px;\n /* borders */\n --duro-border-accent: 3px;\n --duro-border-focus-offset: 2px;\n --duro-border-focus-offset-sm: 1px;\n --duro-border-focus-ring: 2px;\n --duro-border-hairline: 1px;\n --duro-border-strong: 2px;\n /* layers */\n --duro-layer-floating: 50;\n --duro-layer-floating-raised: 60;\n --duro-layer-modal: 1001;\n --duro-layer-modal-raised: 1002;\n --duro-layer-overlay: 1000;\n --duro-layer-popover: 1040;\n --duro-layer-popover-raised: 1041;\n --duro-layer-popup: 1050;\n --duro-layer-popup-backdrop: 1049;\n --duro-layer-portal: 1100;\n --duro-layer-raised: 1;\n --duro-layer-toast: 1060;\n /* effects */\n --duro-effect-overlay-blur: blur(2px);\n --duro-effect-surface-blur: blur(6px);\n /* layoutSpacing */\n --duro-layout-spacing-container-lg: 32px;\n --duro-layout-spacing-container-md: 24px;\n --duro-layout-spacing-container-sm: 16px;\n --duro-layout-spacing-inline-lg: 24px;\n --duro-layout-spacing-inline-md: 16px;\n --duro-layout-spacing-inline-sm: 8px;\n --duro-layout-spacing-inline-xs: 4px;\n --duro-layout-spacing-stack-lg: 24px;\n --duro-layout-spacing-stack-md: 16px;\n --duro-layout-spacing-stack-sm: 8px;\n --duro-layout-spacing-stack-xl: 48px;\n --duro-layout-spacing-stack-xs: 4px;\n /* typography */\n --duro-typography-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n --duro-typography-font-family-mono: \"SF Mono\", \"Fira Code\", \"Fira Mono\", \"Roboto Mono\", \"Courier New\", monospace;\n --duro-typography-font-size-heading: 1.5rem;\n --duro-typography-font-size-lg: 1.125rem;\n --duro-typography-font-size-md: 1rem;\n --duro-typography-font-size-sm: 0.875rem;\n --duro-typography-font-size-xl: 1.25rem;\n --duro-typography-font-size-xs: 0.75rem;\n --duro-typography-font-weight-bold: 700;\n --duro-typography-font-weight-medium: 500;\n --duro-typography-font-weight-normal: 400;\n --duro-typography-font-weight-semibold: 600;\n --duro-typography-line-height: 1.5;\n /* typeScale */\n --duro-type-scale-display-lg: clamp(3.5rem, 2rem + 3.5vw, 4.5rem);\n --duro-type-scale-display-md: clamp(2.75rem, 1.75rem + 2.5vw, 3.75rem);\n --duro-type-scale-display-sm: clamp(2.25rem, 1.5rem + 2vw, 3rem);\n --duro-type-scale-font-size1: 0.75rem;\n --duro-type-scale-font-size2: 0.8125rem;\n --duro-type-scale-font-size3: 0.875rem;\n --duro-type-scale-font-size4: 1rem;\n --duro-type-scale-font-size5: 1.125rem;\n --duro-type-scale-font-size6: 1.25rem;\n --duro-type-scale-font-size7: 1.5rem;\n --duro-type-scale-font-size8: 1.875rem;\n --duro-type-scale-font-size9: 2.25rem;\n --duro-type-scale-letter-spacing-normal: 0;\n --duro-type-scale-letter-spacing-tight: -0.02em;\n --duro-type-scale-letter-spacing-wide: 0.04em;\n --duro-type-scale-line-height1: 1rem;\n --duro-type-scale-line-height2: 1.25rem;\n --duro-type-scale-line-height3: 1.25rem;\n --duro-type-scale-line-height4: 1.5rem;\n --duro-type-scale-line-height5: 1.5rem;\n --duro-type-scale-line-height6: 1.75rem;\n --duro-type-scale-line-height7: 2rem;\n --duro-type-scale-line-height8: 2.25rem;\n --duro-type-scale-line-height9: 2.75rem;\n /* duration */\n --duro-duration-base: 200ms;\n --duro-duration-brisk: 160ms;\n --duro-duration-fast: 150ms;\n --duro-duration-instant: 0ms;\n --duro-duration-minimal: 1ms;\n --duro-duration-quick: 120ms;\n --duro-duration-slow: 280ms;\n /* easing */\n --duro-easing-ease-in: cubic-bezier(0.72, 0, 0.68, 0.28);\n --duro-easing-ease-out: cubic-bezier(0.32, 0.72, 0, 1);\n --duro-easing-linear: linear;\n --duro-easing-standard: ease;\n /* breakpoints */\n --duro-breakpoint-lg: 1024px;\n --duro-breakpoint-md: 768px;\n --duro-breakpoint-sm: 640px;\n --duro-breakpoint-xl: 1280px;\n --duro-breakpoint-xs: 480px;\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n /* colors */\n --duro-color-accent: #1e40af;\n --duro-color-accent-contrast: #ffffff;\n --duro-color-accent-hover: #1a3799;\n --duro-color-bg: #ffffff;\n --duro-color-bg-card: #f5f5f5;\n --duro-color-bg-card-hover: #ebebeb;\n --duro-color-border: #d4d4d4;\n --duro-color-contrast-border: rgba(255, 255, 255, 0.33);\n --duro-color-contrast-surface: #1a1a1a;\n --duro-color-error: #991b1b;\n --duro-color-error-bg: rgba(153, 27, 27, 0.08);\n --duro-color-error-border: rgba(153, 27, 27, 0.3);\n --duro-color-error-contrast: #ffffff;\n --duro-color-error-hover: #7f1d1d;\n --duro-color-error-text: #7f1d1d;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #6b21a8;\n --duro-color-highlight-bg: rgba(107, 33, 168, 0.08);\n --duro-color-highlight-border: rgba(107, 33, 168, 0.3);\n --duro-color-highlight-text: #581c87;\n --duro-color-info: #1e40af;\n --duro-color-info-bg: rgba(30, 64, 175, 0.08);\n --duro-color-info-border: rgba(30, 64, 175, 0.3);\n --duro-color-info-text: #1e40af;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #e5e5e5;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #166534;\n --duro-color-success-bg: rgba(22, 101, 52, 0.08);\n --duro-color-success-border: rgba(22, 101, 52, 0.3);\n --duro-color-success-text: #14532d;\n --duro-color-text: #1a1a1a;\n --duro-color-text-muted: #4a4a4a;\n --duro-color-warning: #92400e;\n --duro-color-warning-bg: rgba(146, 64, 14, 0.08);\n --duro-color-warning-border: rgba(146, 64, 14, 0.3);\n --duro-color-warning-text: #78350f;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #1e40af;\n --duro-shadow-drop-ready: inset 0 0 0 1px #d4d4d4;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.08);\n}\n\n:root[data-theme=\"high-contrast\"] {\n color-scheme: dark;\n /* colors */\n --duro-color-accent: #60a5fa;\n --duro-color-accent-contrast: #000000;\n --duro-color-accent-hover: #93c5fd;\n --duro-color-bg: #000000;\n --duro-color-bg-card: #111111;\n --duro-color-bg-card-hover: #1a1a1a;\n --duro-color-border: #555555;\n --duro-color-contrast-border: rgba(0, 0, 0, 0.42);\n --duro-color-contrast-surface: #ffffff;\n --duro-color-error: #f87171;\n --duro-color-error-bg: rgba(248, 113, 113, 0.15);\n --duro-color-error-border: rgba(248, 113, 113, 0.5);\n --duro-color-error-contrast: #000000;\n --duro-color-error-hover: #fca5a5;\n --duro-color-error-text: #fca5a5;\n --duro-color-fixed-dark: #000000;\n --duro-color-fixed-light: #ffffff;\n --duro-color-highlight: #d8b4fe;\n --duro-color-highlight-bg: rgba(216, 180, 254, 0.15);\n --duro-color-highlight-border: rgba(216, 180, 254, 0.5);\n --duro-color-highlight-text: #e9d5ff;\n --duro-color-info: #60a5fa;\n --duro-color-info-bg: rgba(96, 165, 250, 0.15);\n --duro-color-info-border: rgba(96, 165, 250, 0.5);\n --duro-color-info-text: #bfdbfe;\n --duro-color-inverse-border: rgba(0, 0, 0, 0.55);\n --duro-color-inverse-border-hover: rgba(0, 0, 0, 0.70);\n --duro-color-inverse-fill: rgba(0, 0, 0, 0.10);\n --duro-color-inverse-fill-hover: rgba(0, 0, 0, 0.18);\n --duro-color-on-contrast-surface: #000000;\n --duro-color-overlay-light: rgba(255, 255, 255, 0.78);\n --duro-color-scrim: rgba(0, 0, 0, 0.4);\n --duro-color-success: #4ade80;\n --duro-color-success-bg: rgba(74, 222, 128, 0.15);\n --duro-color-success-border: rgba(74, 222, 128, 0.5);\n --duro-color-success-text: #86efac;\n --duro-color-text: #ffffff;\n --duro-color-text-muted: #b0b0b0;\n --duro-color-warning: #fcd34d;\n --duro-color-warning-bg: rgba(252, 211, 77, 0.15);\n --duro-color-warning-border: rgba(252, 211, 77, 0.5);\n --duro-color-warning-text: #fef08a;\n /* shadows */\n --duro-shadow-drop-over: inset 0 0 0 2px #60a5fa;\n --duro-shadow-drop-ready: inset 0 0 0 1px #555555;\n --duro-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.8);\n --duro-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.7);\n --duro-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.6);\n}\n"
10169
10532
  },
10170
10533
  "unions": {
10171
10534
  "AlertVariant": [
@@ -10318,6 +10681,7 @@
10318
10681
  "plug",
10319
10682
  "search",
10320
10683
  "mic",
10684
+ "x",
10321
10685
  "sun",
10322
10686
  "moon",
10323
10687
  "monitor",
@@ -10415,6 +10779,10 @@
10415
10779
  "dropReady",
10416
10780
  "dropOver"
10417
10781
  ],
10782
+ "SnapPoint": [
10783
+ "medium",
10784
+ "large"
10785
+ ],
10418
10786
  "SpacingKey": [
10419
10787
  "xs",
10420
10788
  "sm",