twico-ui 1.45.1 → 1.47.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/dist/index.d.cts CHANGED
@@ -1636,6 +1636,34 @@ interface ColorPickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "o
1636
1636
  label?: React.ReactNode;
1637
1637
  /** Helper text shown below when there is no error. */
1638
1638
  hint?: React.ReactNode;
1639
+ /**
1640
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
1641
+ * same height as one that has a `hint`, an `error` or a counter.
1642
+ *
1643
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
1644
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
1645
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
1646
+ * controls part company as soon as one label wraps at a narrow width.
1647
+ *
1648
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
1649
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
1650
+ * misalignment straight back:
1651
+ *
1652
+ * ```jsx
1653
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
1654
+ * <Select label="Filter" … />
1655
+ * <Select label="Comparison" hint="Matches the whole value." … />
1656
+ * <Input label="Value" … />
1657
+ * </Stack>
1658
+ * ```
1659
+ *
1660
+ * The lane is held open by generated content, so it matches the real footer by construction -
1661
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
1662
+ * It adds no text to the accessibility tree.
1663
+ *
1664
+ * @default false
1665
+ */
1666
+ reserveFooter?: boolean;
1639
1667
  /** Error message — turns the field red and replaces the hint. */
1640
1668
  error?: React.ReactNode;
1641
1669
  /** Marks the field required (adds an asterisk to the label). @default false */
