@treeui/mcp 0.3.0 → 0.4.1

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.
@@ -1,8 +1,8 @@
1
1
  // src/generated/treeui.catalog.json
2
2
  var treeui_catalog_default = {
3
- generatedAt: "2026-07-15T22:47:49.302Z",
3
+ generatedAt: "2026-07-18T19:02:09.022Z",
4
4
  packageName: "@treeui/vue",
5
- packageVersion: "0.13.0",
5
+ packageVersion: "0.15.0",
6
6
  decisionGuides: [
7
7
  {
8
8
  id: "single-choice",
@@ -946,6 +946,22 @@ var treeui_catalog_default = {
946
946
  type: "boolean",
947
947
  default: "true"
948
948
  },
949
+ {
950
+ name: "showCollapseButton",
951
+ type: "boolean",
952
+ default: "true",
953
+ note: "Renders the built-in collapse toggle in the sidebar footer. Desktop only, and only when `collapsible` is set \u2014 no need to wire your own."
954
+ },
955
+ {
956
+ name: "collapseLabel",
957
+ type: "string",
958
+ default: "Collapse sidebar"
959
+ },
960
+ {
961
+ name: "expandLabel",
962
+ type: "string",
963
+ default: "Expand sidebar"
964
+ },
949
965
  {
950
966
  name: "menuLabel",
951
967
  type: "string",
@@ -980,14 +996,26 @@ var treeui_catalog_default = {
980
996
  {
981
997
  name: "header"
982
998
  },
999
+ {
1000
+ name: "header-start"
1001
+ },
1002
+ {
1003
+ name: "sidebar-header"
1004
+ },
983
1005
  {
984
1006
  name: "sidebar"
985
1007
  },
1008
+ {
1009
+ name: "sidebar-footer"
1010
+ },
986
1011
  {
987
1012
  name: "default"
988
1013
  },
989
1014
  {
990
1015
  name: "menu-icon"
1016
+ },
1017
+ {
1018
+ name: "collapse-icon"
991
1019
  }
992
1020
  ],
993
1021
  a11y: [
@@ -1015,7 +1043,10 @@ var treeui_catalog_default = {
1015
1043
  "left_and_right_variants: true",
1016
1044
  "locks_body_scroll_while_drawer_open: true",
1017
1045
  "overlay_click_closes_when_enabled: true",
1018
- "exposes_slot_props_with_mobile_collapsed_and_toggles: true"
1046
+ "exposes_slot_props_with_mobile_collapsed_and_toggles: true",
1047
+ "sidebar_footer_pinned_to_bottom: true",
1048
+ "sidebar_header_and_footer_inset_to_align_with_nav_icons: true",
1049
+ "header_mirrors_shell_columns_when_header_start_is_used: true"
1019
1050
  ],
1020
1051
  selection: {
1021
1052
  keywords: [
@@ -1287,15 +1318,17 @@ var treeui_catalog_default = {
1287
1318
  },
1288
1319
  {
1289
1320
  name: "variant",
1290
- type: "TVariant",
1321
+ type: "TVariant | brand",
1291
1322
  default: "solid",
1292
1323
  values: [
1293
1324
  "solid",
1294
1325
  "outline",
1295
1326
  "ghost",
1296
1327
  "soft",
1297
- "danger"
1298
- ]
1328
+ "danger",
1329
+ "brand"
1330
+ ],
1331
+ note: "`brand` is a button-only, high-emphasis gradient variant driven by the `--tree-gradient-brand` token. Use it for the single primary CTA of a screen."
1299
1332
  },
1300
1333
  {
1301
1334
  name: "size",
@@ -1322,6 +1355,18 @@ var treeui_catalog_default = {
1322
1355
  name: "type",
1323
1356
  type: "button | submit | reset",
1324
1357
  default: "button"
1358
+ },
1359
+ {
1360
+ name: "block",
1361
+ type: "boolean",
1362
+ default: "false",
1363
+ note: "Stretches the button to the full width of its container. Useful for menu rows and mobile CTAs."
1364
+ },
1365
+ {
1366
+ name: "align",
1367
+ type: "start | center | end",
1368
+ default: "center",
1369
+ note: "Content alignment; only meaningful together with `block`. Use `start` for left-aligned icon+label menu rows."
1325
1370
  }
1326
1371
  ],
1327
1372
  events: [
@@ -1387,7 +1432,6 @@ var treeui_catalog_default = {
1387
1432
  values: [
1388
1433
  "outline",
1389
1434
  "soft",
1390
- "solid",
1391
1435
  "inset"
1392
1436
  ]
1393
1437
  },
@@ -1426,7 +1470,8 @@ var treeui_catalog_default = {
1426
1470
  a11y: [],
1427
1471
  behavior: [
1428
1472
  "body_wrapper_class: t-card__body",
1429
- "solid_variant_rescopes_color_tokens_for_nested_content: true"
1473
+ 'variant_effects: {"outline":"Base surface with a default border. The styling baseline \u2014 the `t-card--outline` class is emitted but carries no CSS rule of its own.","soft":"Subtle tinted surface (bg-subtle), same border and shadow as outline.","inset":"Same tinted surface as soft, but with no shadow, so it reads as recessed inside another surface."}',
1474
+ "note: No card variant inverts its surface, so nested content inherits the normal color tokens. For a high-emphasis card, use a brand-colored border rather than an inverted background."
1430
1475
  ],
1431
1476
  selection: {
1432
1477
  keywords: [
@@ -1755,6 +1800,92 @@ var treeui_catalog_default = {
1755
1800
  },
1756
1801
  recipes: []
1757
1802
  },
1803
+ {
1804
+ name: "TColorSwatch",
1805
+ aliases: [],
1806
+ package: "@treeui/vue",
1807
+ kind: "form",
1808
+ category: "Data Entry",
1809
+ status: "available",
1810
+ purpose: "Pick a colour from a row of preset swatches, with an optional native colour input for an arbitrary value. Built for accent/brand pickers.",
1811
+ sourceFile: "packages/vue/src/components/TColorSwatch.vue",
1812
+ storyFile: "apps/docs/src/stories/ColorSwatch.stories.ts",
1813
+ storyTitle: "Components/Data Entry/ColorSwatch",
1814
+ manifestFile: "docs/ai/COMPONENTS/color-swatch.yaml",
1815
+ props: [
1816
+ {
1817
+ name: "modelValue",
1818
+ type: "string",
1819
+ default: "undefined",
1820
+ note: "Selected colour as a hex string. Matching is case-insensitive."
1821
+ },
1822
+ {
1823
+ name: "options",
1824
+ type: "TColorSwatchOption[]",
1825
+ default: "[]",
1826
+ note: "Preset swatches, each `{ label, value }`. `label` becomes the button's accessible name."
1827
+ },
1828
+ {
1829
+ name: "allowCustom",
1830
+ type: "boolean",
1831
+ default: "false",
1832
+ note: 'Appends a native `<input type="color">` for values outside the presets.'
1833
+ },
1834
+ {
1835
+ name: "size",
1836
+ type: "TSize",
1837
+ default: "md",
1838
+ values: [
1839
+ "sm",
1840
+ "md",
1841
+ "lg"
1842
+ ]
1843
+ },
1844
+ {
1845
+ name: "label",
1846
+ type: "string",
1847
+ default: "Color",
1848
+ note: 'Accessible label for the swatch group (`role="group"`).'
1849
+ },
1850
+ {
1851
+ name: "customLabel",
1852
+ type: "string",
1853
+ default: "Custom color"
1854
+ },
1855
+ {
1856
+ name: "disabled",
1857
+ type: "boolean",
1858
+ default: "false"
1859
+ }
1860
+ ],
1861
+ events: [
1862
+ {
1863
+ name: "update:modelValue"
1864
+ }
1865
+ ],
1866
+ slots: [],
1867
+ a11y: [
1868
+ 'Wrapper is `role="group"` labelled by `label`.',
1869
+ "Each swatch is a native button with `aria-pressed` reflecting selection and `aria-label` from the option label.",
1870
+ "Selection is conveyed by an outline (not colour alone), since the swatch's own colour is the content."
1871
+ ],
1872
+ behavior: [
1873
+ "emits_on_preset_click: true",
1874
+ "emits_on_custom_input: true",
1875
+ "blocks_interaction_when_disabled: true"
1876
+ ],
1877
+ selection: {
1878
+ keywords: [],
1879
+ useWhen: [],
1880
+ avoidWhen: [],
1881
+ alternatives: []
1882
+ },
1883
+ setup: {
1884
+ required: [],
1885
+ notes: []
1886
+ },
1887
+ recipes: []
1888
+ },
1758
1889
  {
1759
1890
  name: "TCombobox",
1760
1891
  aliases: [],
@@ -2140,9 +2271,14 @@ var treeui_catalog_default = {
2140
2271
  "The page shell should stay full-width while content stays readable."
2141
2272
  ],
2142
2273
  avoidWhen: [
2143
- "You need internal spacing between children."
2274
+ "You need internal spacing between children.",
2275
+ "You are laying out a whole screen \u2014 reach for TPage, which adds page padding and vertical rhythm."
2144
2276
  ],
2145
2277
  alternatives: [
2278
+ {
2279
+ component: "TPage",
2280
+ when: "You want a full page region with block padding and vertical spacing between sections, not just a width constraint."
2281
+ },
2146
2282
  {
2147
2283
  component: "TStack",
2148
2284
  when: "The main need is vertical or horizontal spacing."
@@ -3141,7 +3277,7 @@ var treeui_catalog_default = {
3141
3277
  kind: "form",
3142
3278
  category: "Form",
3143
3279
  status: "available",
3144
- purpose: "Composable dropzone and file picker with browse, drag-and-drop, and Ctrl+V paste support.",
3280
+ purpose: "Composable dropzone and file picker with browse, drag-and-drop, and Ctrl+V paste support, plus per-file upload state: progress, remaining time, thumbnails, and independent per-file retry/resume.",
3145
3281
  sourceFile: "packages/vue/src/components/TFileUpload.vue",
3146
3282
  storyFile: "apps/docs/src/stories/FileUpload.stories.ts",
3147
3283
  storyTitle: "Components/Form/FileUpload",
@@ -3165,12 +3301,14 @@ var treeui_catalog_default = {
3165
3301
  {
3166
3302
  name: "disabled",
3167
3303
  type: "boolean",
3168
- default: "false"
3304
+ default: "false",
3305
+ description: "The ONLY prop that gates interaction. Disables the dropzone, the native input, Clear all, and every per-row Remove/Retry."
3169
3306
  },
3170
3307
  {
3171
3308
  name: "loading",
3172
3309
  type: "boolean",
3173
- default: "false"
3310
+ default: "false",
3311
+ description: "Presentational only. Applies `is-loading`, `aria-busy` on the dropzone, and the corner spinner. It gates NO interaction \u2014 see behavior.loading_is_presentational."
3174
3312
  },
3175
3313
  {
3176
3314
  name: "invalid",
@@ -3231,44 +3369,142 @@ var treeui_catalog_default = {
3231
3369
  name: "showFileList",
3232
3370
  type: "boolean",
3233
3371
  default: "true"
3372
+ },
3373
+ {
3374
+ name: "uploadState",
3375
+ type: "TFileUploadStateSource",
3376
+ default: "none",
3377
+ description: "Per-file transport state, keyed by File OBJECT IDENTITY. Accepts a ReadonlyMap<File, TFileUploadState> or a (file, index) => TFileUploadState | undefined resolver. Omit it and the component renders exactly as before: no bars, no status lines, no retry."
3378
+ },
3379
+ {
3380
+ name: "dragLabel",
3381
+ type: "string",
3382
+ default: "Release to upload",
3383
+ description: "Replaces `label` in the dropzone while an acceptable drag is over it. Set it to the same value as `label` to opt out of the text swap."
3384
+ },
3385
+ {
3386
+ name: "dragRejectLabel",
3387
+ type: "string",
3388
+ default: "This file type is not accepted",
3389
+ description: "Replaces `label` while a drag that cannot work is over the dropzone. Only reachable when drag-reject detection is possible \u2014 see behavior.drag_reject_detection_is_mime_only."
3390
+ },
3391
+ {
3392
+ name: "thumbnails",
3393
+ type: "boolean",
3394
+ default: "true",
3395
+ description: "Mint object-URL previews and decode dimensions for image/* files. Set false when you always supply `state.thumbnailUrl` yourself."
3396
+ },
3397
+ {
3398
+ name: "maxThumbnailSize",
3399
+ type: "number | null",
3400
+ default: "10 * 1024 * 1024",
3401
+ description: "Skip auto-thumbnailing above this byte size. null removes the cap."
3402
+ },
3403
+ {
3404
+ name: "retryLabel",
3405
+ type: "string",
3406
+ default: "Retry"
3407
+ },
3408
+ {
3409
+ name: "resumeLabel",
3410
+ type: "(percent: number) => string",
3411
+ default: "(percent) => `Resume from ${percent}%`",
3412
+ description: "Receives Math.round(state.progress). Used only when state.resumable === true AND state.progress is a number."
3413
+ },
3414
+ {
3415
+ name: "remainingTimeFormat",
3416
+ type: "(remainingMs: number) => string",
3417
+ default: "5s/60s/60min thresholds \u2014 see behavior.remaining_time_strings",
3418
+ description: "Formats state.remainingMs. Ignored for a row that supplies state.remainingLabel."
3419
+ },
3420
+ {
3421
+ name: "statusLabels",
3422
+ type: "Partial<Record<TFileUploadStatus, string>>",
3423
+ default: "{}",
3424
+ description: "MERGED over the defaults { pending: Waiting, uploading: Uploading, paused: Paused, error: Upload failed, success: Uploaded } \u2014 pass only the keys you override."
3234
3425
  }
3235
3426
  ],
3236
3427
  events: [
3237
3428
  {
3238
- name: "update:modelValue"
3429
+ name: "update:modelValue",
3430
+ description: "Emitted with the next file list when files are added, removed, or cleared.",
3431
+ payload: "File[]"
3432
+ },
3433
+ {
3434
+ name: "files-accepted",
3435
+ description: "Emitted with only the newly accepted files, after accept/maxFiles/maxFileSize validation.",
3436
+ payload: "File[]"
3437
+ },
3438
+ {
3439
+ name: "files-rejected",
3440
+ description: "Pre-acceptance validation failures. Unrelated to transport errors, which you report through `uploadState`.",
3441
+ payload: "TFileUploadRejection[]"
3239
3442
  },
3240
3443
  {
3241
- name: "files-accepted"
3444
+ name: "retry",
3445
+ description: "Emitted when a row's retry affordance is activated. Pure intent \u2014 the component performs no upload. Use `payload.mode` ('resume' | 'restart'); it reports the affordance the component ACTUALLY DREW, so never re-derive it from your own state.",
3446
+ payload: "TFileUploadRetryPayload"
3242
3447
  },
3243
3448
  {
3244
- name: "files-rejected"
3449
+ name: "cancel",
3450
+ description: "Emitted ONLY for rows whose status is 'uploading' or 'paused', immediately BEFORE the `update:modelValue` that drops them \u2014 so the file is still in the list when your handler runs and you can abort the request. `reason` is 'remove' for one row or 'clear' (one event per in-flight file) for Clear all.",
3451
+ payload: "TFileUploadCancelPayload"
3245
3452
  }
3246
3453
  ],
3247
3454
  slots: [
3248
3455
  {
3249
- name: "icon"
3456
+ name: "icon",
3457
+ description: "Replaces the dropzone icon."
3250
3458
  },
3251
3459
  {
3252
- name: "default"
3460
+ name: "default",
3461
+ description: "Replaces the dropzone body. Receives `{ files, isDragActive, isDragReject, openFileDialog, clearFiles }`."
3253
3462
  },
3254
3463
  {
3255
- name: "file"
3464
+ name: "file",
3465
+ description: "Full row override. Receives all of TFileUploadFileSlotProps, including every derived label, so a custom row re-derives nothing."
3256
3466
  },
3257
3467
  {
3258
- name: "actions"
3468
+ name: "file-status",
3469
+ description: "Replaces ONLY the progress bar + status line + error block inside the default row. Receives TFileUploadFileSlotProps. Name, meta, thumbnail, Retry, and Remove keep rendering. Ignored when the `file` slot is provided."
3470
+ },
3471
+ {
3472
+ name: "actions",
3473
+ description: "Footer actions. Receives `{ files, openFileDialog, clearFiles }` \u2014 no upload state."
3259
3474
  }
3260
3475
  ],
3261
3476
  a11y: [
3262
3477
  "dropzone_role: button",
3263
3478
  "keyboard_activation: Enter_and_Space_open_the_file_picker",
3264
3479
  "supports_paste_when_component_is_focused: true",
3265
- "file_feedback_announced_via_live_region: true"
3480
+ "file_feedback_announced_via_live_region: true",
3481
+ "aria_busy_on_dropzone_tracks_loading_prop: true",
3482
+ "aria_busy_on_row_tracks_that_rows_uploading_status: true",
3483
+ "per_row_progressbar_is_labelled_with_status_and_filename: e.g. 'Uploading hero.png' \u2014 every row's bar has a distinct accessible name.",
3484
+ "terminal_transitions_announced_with_filenames: One polite root live region (`role=log`) announces only success/error, e.g. 'alpha.txt uploaded.' / 'alpha.txt failed. Connection lost.'",
3485
+ "repeated_identical_messages_are_still_announced: The log appends each message as a new node rather than overwriting one string, so a retry that fails with the byte-identical error still mutates the region and is announced. It is not aria-atomic: only the appended message is read, and the trimmed history (5 most recent) is not.",
3486
+ "percentages_are_never_announced: Percent and ETA live in non-live text; the value reaches assistive tech through the progressbar's aria-valuenow.",
3487
+ "retry_button_is_described_by_its_row_error: Announces 'Resume from 90%, Connection lost at 90%'.",
3488
+ "focus_is_rescued_when_retry_unmounts: Flipping error -> uploading removes the focused Retry; focus moves to that row's Remove (or the dropzone), and only when it was genuinely lost.",
3489
+ "remove_and_retry_meet_the_44px_target: true"
3266
3490
  ],
3267
3491
  behavior: [
3268
3492
  "accepts_files_from_input_drop_and_paste: true",
3269
3493
  "multiple_false_replaces_existing_selection: true",
3270
3494
  "accept_maxFiles_and_maxFileSize_apply_to_all_entry_points: true",
3271
- "selected_files_can_be_removed_or_cleared: true"
3495
+ "selected_files_can_be_removed_or_cleared: true",
3496
+ "upload_state_is_keyed_by_file_identity: Both source shapes key on the File object, never on index or name. Key your own store with the File objects you receive from `files-accepted`.",
3497
+ "a_lookup_miss_renders_nothing: An unknown File yields `undefined` and the row falls back to name + meta only \u2014 no bar, no status, no retry. This fail-blank is deliberate: it is the back-compat path and it fails loudly instead of showing another file's percentage.",
3498
+ "loading_is_presentational: BREAKING since 0.14.0 \u2014 `loading` no longer disables anything. It was freezing the dropzone, Clear all, and every Remove, which blocked removing file 1 while file 4 uploaded. Use `:disabled` to reproduce the old behavior; prefer `uploadState` and freeze nothing.",
3499
+ "each_file_has_an_independent_track: One row's error, progress, or retry never gates another's. Remove stays enabled for in-flight rows and emits `cancel` so you can abort.",
3500
+ "errors_are_per_row: Transport errors go in `state.error` and render under their own row. They never enter the dropzone feedback list, which Remove and Clear all wipe.",
3501
+ "drag_feedback_has_three_signals: Border, glow, and a text swap to `dragLabel` \u2014 all three fire together, including when files are already listed.",
3502
+ "drag_reject_detection_is_mime_only: `is-drag-reject` + `dragRejectLabel` require every `accept` token to be a MIME token; with any `.ext` token the dropzone stays neutral, because a drag exposes only `.type`. A mixed drag (one good file, one bad) is NOT rejected, and the drop is never blocked \u2014 rejected files always get the normal `files-rejected` explanation.",
3503
+ "progress_bar_visibility: Shown on uploading/paused/error, and on success only with numeric progress. 'uploading' without progress is indeterminate; paused/error without progress is a determinate 0 \u2014 an errored bar must never animate.",
3504
+ "the_bar_is_never_reset_on_error: It holds `state.progress` in the error tone. That retained 90% is what makes the resume promise legible.",
3505
+ "remaining_time_strings: < 5000ms -> 'Less than 5s left'; < 60s -> 'About 12s left'; < 60min -> 'About 2 min left'; else 'About 2h left'. Negative, NaN, and Infinity render nothing.",
3506
+ "status_line_composition: Joins the present parts with ' \xB7 ': 'Uploading \xB7 62% \xB7 About 12s left' while uploading, 'Upload failed \xB7 2.1 MB of 2.4 MB uploaded' on a resumable error.",
3507
+ "thumbnails_are_client_only: Object URLs are minted after mount and revoked on removal and unmount. SSR and the hydrating render both show the extension chip, so there is no mismatch."
3272
3508
  ],
3273
3509
  selection: {
3274
3510
  keywords: [],
@@ -3633,6 +3869,20 @@ var treeui_catalog_default = {
3633
3869
  "lg"
3634
3870
  ]
3635
3871
  },
3872
+ {
3873
+ name: "width",
3874
+ type: "TFieldWidth",
3875
+ default: "full",
3876
+ values: [
3877
+ "xs",
3878
+ "sm",
3879
+ "md",
3880
+ "lg",
3881
+ "xl",
3882
+ "full"
3883
+ ],
3884
+ note: "Inline-size cap (max-inline-size). Fluid by default; use instead of an inline width style."
3885
+ },
3636
3886
  {
3637
3887
  name: "disabled",
3638
3888
  type: "boolean",
@@ -3717,6 +3967,43 @@ var treeui_catalog_default = {
3717
3967
  "tag-input"
3718
3968
  ]
3719
3969
  },
3970
+ {
3971
+ name: "TKbd",
3972
+ aliases: [],
3973
+ package: "@treeui/vue",
3974
+ kind: "primitive",
3975
+ category: "Data Display",
3976
+ status: "available",
3977
+ purpose: "Keyboard-key hint (e.g. \u2318K, Esc, Ctrl) rendered as a native <kbd> with the library's chip styling.",
3978
+ sourceFile: "packages/vue/src/components/TKbd.vue",
3979
+ storyFile: "apps/docs/src/stories/Kbd.stories.ts",
3980
+ storyTitle: "Components/Data Display/Kbd",
3981
+ manifestFile: "docs/ai/COMPONENTS/kbd.yaml",
3982
+ props: [],
3983
+ events: [],
3984
+ slots: [
3985
+ {
3986
+ name: "default"
3987
+ }
3988
+ ],
3989
+ a11y: [
3990
+ "Uses the native <kbd> element so assistive tech announces it as keyboard input."
3991
+ ],
3992
+ behavior: [
3993
+ "renders_native_kbd_element: true"
3994
+ ],
3995
+ selection: {
3996
+ keywords: [],
3997
+ useWhen: [],
3998
+ avoidWhen: [],
3999
+ alternatives: []
4000
+ },
4001
+ setup: {
4002
+ required: [],
4003
+ notes: []
4004
+ },
4005
+ recipes: []
4006
+ },
3720
4007
  {
3721
4008
  name: "TLink",
3722
4009
  aliases: [],
@@ -4323,7 +4610,7 @@ var treeui_catalog_default = {
4323
4610
  name: "items",
4324
4611
  type: "TNavMenuItem[]",
4325
4612
  default: "[]",
4326
- note: "Array of `{ label, value, shortLabel?, description?, icon?, to?, badge?, disabled? }`."
4613
+ note: "Array of `{ label, value, shortLabel?, description?, icon?, to?, badge?, disabled?, exact? }`."
4327
4614
  },
4328
4615
  {
4329
4616
  name: "size",
@@ -4344,6 +4631,12 @@ var treeui_catalog_default = {
4344
4631
  name: "disabled",
4345
4632
  type: "boolean",
4346
4633
  default: "false"
4634
+ },
4635
+ {
4636
+ name: "exact",
4637
+ type: "boolean",
4638
+ default: "true",
4639
+ note: "Default active-match strategy for router-link items. `true` keeps only the exactly-active route highlighted (prevents prefix matches from lighting up several items at once). Override per item via `TNavMenuItem.exact`."
4347
4640
  }
4348
4641
  ],
4349
4642
  events: [
@@ -4518,75 +4811,262 @@ var treeui_catalog_default = {
4518
4811
  recipes: []
4519
4812
  },
4520
4813
  {
4521
- name: "TPagination",
4814
+ name: "TPage",
4522
4815
  aliases: [],
4523
4816
  package: "@treeui/vue",
4524
- kind: "primitive",
4525
- category: "Navigation",
4817
+ kind: "layout",
4818
+ category: "Layout",
4526
4819
  status: "available",
4527
- purpose: "Page controls with previous/next navigation, numbered page buttons, and ellipsis for large page ranges. Supports v-model for the active page.\n",
4528
- sourceFile: "packages/vue/src/components/TPagination.vue",
4529
- storyFile: "apps/docs/src/stories/Pagination.stories.ts",
4530
- storyTitle: "Components/Navigation/Pagination",
4531
- manifestFile: "docs/ai/COMPONENTS/pagination.yaml",
4820
+ purpose: "Semantic page region under the app shell: block padding, a centered max-width content column, and standardized vertical rhythm between sections.",
4821
+ sourceFile: "packages/vue/src/components/TPage.vue",
4822
+ storyFile: "apps/docs/src/stories/Page.stories.ts",
4823
+ storyTitle: "Components/Layout/Page",
4824
+ manifestFile: "docs/ai/COMPONENTS/page.yaml",
4532
4825
  props: [
4533
4826
  {
4534
- name: "modelValue",
4535
- type: "number",
4536
- default: "1",
4537
- description: "Current active page (1-based). Use with v-model."
4538
- },
4539
- {
4540
- name: "totalPages",
4541
- type: "number",
4542
- required: true,
4543
- description: "Total number of pages."
4544
- },
4545
- {
4546
- name: "siblings",
4547
- type: "number",
4548
- default: "1",
4549
- description: "Number of sibling page buttons shown around the current page."
4827
+ name: "as",
4828
+ type: "string",
4829
+ default: "div"
4550
4830
  },
4551
4831
  {
4552
- name: "size",
4553
- type: "TSize",
4554
- default: "md",
4832
+ name: "width",
4833
+ type: "TContainerSize",
4834
+ default: "lg",
4555
4835
  values: [
4556
4836
  "sm",
4557
4837
  "md",
4558
- "lg"
4838
+ "lg",
4839
+ "xl",
4840
+ "full"
4559
4841
  ],
4560
- description: "Size variant for the pagination controls."
4842
+ note: "Max-width of the centered content column. Mirrors TContainer sizes."
4561
4843
  },
4562
4844
  {
4563
- name: "disabled",
4845
+ name: "padded",
4564
4846
  type: "boolean",
4565
- default: "false",
4566
- description: "Disables all pagination controls."
4847
+ default: "true",
4848
+ note: "Adds vertical (block) padding on the region and horizontal padding on the inner column."
4567
4849
  },
4568
4850
  {
4569
- name: "label",
4570
- type: "string",
4571
- default: "Pagination",
4572
- description: "Accessible label for the nav element."
4573
- }
4574
- ],
4575
- events: [
4576
- {
4577
- name: "update:modelValue",
4578
- description: "Emitted when the active page changes. Enables v-model binding.",
4579
- payload: "[page: number]"
4851
+ name: "gap",
4852
+ type: "TPageGap",
4853
+ default: "lg",
4854
+ values: [
4855
+ "sm",
4856
+ "md",
4857
+ "lg"
4858
+ ],
4859
+ note: "Vertical spacing between direct children (e.g. a TPageHeader and content sections)."
4580
4860
  }
4581
4861
  ],
4862
+ events: [],
4582
4863
  slots: [
4583
4864
  {
4584
- name: "previous",
4585
- description: "Custom content for the previous button."
4586
- },
4587
- {
4588
- name: "next",
4589
- description: "Custom content for the next button."
4865
+ name: "default"
4866
+ }
4867
+ ],
4868
+ a11y: [
4869
+ "Structural wrapper only; landmark semantics come from `as` and the surrounding app shell (avoid nesting a second `main`)."
4870
+ ],
4871
+ behavior: [
4872
+ "centers_content_column_by_width: true",
4873
+ "applies_block_padding_when_padded: true",
4874
+ "spaces_children_vertically_by_gap: true"
4875
+ ],
4876
+ selection: {
4877
+ keywords: [
4878
+ "page",
4879
+ "page shell",
4880
+ "content column",
4881
+ "screen layout"
4882
+ ],
4883
+ useWhen: [
4884
+ "You want a standard screen region under the app shell with consistent max width, page padding, and vertical rhythm between sections."
4885
+ ],
4886
+ avoidWhen: [
4887
+ "You only need a bare width constraint with no page padding or child spacing."
4888
+ ],
4889
+ alternatives: [
4890
+ {
4891
+ component: "TContainer",
4892
+ when: "You just need a centered max-width wrapper without page-level block padding or child spacing."
4893
+ },
4894
+ {
4895
+ component: "TStack",
4896
+ when: "You only need to space a group of children, not lay out a whole page."
4897
+ }
4898
+ ]
4899
+ },
4900
+ setup: {
4901
+ required: [],
4902
+ notes: []
4903
+ },
4904
+ recipes: []
4905
+ },
4906
+ {
4907
+ name: "TPageHeader",
4908
+ aliases: [],
4909
+ package: "@treeui/vue",
4910
+ kind: "layout",
4911
+ category: "Layout",
4912
+ status: "available",
4913
+ purpose: "Canonical page title block: heading, optional subtitle, and a right-aligned actions area, so title hierarchy and alignment stay consistent across screens.",
4914
+ sourceFile: "packages/vue/src/components/TPageHeader.vue",
4915
+ storyFile: "apps/docs/src/stories/PageHeader.stories.ts",
4916
+ storyTitle: "Components/Layout/PageHeader",
4917
+ manifestFile: "docs/ai/COMPONENTS/page-header.yaml",
4918
+ props: [
4919
+ {
4920
+ name: "title",
4921
+ type: "string",
4922
+ default: "",
4923
+ note: "Rendered inside the heading element. Overridden by the `title` slot."
4924
+ },
4925
+ {
4926
+ name: "subtitle",
4927
+ type: "string",
4928
+ default: ""
4929
+ },
4930
+ {
4931
+ name: "level",
4932
+ type: "1 | 2 | 3 | 4 | 5 | 6",
4933
+ default: "1",
4934
+ note: "Heading level for the title element. Default `1` treats it as the page title; lower it when the page already has an h1."
4935
+ },
4936
+ {
4937
+ name: "as",
4938
+ type: "string",
4939
+ default: "header"
4940
+ }
4941
+ ],
4942
+ events: [],
4943
+ slots: [
4944
+ {
4945
+ name: "breadcrumb"
4946
+ },
4947
+ {
4948
+ name: "title"
4949
+ },
4950
+ {
4951
+ name: "subtitle"
4952
+ },
4953
+ {
4954
+ name: "actions"
4955
+ },
4956
+ {
4957
+ name: "default"
4958
+ }
4959
+ ],
4960
+ a11y: [
4961
+ "Title renders as a real heading element (`h1`\u2013`h6`) chosen by `level`; keep one h1 per page.",
4962
+ "Actions area holds standard focusable controls (e.g. TButton) \u2014 no custom roles added."
4963
+ ],
4964
+ behavior: [
4965
+ "title_and_subtitle_optional: true",
4966
+ "heading_level_controlled_by_level: true",
4967
+ "actions_area_right_aligned_and_wraps: true",
4968
+ "default_slot_renders_below_heading: true"
4969
+ ],
4970
+ selection: {
4971
+ keywords: [
4972
+ "page header",
4973
+ "title bar",
4974
+ "page title",
4975
+ "heading and actions"
4976
+ ],
4977
+ useWhen: [
4978
+ "A screen needs a title (and optional subtitle) with a right-aligned actions area in a consistent hierarchy."
4979
+ ],
4980
+ avoidWhen: [
4981
+ "You need cross-page hierarchy or navigation trails rather than a single page title."
4982
+ ],
4983
+ alternatives: [
4984
+ {
4985
+ component: "TBreadcrumb",
4986
+ when: "You are showing where the page sits in a hierarchy, not its title."
4987
+ },
4988
+ {
4989
+ component: "TNavbar",
4990
+ when: "You need a top application bar, not a per-page title block."
4991
+ }
4992
+ ]
4993
+ },
4994
+ setup: {
4995
+ required: [],
4996
+ notes: []
4997
+ },
4998
+ recipes: []
4999
+ },
5000
+ {
5001
+ name: "TPagination",
5002
+ aliases: [],
5003
+ package: "@treeui/vue",
5004
+ kind: "primitive",
5005
+ category: "Navigation",
5006
+ status: "available",
5007
+ purpose: "Page controls with previous/next navigation, numbered page buttons, and ellipsis for large page ranges. Supports v-model for the active page.\n",
5008
+ sourceFile: "packages/vue/src/components/TPagination.vue",
5009
+ storyFile: "apps/docs/src/stories/Pagination.stories.ts",
5010
+ storyTitle: "Components/Navigation/Pagination",
5011
+ manifestFile: "docs/ai/COMPONENTS/pagination.yaml",
5012
+ props: [
5013
+ {
5014
+ name: "modelValue",
5015
+ type: "number",
5016
+ default: "1",
5017
+ description: "Current active page (1-based). Use with v-model."
5018
+ },
5019
+ {
5020
+ name: "totalPages",
5021
+ type: "number",
5022
+ required: true,
5023
+ description: "Total number of pages."
5024
+ },
5025
+ {
5026
+ name: "siblings",
5027
+ type: "number",
5028
+ default: "1",
5029
+ description: "Number of sibling page buttons shown around the current page."
5030
+ },
5031
+ {
5032
+ name: "size",
5033
+ type: "TSize",
5034
+ default: "md",
5035
+ values: [
5036
+ "sm",
5037
+ "md",
5038
+ "lg"
5039
+ ],
5040
+ description: "Size variant for the pagination controls."
5041
+ },
5042
+ {
5043
+ name: "disabled",
5044
+ type: "boolean",
5045
+ default: "false",
5046
+ description: "Disables all pagination controls."
5047
+ },
5048
+ {
5049
+ name: "label",
5050
+ type: "string",
5051
+ default: "Pagination",
5052
+ description: "Accessible label for the nav element."
5053
+ }
5054
+ ],
5055
+ events: [
5056
+ {
5057
+ name: "update:modelValue",
5058
+ description: "Emitted when the active page changes. Enables v-model binding.",
5059
+ payload: "[page: number]"
5060
+ }
5061
+ ],
5062
+ slots: [
5063
+ {
5064
+ name: "previous",
5065
+ description: "Custom content for the previous button."
5066
+ },
5067
+ {
5068
+ name: "next",
5069
+ description: "Custom content for the next button."
4590
5070
  }
4591
5071
  ],
4592
5072
  a11y: [
@@ -5174,6 +5654,20 @@ var treeui_catalog_default = {
5174
5654
  "lg"
5175
5655
  ]
5176
5656
  },
5657
+ {
5658
+ name: "width",
5659
+ type: "TFieldWidth",
5660
+ default: "full",
5661
+ values: [
5662
+ "xs",
5663
+ "sm",
5664
+ "md",
5665
+ "lg",
5666
+ "xl",
5667
+ "full"
5668
+ ],
5669
+ note: "Inline-size cap (max-inline-size). Fluid by default; use instead of an inline width style."
5670
+ },
5177
5671
  {
5178
5672
  name: "disabled",
5179
5673
  type: "boolean",
@@ -5588,6 +6082,46 @@ var treeui_catalog_default = {
5588
6082
  },
5589
6083
  recipes: []
5590
6084
  },
6085
+ {
6086
+ name: "TSpacer",
6087
+ aliases: [],
6088
+ package: "@treeui/vue",
6089
+ kind: "layout",
6090
+ category: "Layout",
6091
+ status: "available",
6092
+ purpose: "Flexible spacer that absorbs free space between siblings in a flex layout (e.g. TStack), replacing hand-written margin:auto / flex:1.",
6093
+ sourceFile: "packages/vue/src/components/TSpacer.vue",
6094
+ storyFile: "apps/docs/src/stories/Spacer.stories.ts",
6095
+ storyTitle: "Components/Layout/Spacer",
6096
+ manifestFile: "docs/ai/COMPONENTS/spacer.yaml",
6097
+ props: [
6098
+ {
6099
+ name: "as",
6100
+ type: "string",
6101
+ default: "div"
6102
+ }
6103
+ ],
6104
+ events: [],
6105
+ slots: [],
6106
+ a11y: [
6107
+ "Rendered with aria-hidden='true'; it is a layout-only element with no content."
6108
+ ],
6109
+ behavior: [
6110
+ "grows_to_fill_free_space: true",
6111
+ "pushes_siblings_apart_or_to_ends: true"
6112
+ ],
6113
+ selection: {
6114
+ keywords: [],
6115
+ useWhen: [],
6116
+ avoidWhen: [],
6117
+ alternatives: []
6118
+ },
6119
+ setup: {
6120
+ required: [],
6121
+ notes: []
6122
+ },
6123
+ recipes: []
6124
+ },
5591
6125
  {
5592
6126
  name: "TSparkline",
5593
6127
  aliases: [],
@@ -5760,6 +6294,82 @@ var treeui_catalog_default = {
5760
6294
  },
5761
6295
  recipes: []
5762
6296
  },
6297
+ {
6298
+ name: "TSplit",
6299
+ aliases: [],
6300
+ package: "@treeui/vue",
6301
+ kind: "layout",
6302
+ category: "Layout",
6303
+ status: "available",
6304
+ purpose: "Responsive two-pane layout (a wider main pane + a narrower aside) that sits side by side and stacks to one column when narrow \u2014 no media query.",
6305
+ sourceFile: "packages/vue/src/components/TSplit.vue",
6306
+ storyFile: "apps/docs/src/stories/Split.stories.ts",
6307
+ storyTitle: "Components/Layout/Split",
6308
+ manifestFile: "docs/ai/COMPONENTS/split.yaml",
6309
+ props: [
6310
+ {
6311
+ name: "as",
6312
+ type: "string",
6313
+ default: "div"
6314
+ },
6315
+ {
6316
+ name: "gap",
6317
+ type: "string",
6318
+ default: "var(--tree-space-5)"
6319
+ },
6320
+ {
6321
+ name: "ratio",
6322
+ type: "number",
6323
+ default: "2",
6324
+ note: "How many times faster the main pane grows than the aside pane."
6325
+ },
6326
+ {
6327
+ name: "minWidth",
6328
+ type: "string",
6329
+ default: "18rem",
6330
+ note: "Min width each pane keeps before the layout wraps to a single column (flex-wrap, no @media)."
6331
+ },
6332
+ {
6333
+ name: "side",
6334
+ type: "start | end",
6335
+ default: "end",
6336
+ note: "Which side the aside pane sits on when side by side."
6337
+ },
6338
+ {
6339
+ name: "align",
6340
+ type: "string",
6341
+ default: "start",
6342
+ note: "Cross-axis alignment (align-items)."
6343
+ }
6344
+ ],
6345
+ events: [],
6346
+ slots: [
6347
+ {
6348
+ name: "default"
6349
+ },
6350
+ {
6351
+ name: "aside"
6352
+ }
6353
+ ],
6354
+ a11y: [
6355
+ "Layout-only wrapper; semantics come from the slotted content."
6356
+ ],
6357
+ behavior: [
6358
+ "wraps_to_single_column_when_narrow: true",
6359
+ "media_query_free: true"
6360
+ ],
6361
+ selection: {
6362
+ keywords: [],
6363
+ useWhen: [],
6364
+ avoidWhen: [],
6365
+ alternatives: []
6366
+ },
6367
+ setup: {
6368
+ required: [],
6369
+ notes: []
6370
+ },
6371
+ recipes: []
6372
+ },
5763
6373
  {
5764
6374
  name: "TStack",
5765
6375
  aliases: [],
@@ -5811,6 +6421,12 @@ var treeui_catalog_default = {
5811
6421
  name: "reverse",
5812
6422
  type: "boolean",
5813
6423
  default: "false"
6424
+ },
6425
+ {
6426
+ name: "grow",
6427
+ type: "boolean",
6428
+ default: "false",
6429
+ note: "Expands the stack to fill available space along its parent's main axis (flex: 1 with min-size 0). Use it to make a sidebar column fill its container height."
5814
6430
  }
5815
6431
  ],
5816
6432
  events: [],
@@ -5858,6 +6474,80 @@ var treeui_catalog_default = {
5858
6474
  "chart-surface"
5859
6475
  ]
5860
6476
  },
6477
+ {
6478
+ name: "TStackItem",
6479
+ aliases: [],
6480
+ package: "@treeui/vue",
6481
+ kind: "layout",
6482
+ category: "Layout",
6483
+ status: "available",
6484
+ purpose: "Per-child flex control inside a TStack \u2014 grow, shrink, basis, min width and align-self \u2014 so one item can fill the row without hand-written CSS.",
6485
+ sourceFile: "packages/vue/src/components/TStackItem.vue",
6486
+ storyFile: "apps/docs/src/stories/StackItem.stories.ts",
6487
+ storyTitle: "Components/Layout/StackItem",
6488
+ manifestFile: "docs/ai/COMPONENTS/stack-item.yaml",
6489
+ props: [
6490
+ {
6491
+ name: "as",
6492
+ type: "string",
6493
+ default: "div"
6494
+ },
6495
+ {
6496
+ name: "grow",
6497
+ type: "boolean | number",
6498
+ default: "false",
6499
+ note: "true = flex-grow 1; a number sets the grow factor."
6500
+ },
6501
+ {
6502
+ name: "shrink",
6503
+ type: "boolean | number",
6504
+ default: "undefined",
6505
+ note: "false prevents shrinking; a number sets the shrink factor."
6506
+ },
6507
+ {
6508
+ name: "basis",
6509
+ type: "string",
6510
+ default: "undefined",
6511
+ note: "flex-basis (initial main-axis size)."
6512
+ },
6513
+ {
6514
+ name: "minWidth",
6515
+ type: "string",
6516
+ default: "undefined",
6517
+ note: "Minimum main-axis size before it wraps/shrinks."
6518
+ },
6519
+ {
6520
+ name: "align",
6521
+ type: "string",
6522
+ default: "undefined",
6523
+ note: "Per-item cross-axis alignment (align-self)."
6524
+ }
6525
+ ],
6526
+ events: [],
6527
+ slots: [
6528
+ {
6529
+ name: "default"
6530
+ }
6531
+ ],
6532
+ a11y: [
6533
+ "Layout-only wrapper; semantics come from the slotted content."
6534
+ ],
6535
+ behavior: [
6536
+ "controls_single_flex_child: true",
6537
+ "defaults_min_inline_size_zero: true"
6538
+ ],
6539
+ selection: {
6540
+ keywords: [],
6541
+ useWhen: [],
6542
+ avoidWhen: [],
6543
+ alternatives: []
6544
+ },
6545
+ setup: {
6546
+ required: [],
6547
+ notes: []
6548
+ },
6549
+ recipes: []
6550
+ },
5861
6551
  {
5862
6552
  name: "TStat",
5863
6553
  aliases: [],
@@ -6460,6 +7150,95 @@ var treeui_catalog_default = {
6460
7150
  "tag-input"
6461
7151
  ]
6462
7152
  },
7153
+ {
7154
+ name: "TText",
7155
+ aliases: [],
7156
+ package: "@treeui/vue",
7157
+ kind: "primitive",
7158
+ category: "Data Display",
7159
+ status: "available",
7160
+ purpose: "Typography primitive for body text, labels and headings \u2014 size, tone, weight and truncation from tokens, with no hand-written CSS.",
7161
+ sourceFile: "packages/vue/src/components/TText.vue",
7162
+ storyFile: "apps/docs/src/stories/Text.stories.ts",
7163
+ storyTitle: "Components/Data Display/Text",
7164
+ manifestFile: "docs/ai/COMPONENTS/text.yaml",
7165
+ props: [
7166
+ {
7167
+ name: "as",
7168
+ type: "string",
7169
+ default: "span",
7170
+ note: "Render element. Use `p`, `h1`\u2013`h6`, `label`, `small`, `strong`, \u2026 for correct semantics."
7171
+ },
7172
+ {
7173
+ name: "size",
7174
+ type: "TTextSize",
7175
+ default: "undefined",
7176
+ values: [
7177
+ "xs",
7178
+ "sm",
7179
+ "md",
7180
+ "lg",
7181
+ "xl",
7182
+ "2xl",
7183
+ "3xl"
7184
+ ],
7185
+ note: "Maps to --tree-font-size-*. Omitted = inherit."
7186
+ },
7187
+ {
7188
+ name: "tone",
7189
+ type: "TTextTone",
7190
+ default: "default",
7191
+ values: [
7192
+ "default",
7193
+ "muted",
7194
+ "inverse",
7195
+ "brand"
7196
+ ],
7197
+ note: "`default` inherits the current colour; others map to --tree-color-text-* / brand."
7198
+ },
7199
+ {
7200
+ name: "weight",
7201
+ type: "TTextWeight",
7202
+ default: "undefined",
7203
+ values: [
7204
+ "regular",
7205
+ "medium",
7206
+ "semibold",
7207
+ "bold"
7208
+ ]
7209
+ },
7210
+ {
7211
+ name: "truncate",
7212
+ type: "boolean",
7213
+ default: "false",
7214
+ note: "Clip to a single line with an ellipsis."
7215
+ }
7216
+ ],
7217
+ events: [],
7218
+ slots: [
7219
+ {
7220
+ name: "default"
7221
+ }
7222
+ ],
7223
+ a11y: [
7224
+ "Choose `as` to convey the correct semantics (e.g. headings via h1\u2013h6); TText adds only visual styling."
7225
+ ],
7226
+ behavior: [
7227
+ "semantic_element_controlled_by_as: true",
7228
+ "token_driven_size_tone_weight: true"
7229
+ ],
7230
+ selection: {
7231
+ keywords: [],
7232
+ useWhen: [],
7233
+ avoidWhen: [],
7234
+ alternatives: []
7235
+ },
7236
+ setup: {
7237
+ required: [],
7238
+ notes: []
7239
+ },
7240
+ recipes: []
7241
+ },
6463
7242
  {
6464
7243
  name: "TTextarea",
6465
7244
  aliases: [],
@@ -6488,6 +7267,20 @@ var treeui_catalog_default = {
6488
7267
  "lg"
6489
7268
  ]
6490
7269
  },
7270
+ {
7271
+ name: "width",
7272
+ type: "TFieldWidth",
7273
+ default: "full",
7274
+ values: [
7275
+ "xs",
7276
+ "sm",
7277
+ "md",
7278
+ "lg",
7279
+ "xl",
7280
+ "full"
7281
+ ],
7282
+ note: "Inline-size cap (max-inline-size). Fluid by default; use instead of an inline width style."
7283
+ },
6491
7284
  {
6492
7285
  name: "disabled",
6493
7286
  type: "boolean",