@mittwald/flow-react-components 1.2.0-next.1 → 1.2.0-next.3

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/MIGRATION.md CHANGED
@@ -99,8 +99,16 @@ tracked the selection both go away.
99
99
  ```
100
100
 
101
101
  **Setting a value.** The selection feeds a form or a setting. Use a
102
- `RadioGroup`: each `Segment` becomes a `Radio`, everything else — `Label`,
103
- `FieldDescription`, `FieldError`, the React Hook Form binding — stays as it is.
102
+ `RadioGroup`: each `Segment` becomes a `RadioButton`, everything else — `Label`,
103
+ `FieldDescription`, `FieldError`, `value`/`defaultValue`/`onChange`, the React
104
+ Hook Form binding — stays as it is.
105
+
106
+ This direction is a rename, not a restructure. The types line up:
107
+ `SegmentedControlProps` and `RadioGroupProps` both extend
108
+ `Omit<Aria.RadioGroupProps, "children">`, and `SegmentProps` and
109
+ `RadioButtonProps` are the same type (`RadioButtonProps` is declared as
110
+ `RadioProps`, which is identical to `SegmentProps`). Only
111
+ `containerBreakpointSize` has no counterpart — see the note at the end.
104
112
 
105
113
  ```diff
106
114
  - <SegmentedControl defaultValue="debit">
@@ -108,15 +116,56 @@ tracked the selection both go away.
108
116
  <Label>Zahlungsart</Label>
109
117
  - <Segment value="debit">Lastschrift</Segment>
110
118
  - <Segment value="invoice">Rechnung</Segment>
111
- + <Radio value="debit">Lastschrift</Radio>
112
- + <Radio value="invoice">Rechnung</Radio>
119
+ + <RadioButton value="debit">Lastschrift</RadioButton>
120
+ + <RadioButton value="invoice">Rechnung</RadioButton>
113
121
  <FieldDescription>Jederzeit änderbar</FieldDescription>
114
122
  - </SegmentedControl>
115
123
  + </RadioGroup>