@@ -1696,6 +1724,34 @@ interface OptionGroup {
1696
1724
  interface ComboboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "value" | "defaultValue" | "onChange"> {
1697
1725
  label?: React.ReactNode;
1698
1726
  hint?: React.ReactNode;
1727
+ /**
1728
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
1729
+ * same height as one that has a `hint`, an `error` or a counter.
1730
+ *
1731
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
1732
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
1733
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
1734
+ * controls part company as soon as one label wraps at a narrow width.
1735
+ *
1736
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
1737
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
1738
+ * misalignment straight back:
1739
+ *
1740
+ * ```jsx
1741
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
1742
+ * <Select label="Filter" … />
1743
+ * <Select label="Comparison" hint="Matches the whole value." … />
1744
+ * <Input label="Value" … />
1745
+ * </Stack>
1746
+ * ```
1747
+ *
1748
+ * The lane is held open by generated content, so it matches the real footer by construction -
1749
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
1750
+ * It adds no text to the accessibility tree.
1751
+ *
1752
+ * @default false
1753
+ */
1754
+ reserveFooter?: boolean;
1699
1755
  error?: React.ReactNode;
1700
1756
  required?: boolean;
1701
1757
  size?: "sm" | "md" | "lg";
@@ -1825,6 +1881,34 @@ interface CurrencyProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>
1825
1881
  label?: React.ReactNode;
1826
1882
  /** Helper text below the field; linked to the input via aria-describedby. */
1827
1883
  hint?: React.ReactNode;
1884
+ /**
1885
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
1886
+ * same height as one that has a `hint`, an `error` or a counter.
1887
+ *
1888
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
1889
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
1890
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
1891
+ * controls part company as soon as one label wraps at a narrow width.
1892
+ *
1893
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
1894
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
1895
+ * misalignment straight back:
1896
+ *
1897
+ * ```jsx
1898
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
1899
+ * <Select label="Filter" … />
1900
+ * <Select label="Comparison" hint="Matches the whole value." … />
1901
+ * <Input label="Value" … />
1902
+ * </Stack>
1903
+ * ```
1904
+ *
1905
+ * The lane is held open by generated content, so it matches the real footer by construction -
1906
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
1907
+ * It adds no text to the accessibility tree.
1908
+ *
1909
+ * @default false
1910
+ */
1911
+ reserveFooter?: boolean;
1828
1912
  /** Error text below the field; sets aria-invalid and is linked via aria-describedby (takes precedence over hint). */
1829
1913
  error?: React.ReactNode;
1830
1914
  /** Intercepted to render the label asterisk, and forwarded to the native input as `required`. @default false */
@@ -1865,6 +1949,34 @@ interface CurrencyFieldProps extends Omit<React.InputHTMLAttributes<HTMLInputEle
1865
1949
  label?: React.ReactNode;
1866
1950
  /** Helper text below the field; linked to the input via aria-describedby. */
1867
1951
  hint?: React.ReactNode;
1952
+ /**
1953
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
1954
+ * same height as one that has a `hint`, an `error` or a counter.
1955
+ *
1956
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
1957
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
1958
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
1959
+ * controls part company as soon as one label wraps at a narrow width.
1960
+ *
1961
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
1962
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
1963
+ * misalignment straight back:
1964
+ *
1965
+ * ```jsx
1966
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
1967
+ * <Select label="Filter" … />
1968
+ * <Select label="Comparison" hint="Matches the whole value." … />
1969
+ * <Input label="Value" … />
1970
+ * </Stack>
1971
+ * ```
1972
+ *
1973
+ * The lane is held open by generated content, so it matches the real footer by construction -
1974
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
1975
+ * It adds no text to the accessibility tree.
1976
+ *
1977
+ * @default false
1978
+ */
1979
+ reserveFooter?: boolean;
1868
1980
  /** Error text below the field; sets aria-invalid and is linked via aria-describedby (takes precedence over hint). */
1869
1981
  error?: React.ReactNode;
1870
1982
  /** Intercepted to render the label asterisk, and forwarded to the native input as `required`. @default false */
@@ -2087,6 +2199,26 @@ interface DatatableProps<T = any> extends Omit<React.HTMLAttributes<HTMLDivEleme
2087
2199
  * full row objects server-side rather than relying on `selectedRows`.
2088
2200
  */
2089
2201
  batchActions?: DatatableBatchAction<T>[];
2202
+ /**
2203
+ * #492: extra node in the **selection** toolbar, rendered beside the "N selected" count and before
2204
+ * the batch actions — for the number the count does not carry.
2205
+ *
2206
+ * The count is not always what decides anything. A batch capped by total **size** refuses on
2207
+ * gigabytes, not on items: "12 selected" is not the number the user needs, "12 selected · 2.4 GB"
2208
+ * is. Before this there was nowhere to put it, so hosts lifted it into the main toolbar behind
2209
+ * their own `selected.length > 0` guard — putting it in a different row from the count it
2210
+ * qualifies, and re-deriving a selection this component already tracks.
2211
+ *
2212
+ * Prefer the **function form**: it receives the same `(keys, rows)` the batch actions get, so the
2213
+ * host supplies one pure function and keeps no selection state of its own. (With `retainSelectedRows`
2214
+ * the off-page rows resolve too; without it only the rows currently rendered do.) Pass a plain
2215
+ * node when the summary does not depend on the selection.
2216
+ *
2217
+ * Complements `disabledReason` (#479), which explains the refusal once a cap is exceeded: this is
2218
+ * the running total *before* that point, which is what lets someone stop at the right number
2219
+ * instead of discovering the cap by hitting it.
2220
+ */
2221
+ selectionSummary?: React.ReactNode | ((keys: Array<string | number>, rows: T[]) => React.ReactNode);
2090
2222
  /** #322: fired when the checkbox row selection changes — `(keys, rows)`. The row-mode analogue of
2091
2223
  * `onCellSelectionChange`, so a consumer can track the selection and drive a batch action's own
2092
2224
  * `disabled`/`hidden` (which also accept predicates). */
@@ -2879,6 +3011,26 @@ interface CardGridProps<Row = any> extends Omit<React.HTMLAttributes<HTMLDivElem
2879
3011
  * `ctx.anchorEl` so an action can anchor a popover to its own button.
2880
3012
  */
2881
3013
  batchActions?: CardGridBatchAction<Row>[];
3014
+ /**
3015
+ * #492: extra node in the **selection** toolbar, rendered beside the "N selected" count and before
3016
+ * the batch actions — for the number the count does not carry.
3017
+ *
3018
+ * The count is not always what decides anything. A batch capped by total **size** refuses on
3019
+ * gigabytes, not on items: "12 selected" is not the number the user needs, "12 selected · 2.4 GB"
3020
+ * is. Before this there was nowhere to put it, so hosts lifted it into the main toolbar behind
3021
+ * their own `selected.length > 0` guard — putting it in a different row from the count it
3022
+ * qualifies, and re-deriving a selection this component already tracks.
3023
+ *
3024
+ * Prefer the **function form**: it receives the same `(keys, rows)` the batch actions get, so the
3025
+ * host supplies one pure function and keeps no selection state of its own. (With `retainSelectedRows`
3026
+ * the off-page rows resolve too; without it only the rows currently rendered do.) Pass a plain
3027
+ * node when the summary does not depend on the selection.
3028
+ *
3029
+ * Complements `disabledReason` (#479), which explains the refusal once a cap is exceeded: this is
3030
+ * the running total *before* that point, which is what lets someone stop at the right number
3031
+ * instead of discovering the cap by hitting it.
3032
+ */
3033
+ selectionSummary?: React.ReactNode | ((keys: Array<string | number>, rows: Row[]) => React.ReactNode);
2882
3034
  /** Fired when the selection changes — `(keys, rows)`, matching `Datatable.onRowSelectionChange`. */
2883
3035
  onRowSelectionChange?: (keys: Array<string | number>, rows: Row[]) => void;
2884
3036
  /**
@@ -3013,6 +3165,34 @@ interface DatePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "on
3013
3165
  label?: React.ReactNode;
3014
3166
  /** Helper text shown below when there is no error. */
3015
3167
  hint?: React.ReactNode;
3168
+ /**
3169
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
3170
+ * same height as one that has a `hint`, an `error` or a counter.
3171
+ *
3172
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
3173
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
3174
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
3175
+ * controls part company as soon as one label wraps at a narrow width.
3176
+ *
3177
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
3178
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
3179
+ * misalignment straight back:
3180
+ *
3181
+ * ```jsx
3182
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
3183
+ * <Select label="Filter" … />
3184
+ * <Select label="Comparison" hint="Matches the whole value." … />
3185
+ * <Input label="Value" … />
3186
+ * </Stack>
3187
+ * ```
3188
+ *
3189
+ * The lane is held open by generated content, so it matches the real footer by construction -
3190
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
3191
+ * It adds no text to the accessibility tree.
3192
+ *
3193
+ * @default false
3194
+ */
3195
+ reserveFooter?: boolean;
3016
3196
  /** Error message — turns the field red and replaces the hint. */
3017
3197
  error?: React.ReactNode;
3018
3198
  /** Marks the field required (adds an asterisk to the label). @default false */
@@ -3066,6 +3246,34 @@ interface DateRangePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>
3066
3246
  label?: React.ReactNode;
3067
3247
  /** Helper text shown below when there is no error. */
3068
3248
  hint?: React.ReactNode;
3249
+ /**
3250
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
3251
+ * same height as one that has a `hint`, an `error` or a counter.
3252
+ *
3253
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
3254
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
3255
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
3256
+ * controls part company as soon as one label wraps at a narrow width.
3257
+ *
3258
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
3259
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
3260
+ * misalignment straight back:
3261
+ *
3262
+ * ```jsx
3263
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
3264
+ * <Select label="Filter" … />
3265
+ * <Select label="Comparison" hint="Matches the whole value." … />
3266
+ * <Input label="Value" … />
3267
+ * </Stack>
3268
+ * ```
3269
+ *
3270
+ * The lane is held open by generated content, so it matches the real footer by construction -
3271
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
3272
+ * It adds no text to the accessibility tree.
3273
+ *
3274
+ * @default false
3275
+ */
3276
+ reserveFooter?: boolean;
3069
3277
  /** Error message — turns the field red and replaces the hint. */
3070
3278
  error?: React.ReactNode;
3071
3279
  /** Marks the field required (adds an asterisk to the label). @default false */
@@ -3123,6 +3331,34 @@ interface TimePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "on
3123
3331
  label?: React.ReactNode;
3124
3332
  /** Helper text shown below when there is no error. */
3125
3333
  hint?: React.ReactNode;
3334
+ /**
3335
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
3336
+ * same height as one that has a `hint`, an `error` or a counter.
3337
+ *
3338
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
3339
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
3340
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
3341
+ * controls part company as soon as one label wraps at a narrow width.
3342
+ *
3343
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
3344
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
3345
+ * misalignment straight back:
3346
+ *
3347
+ * ```jsx
3348
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
3349
+ * <Select label="Filter" … />
3350
+ * <Select label="Comparison" hint="Matches the whole value." … />
3351
+ * <Input label="Value" … />
3352
+ * </Stack>
3353
+ * ```
3354
+ *
3355
+ * The lane is held open by generated content, so it matches the real footer by construction -
3356
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
3357
+ * It adds no text to the accessibility tree.
3358
+ *
3359
+ * @default false
3360
+ */
3361
+ reserveFooter?: boolean;
3126
3362
  /** Error message — turns the field red and replaces the hint. */
3127
3363
  error?: React.ReactNode;
3128
3364
  /** Marks the field required (adds an asterisk to the label). @default false */
@@ -3178,6 +3414,34 @@ interface DateTimePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
3178
3414
  label?: React.ReactNode;
3179
3415
  /** Helper text shown below when there is no error. */
3180
3416
  hint?: React.ReactNode;
3417
+ /**
3418
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
3419
+ * same height as one that has a `hint`, an `error` or a counter.
3420
+ *
3421
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
3422
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
3423
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
3424
+ * controls part company as soon as one label wraps at a narrow width.
3425
+ *
3426
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
3427
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
3428
+ * misalignment straight back:
3429
+ *
3430
+ * ```jsx
3431
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
3432
+ * <Select label="Filter" … />
3433
+ * <Select label="Comparison" hint="Matches the whole value." … />
3434
+ * <Input label="Value" … />
3435
+ * </Stack>
3436
+ * ```
3437
+ *
3438
+ * The lane is held open by generated content, so it matches the real footer by construction -
3439
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
3440
+ * It adds no text to the accessibility tree.
3441
+ *
3442
+ * @default false
3443
+ */
3444
+ reserveFooter?: boolean;
3181
3445
  /** Error message — recolors both controls and replaces the hint. */
3182
3446
  error?: React.ReactNode;
3183
3447
  /** Marks the field required (adds an asterisk to the label). @default false */
@@ -3235,6 +3499,34 @@ declare function DateTimePicker(props: DateTimePickerProps): React.JSX.Element;
3235
3499
  interface MultiSelectProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "value" | "defaultValue" | "onChange"> {
3236
3500
  label?: React.ReactNode;
3237
3501
  hint?: React.ReactNode;
3502
+ /**
3503
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
3504
+ * same height as one that has a `hint`, an `error` or a counter.
3505
+ *
3506
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
3507
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
3508
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
3509
+ * controls part company as soon as one label wraps at a narrow width.
3510
+ *
3511
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
3512
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
3513
+ * misalignment straight back:
3514
+ *
3515
+ * ```jsx
3516
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
3517
+ * <Select label="Filter" … />
3518
+ * <Select label="Comparison" hint="Matches the whole value." … />
3519
+ * <Input label="Value" … />
3520
+ * </Stack>
3521
+ * ```
3522
+ *
3523
+ * The lane is held open by generated content, so it matches the real footer by construction -
3524
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
3525
+ * It adds no text to the accessibility tree.
3526
+ *
3527
+ * @default false
3528
+ */
3529
+ reserveFooter?: boolean;
3238
3530
  error?: React.ReactNode;
3239
3531
  required?: boolean;
3240
3532
  /** Control height preset, matching Select/Combobox. @default "md" */
@@ -3500,6 +3792,34 @@ interface FieldProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "id"> {
3500
3792
  label?: React.ReactNode;
3501
3793
  /** Helper text shown below when there is no error. */
3502
3794
  hint?: React.ReactNode;
3795
+ /**
3796
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
3797
+ * same height as one that has a `hint`, an `error` or a counter.
3798
+ *
3799
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
3800
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
3801
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
3802
+ * controls part company as soon as one label wraps at a narrow width.
3803
+ *
3804
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
3805
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
3806
+ * misalignment straight back:
3807
+ *
3808
+ * ```jsx
3809
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
3810
+ * <Select label="Filter" … />
3811
+ * <Select label="Comparison" hint="Matches the whole value." … />
3812
+ * <Input label="Value" … />
3813
+ * </Stack>
3814
+ * ```
3815
+ *
3816
+ * The lane is held open by generated content, so it matches the real footer by construction -
3817
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
3818
+ * It adds no text to the accessibility tree.
3819
+ *
3820
+ * @default false
3821
+ */
3822
+ reserveFooter?: boolean;
3503
3823
  /** Error message — replaces the hint and renders in the danger color. */
3504
3824
  error?: React.ReactNode;
3505
3825
  /** Marks the field required (adds an asterisk after the label). @default false */
@@ -3688,6 +4008,34 @@ interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "
3688
4008
  label?: React.ReactNode;
3689
4009
  /** Helper text shown below when there is no error. */
3690
4010
  hint?: React.ReactNode;
4011
+ /**
4012
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
4013
+ * same height as one that has a `hint`, an `error` or a counter.
4014
+ *
4015
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
4016
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
4017
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
4018
+ * controls part company as soon as one label wraps at a narrow width.
4019
+ *
4020
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
4021
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
4022
+ * misalignment straight back:
4023
+ *
4024
+ * ```jsx
4025
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
4026
+ * <Select label="Filter" … />
4027
+ * <Select label="Comparison" hint="Matches the whole value." … />
4028
+ * <Input label="Value" … />
4029
+ * </Stack>
4030
+ * ```
4031
+ *
4032
+ * The lane is held open by generated content, so it matches the real footer by construction -
4033
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
4034
+ * It adds no text to the accessibility tree.
4035
+ *
4036
+ * @default false
4037
+ */
4038
+ reserveFooter?: boolean;
3691
4039
  /** Error message — turns the field red and replaces the hint. */
3692
4040
  error?: React.ReactNode;
3693
4041
  /** Marks the field required (adds an asterisk and forwards to the native input). */
@@ -3996,6 +4344,34 @@ interface RadioGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "on
3996
4344
  label?: React.ReactNode;
3997
4345
  /** Helper text below the group. */
3998
4346
  hint?: React.ReactNode;
4347
+ /**
4348
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
4349
+ * same height as one that has a `hint`, an `error` or a counter.
4350
+ *
4351
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
4352
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
4353
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
4354
+ * controls part company as soon as one label wraps at a narrow width.
4355
+ *
4356
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
4357
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
4358
+ * misalignment straight back:
4359
+ *
4360
+ * ```jsx
4361
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
4362
+ * <Select label="Filter" … />
4363
+ * <Select label="Comparison" hint="Matches the whole value." … />
4364
+ * <Input label="Value" … />
4365
+ * </Stack>
4366
+ * ```
4367
+ *
4368
+ * The lane is held open by generated content, so it matches the real footer by construction -
4369
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
4370
+ * It adds no text to the accessibility tree.
4371
+ *
4372
+ * @default false
4373
+ */
4374
+ reserveFooter?: boolean;
3999
4375
  /** Error text below the group; sets `aria-invalid` and is announced once for the whole group. */
4000
4376
  error?: React.ReactNode;
4001
4377
  /** Lay the radios in a column or a wrapping row. @default "vertical" */
@@ -4058,6 +4434,34 @@ declare function Rating(props: RatingProps): React.JSX.Element;
4058
4434
  interface SelectProps extends Omit<React.HTMLAttributes<HTMLButtonElement>, "onChange" | "defaultValue"> {
4059
4435
  label?: React.ReactNode;
4060
4436
  hint?: React.ReactNode;
4437
+ /**
4438
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
4439
+ * same height as one that has a `hint`, an `error` or a counter.
4440
+ *
4441
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
4442
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
4443
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
4444
+ * controls part company as soon as one label wraps at a narrow width.
4445
+ *
4446
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
4447
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
4448
+ * misalignment straight back:
4449
+ *
4450
+ * ```jsx
4451
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
4452
+ * <Select label="Filter" … />
4453
+ * <Select label="Comparison" hint="Matches the whole value." … />
4454
+ * <Input label="Value" … />
4455
+ * </Stack>
4456
+ * ```
4457
+ *
4458
+ * The lane is held open by generated content, so it matches the real footer by construction -
4459
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
4460
+ * It adds no text to the accessibility tree.
4461
+ *
4462
+ * @default false
4463
+ */
4464
+ reserveFooter?: boolean;
4061
4465
  error?: React.ReactNode;
4062
4466
  required?: boolean;
4063
4467
  size?: "sm" | "md" | "lg";
@@ -4495,6 +4899,34 @@ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement
4495
4899
  label?: React.ReactNode;
4496
4900
  /** Helper text shown below when there is no error. */
4497
4901
  hint?: React.ReactNode;
4902
+ /**
4903
+ * #494: always render the footer lane, empty when there is nothing to say, so this field is the
4904
+ * same height as one that has a `hint`, an `error` or a counter.
4905
+ *
4906
+ * A row of fields aligned with `align="flex-end"` splits onto two lines the moment ONE of them
4907
+ * has a hint: that field grows a footer, the others do not, and the caption ends up moving a
4908
+ * *different* control. `align` cannot fix it - `flex-start` lines up the labels instead, so the
4909
+ * controls part company as soon as one label wraps at a narrow width.
4910
+ *
4911
+ * Set it per field, or once on any ancestor for everything inside, which is the safer habit in a
4912
+ * row - with a per-field prop alone, adding a field later and forgetting it brings the
4913
+ * misalignment straight back:
4914
+ *
4915
+ * ```jsx
4916
+ * <Stack direction="row" align="flex-end" data-reserve-footer>
4917
+ * <Select label="Filter" … />
4918
+ * <Select label="Comparison" hint="Matches the whole value." … />
4919
+ * <Input label="Value" … />
4920
+ * </Stack>
4921
+ * ```
4922
+ *
4923
+ * The lane is held open by generated content, so it matches the real footer by construction -
4924
+ * same font metrics, same flex gap - rather than by a height that drifts when a token changes.
4925
+ * It adds no text to the accessibility tree.
4926
+ *
4927
+ * @default false
4928
+ */
4929
+ reserveFooter?: boolean;
4498
4930
  /** Error message — turns the field red and replaces the hint. */
4499
4931
  error?: React.ReactNode;
4500
4932
  /** Marks the field required (adds an asterisk and forwards to the native textarea). */