116
124
  ```
117
125
 
118
- There is no codemod. Both replacements change the structure, and picking the
119
- right one is a decision per usage, not a rename.
126
+ **Always `RadioButton`, not `Radio`.** A `RadioGroup` also accepts a plain
127
+ `Radio`, and it takes the same props, so both would compile — but `RadioButton`
128
+ is the replacement for a `Segment`. The appearance still changes: `RadioButton`
129
+ does not reproduce the segmented control's joined row, which came from
130
+ `Segment`'s own `flex: 1` and collapsed borders. See the
131
+ [RadioGroup docs](https://flow.mittwald.de/components/form-controls/radio-group).
132
+
133
+ #### Three things the Tabs direction changes
134
+
135
+ **The controlled-state props are named differently.** `SegmentedControl` takes
136
+ `value` / `defaultValue` / `onChange` (it is a radio group); `Tabs` takes
137
+ `selectedKey` / `defaultSelectedKey` / `onSelectionChange`. Nothing carries over
138
+ by name here, unlike the `RadioGroup` direction.
139
+
140
+ **The group `Label`.** `SegmentedControl` is a form field — it wraps
141
+ react-aria's `RadioGroup` — so a `<Label>` inside it is a field label. `Tabs`
142
+ has no equivalent slot, and a tab list labels itself through its tab titles. The
143
+ `Label` therefore cannot move into `Tabs`, but its text has somewhere to go:
144
+
145
+ - into the surrounding `Section`'s `Heading`, when the group wants a visible
146
+ title
147
+ - into `aria-label` on `Tabs`, when it does not — that names the tab list for
148
+ screen readers without showing the name
149
+ - nowhere, when the surrounding heading already names the group
150
+
151
+ A `Label` on its own is **not** a signal that the usage was a `RadioGroup` case
152
+ — plenty of content switchers were authored with one, because the component
153
+ required a field label.
154
+
155
+ **Panels that register form fields.** Moving the switched-in content into a
156
+ `Tab` does not change when it mounts. `Tab` renders its panel with
157
+ `shouldForceMount` and wraps `Content` and `Section` in
158
+ `<Activity isActive={…}>`, so every tab's subtree is mounted and keeps its state
159
+ — an inactive tab is hidden, not unmounted. React Hook Form fields inside a tab
160
+ stay registered across a tab switch, exactly as they did as siblings below a
161
+ `SegmentedControl`.
162
+
163
+ There is no codemod, and the reason is the choice rather than the edit. Which
164
+ replacement is right cannot be decided from the source: a value-setting usage
165
+ and a content switcher look alike at the call site, and only the surrounding
166
+ intention separates them. The `RadioGroup` direction would be mechanical **once
167
+ that decision is made** — if you have many usages that all go that way, a
168
+ find-and-replace of `SegmentedControl`/`Segment` gets you most of it.
120
169
 
121
170
  `SegmentedControl` and `Segment` (and the `flr-segmented-control` /
122
171
  `flr-segment` remote elements) keep working unchanged and will be removed in a
@@ -128,8 +177,18 @@ their own when the available width runs out, and a `RadioGroup` stacks its
128
177
  options anyway.
129
178
 
130
179
  **Apply:** Replace `SegmentedControl` with `Tabs` when the selection switches
131
- displayed content, or with `RadioGroup` when it sets a value. Pick per usage —
132
- this is a structural change, not a rename.
180
+ displayed content, or with `RadioGroup` when it sets a value. Pick per usage.
181
+ The two directions cost very different amounts of work. Towards `RadioGroup` it
182
+ is a prop-compatible rename: `SegmentedControl` → `RadioGroup` and `Segment` →
183
+ `RadioButton` (always `RadioButton`, not `Radio`; it takes exactly `Segment`'s
184
+ props), with `value`/`defaultValue`/`onChange` and a `Label` child all carrying
185
+ over. Only `containerBreakpointSize` has no counterpart, and the joined row is
186
+ not reproduced. Towards `Tabs` it is structural: the state props are
187
+ `selectedKey`/`defaultSelectedKey` rather than `value`/`defaultValue`, there is
188
+ no `Label` slot (the group label moves to the surrounding `Heading`, or to
189
+ `aria-label` on `Tabs` when it should not be visible, or goes away), and the
190
+ switched panels move inside the tabs — where they stay mounted, so form fields
191
+ in them keep their registration.
133
192
 
134
193
  ---
135
194
 
@@ -774,9 +833,41 @@ prop. At runtime the old prop still works: `Action` maps it to `onAction` and
774
833
  logs a deprecation warning, and an explicit `onAction` wins. The fallback will
775
834
  be removed in a future major version.
776
835
 
777
- A codemod renames the prop on `Action`.
836
+ #### The type changed too
837
+
838
+ `onAction` is typed `ActionFn`, which is `(...args: unknown[]) => unknown`. A
839
+ function that declares a parameter of any narrower type is not assignable to it,
840
+ because the parameter would receive `unknown`:
778
841
 
779
- **Apply:** Rename the `action` prop on `Action` to `onAction`.
842
+ ```
843
+ Type '(options?: CloseOptions | undefined) => void' is not assignable to type 'ActionFn'.
844
+ Types of parameters 'options' and 'args' are incompatible.
845
+ Type 'unknown' is not assignable to type 'CloseOptions | undefined'.
846
+ ```
847
+
848
+ Wrap the call instead of passing the reference:
849
+
850
+ ```diff
851
+ - <Action onAction={controller.close}>
852
+ + <Action onAction={() => controller.close()}>
853
+ ```
854
+
855
+ Only function **references** are affected. An inline arrow
856
+ (`onAction={() => …}`), a zero-parameter function, and anything already
857
+ accepting `unknown` are all fine.
858
+
859
+ A codemod renames the prop. It deliberately does not wrap: whether the
860
+ referenced function declares a parameter cannot be decided from the source —
861
+ that needs type information — and wrapping everything would silently drop the
862
+ arguments `Action` passes to handlers that do accept them.
863
+
864
+ **Apply:** Rename the `action` prop on `Action` to `onAction`. Not only a
865
+ rename: the new prop is typed `ActionFn` (`(...args: unknown[]) => unknown`), so
866
+ a function _reference_ that declares a parameter no longer type-checks and needs
867
+ wrapping — `onAction={() => controller.close()}` rather than
868
+ `onAction={controller.close}`. Check every site where you passed a reference
869
+ rather than an inline arrow; the codemod renames the prop but cannot decide this
870
+ one from the source.
780
871
 
781
872
  ```shell
782
873
  npx @mittwald/flow-codemods@latest action-prop-to-on-action src