Repository navigation
Conversation
Add the Controls panel to @storybook/react-native, registered from start() when features.ondeviceControls is set. It uses the same RNCONTROLS addon id as @storybook/addon-ondevice-controls, so the built-in registration replaces the package's if both are present. @react-native-community/datetimepicker and @react-native-community/slider are optional peers. Each is loaded with a guarded require and only used when its native side is linked (RNDateTimePicker view on iOS, RNCDatePicker and RNCTimePicker modules on Android, RNCSlider view). Otherwise the date control falls back to validated YYYY-MM-DD and HH:mm text inputs, and range controls to the JS slider. react-native-modal-datetime-picker is replaced by a small modal around the native iOS picker and DateTimePickerAndroid.open on Android. Enable the flag in the expo example, and document it.
🦋 Changeset detectedLatest commit: c7f9ae0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…trols a stub Register the built-in Controls panel unless features.ondeviceControls is false. @storybook/addon-ondevice-controls becomes a no-op stub that keeps its exports and warns once in development; its duplicated source and dependencies are removed. getControlArgTypes and the addon constants are exported from @storybook/react-native/controls. Bring the iOS date picker sheet in line with react-native-modal-datetime-picker: take the value only when it opens, keep the mode while it closes, animate the backdrop and sheet, respect the bottom safe area and label the buttons. Guard against stacking Android dialogs. Read a story's args during the render that switches to it, and reset the form per story, so number inputs no longer start empty after a story change.
Submitting a YYYY-MM-DD or HH:mm fallback input also blurs it, and both committed the value, so the same updateStoryArgs was sent twice (seen on Android). Each field now remembers the text it last applied and skips re-applying it.
…controls Brings in Storybook 11.0.0-beta.0, Expo SDK 58 and React Native 0.88 (#958), glob 14 (#962), url-polyfill v4 (#953) and the FEATURES spread in generated requires (#945). - #958's controls type fixes (TextInputRef, the typed TextInputWithSwitcher and Input, the immutable Object error style, PanResponderInstance, the ComponentRef picker ref) now live in packages/react-native/src/controls, where the controls code moved. - packages/ondevice-controls/package.json keeps the stub's dependencies and peers. - packages/react-native/package.json takes next's versions plus this branch's controls dependencies; the datetimepicker and slider dev versions move to 9.2.1 and 5.2.1 to match Expo SDK 58. - pnpm-lock.yaml is next's lockfile reinstalled.
The controls type fixes now ship in @storybook/react-native, and the addon-ondevice-controls stub has none of that code.
Port the check/inline-check control (#972) and the number control fix (#965) to the built-in controls in packages/react-native/src/controls. Move the check control's PropField tests into @storybook/react-native, since PropField is no longer reachable from the addon-ondevice-controls stub, and retarget the check-control and number-control changesets to @storybook/react-native.
The stub has no DOM or node types, and after the Storybook 11.0.0-beta.3 update nothing pulls them in transitively, so tsc no longer finds console. Declare it the same way the actions stub does.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The on-device Controls addon is now built into
@storybook/react-nativeand on by default.@storybook/addon-ondevice-controlsbecomes a no-op compatibility stub.start()registers the built-in Controls panel unlessfeatures.ondeviceControlsisfalse(globalThis.FEATURES?.ondeviceControls !== false).Features.ondeviceControlsis documented as "on by default; setfalseto turn it off".packages/react-native/src/controls/, the single source of truth. It's lazily required fromstart(), right after backgrounds.@storybook/addon-ondevice-controlskeeps working for projects that still install it and list it indeviceAddons, but they get the panel from the built-in addon.register(andregister.js) registers nothing and doesn't touch the built-in panel. In__DEV__it logs oneconsole.warnsaying the addon is now built in, and that you can remove it fromdeviceAddonsand uninstall it.register,ADDON_IDandPARAM_KEY, re-exported from@storybook/react-native/controls.dist/controlArgTypes(getControlArgTypes,hasEnabledControl), kept because deep imports of it existed, e.g. our own example test.package.jsondrops@gorhom/portal,polished,tinycolor2andreact-native-modal-datetime-picker, and the datetimepicker, slider and bottom-sheet peers. It adds@storybook/react-native(^11.0.0-next.1) as a peer, and thecopyimagesscript is gone. The build still emitsdist/index.jsforregister.js.@storybook/react-native/controlsis a new, small subpath export:ADDON_ID,PARAM_KEY,getControlArgTypes,hasEnabledControland theArgType/ArgTypestypes. It builds todist/controls/index.jsplus a shared chunk, and doesn't pull in the panel UI.@react-native-community/datetimepicker(>=8) and@react-native-community/sliderare optional peers.requirewritten directly in atryblock, which is what Metro treats as optional.RNDateTimePickerview on iOS, theRNCDatePicker/RNCTimePickermodules on Android, and theRNCSliderview.YYYY-MM-DD/HH:mmtext inputs and range controls to the JS slider. There's a dev warning when a package is installed but not linked.@gorhom/bottom-sheetstays an optional require.@gorhom/portal,polishedandtinycolor2are regular dependencies. The colour-picker png ships indist/assets.Replacing
react-native-modal-datetime-pickerIt's replaced by
DateTimePickerAndroid.openon Android and a small sheet around the native picker on iOS (src/controls/components/DateTimePickerModal.tsx). Why: the package hard-imports the community datetimepicker outside a try block, so keeping it would break bundling for apps without the picker or force a third install.I compared ours against its v18 source (
DateTimePickerModal.ios.js,Modal.js,DateTimePickerModal.android.js) and fixed the gaps I found:datewhile the time sheet faded out.react-native-modal-datetime-pickerv18modeprop,displaydefaultspinnerdateandtimebuttons,display="spinner"onRequestClose) also cancelsModalstays mounted until the hide animation endsmarginBottom: 34on notched iPhonesSafeAreaInsetsContext(the Storybook UI provides it), with a minimum when there's no providerAppearance.getColorScheme())themeVariantandtextColorfrom it, so it matches the panel in light and darkgetDerivedStateFromProps)ModalModal, tested in both UIs<DateTimePicker>per mode and usesmemoto avoid double events (#54)DateTimePickerAndroid.openfor date, then time.dismissedis ignored and onlysetupdates the arg. A ref stops a second tap from stacking another dialog, and it's reset byonChange/onErroraccessibilityRole="button"and labels on Confirm/Cancelbuttonrole, a label ("<arg> date, 25-02-1983") and a hint.accessibilityViewIsModalis set on the sheetEmpty number field after switching stories
Switching stories over the websocket while the Controls panel was open could leave a number input empty (the Range story's
secondshowed blank instead of 7).This reproduces with the original standalone package on
next.ControlsPanel.tsx,hooks.ts,PropForm.tsx,PropField.tsx,types/Number.tsxanduseResyncValue.tswere byte-identical topackages/ondevice-controls/srconnextwhen I reproduced it on device.The cause isn't in
Number.tsx:useArgsre-read the new story's args in an effect, so the first render after a story change used the previous story's args.NumberTypethen mounted withuseState(undefined).useResyncValueonly resyncs whileisPristine, and that was already false from an edit on the previous story.The fix was small and clearly a bug, so it's in this PR:
useArgsreads the new story's args during the render that switches to it (state keyed bystoryId).isPristineis tracked per story.PropFormis keyed bystoryId.On device the same scenario now shows 7 straight away.
Examples, docs, migration
.rnstorybookdrops the package fromdeviceAddonsand doesn't set the flag, so this is the built-in-by-default path..rnstorybook-nofactoriesstill lists@storybook/addon-ondevice-controls, which exercises the stub. The package stays in expo-example'spackage.jsonfor it.DisabledControls.test.tsximports from@storybook/react-native/controls.storybook-addon-deep-controlsdoesn't touch controls internals, and its stories render as before.template/cli/main.ts,storybook.requires.ts) no longer installs or registers the package.configuration/controls.mdcovers on by default, turning it off, migrating from the package, and the optional native modules.falseto turn it off". There's no "will become default" wording for controls.addons/controls.mdandaddons/index.mdno longer tell you to install the package or its native peers.CLAUDE.md, the stories skill andreferences/controls.md.MIGRATION.md: a new v11 entry, "Controls are built in". It covers removing the package (or leaving it, since the stub is harmless), the datetimepicker and slider now being optional, droppingreact-native-modal-datetime-picker,ondeviceControls: false, and the@storybook/react-native/controlsimport path.next; breaking changes are recorded asmajor, likerequire-device-addons):@storybook/react-nativemajor, and@storybook/addon-ondevice-controlsmajor for the stub.pnpm-lock.yaml: only the importer changes, plus removal of the now-unusedreact-native-modal-datetime-pickerentries.pnpm install --frozen-lockfilepasses.Testing
pnpm build,pnpm test,pnpm lint,pnpm format:checkandcheck:typesfor@storybook/react-nativeand@storybook/addon-ondevice-controlsall pass. Test counts: react-native 105, generator 29, expo-example 32 passed with 2 skipped.builtInControls.test.ts, against the real manager-api:ondeviceControls: false;registertwice, thenstart(), leaves exactly one Controls panel and one warning.DateTimePickerModal.test.tsx:types/Date.test.tsx: the fallback date/time inputs apply a submitted value once, time keeps the date, and invalid input isn't applied.optionalNativeModules.test.tsanddateUtils.test.ts.iOS 26.4 simulator: expo-example dev build (SDK 57; datetimepicker 9.1.0 and slider 5.2.0 linked)
Built-in by default (
.rnstorybook, no package, no flag):EXPO_PUBLIC_LITE_UI=true, dark): the sheet opens over the portal-based panel and Confirm applies.secondshows 7 (the empty-field fix).Stub path (
.rnstorybook-nofactories, which still lists@storybook/addon-ondevice-controls):first→ 9, the story shows9 x 3 = 27).Android: temporary Android 15 emulator, Expo Go SDK 57 (bundles datetimepicker and slider)
updateStoryArgs. I logged this with a websocket listener on the channel server.Android: dev build on an Android 15 (API 35) emulator
expo-example dev build (
expo prebuild+gradlew app:assembleDebug, arm64; datetimepicker 9.1.0 and slider 5.2.0 linked) on a temporary Pixel 9 AVD (google_apisarm64 image). Metro was on 8081 and the channel server on 7007, with a websocket listener countingupdateStoryArgs. Screenshots are in~/Developer/test-evidence/android-960/.Native path (
.rnstorybook, no warnings):01–05): the date dialog opens. CANCEL sends 0 updates; picking the 10th and pressing OK sends exactly 1. The time dialog (text mode, 14:35): CANCEL sends 0, OK sends exactly 1 and keeps the date (1983-02-10T14:35). A double tap on the date or time button opens one dialog, and one CANCEL clears it. Dismissing with back resets the guard, so the next tap opens it again.06): switching Basic → Range with the panel open showssecond= 7. Tapping the native slider's track applies (first→ 37).08–11,17–21): all apply. Invalid object JSON shows the error style and sends no update.13–16, with Gboard): the sheet moves above the keyboard with no jump, and the focused text or number input stays visible. Typing applies (name→Storytellerxx,second→ 39).22–24): the panel and both native dialogs follow dark mode.EXPO_PUBLIC_LITE_UI=true,25–27): dragging the native slider applies (first→ 37), and the date dialog applies one update.Fallback path (temporarily patched
isDateTimePickerNativeAvailable/isSliderNativeAvailableindistto return false, restored afterwards):28–30):2024-13-40and25:99show the error border and send nothing.2024-07-15and09:45apply.updateStoryArgstwice. Submit committed the value, and the blur that follows the submit committed it again against the staledate. Each field now remembers the text it last applied. After the fix, one submit sends one update, and blur alone still sends one. A newtypes/Date.test.tsxcovers submit+blur, time-keeps-date and invalid input; it fails without the fix. This logic isn't Android-specific, so the iOS fallback should behave the same way.31/32): dragging inside the sheet applies (first→ 34).Stub path (
.rnstorybook-nofactories, lists@storybook/addon-ondevice-controls;33/34): exactly one Controls tab, the "no longer needed" warning logged once, and args are editable (second→ 8,6 x 8 = 48).Known issue, not fixed here (already on
next): in the full UI on Android, dragging the native@react-native-community/sliderinside the addons bottom sheet doesn't move it, though tapping the track works (07-native-slider-dragged.png). The same drag works on a native slider outside the sheet (the color picker modal,10-color-picker-sat.png), in the lite UI, and on the JS fallback slider inside the sheet. So the gorhomBottomSheetModalcontent pan gesture is probably taking the horizontal move from the native SeekBar.next's addon renders the same native slider in the same sheet, and this PR doesn't touchreact-native-ui. A possible follow-up isfailOffsetX/activeOffsetYon theBottomSheetModalinMobileAddonsPanel.Screenshots
The screenshots are saved locally on the dev machine in
~/Developer/test-evidence/builtin-controls-v2/and aren't attached here:02-light-date-sheet.png,06-dark-date-sheet.png,07-dark-time-sheet.png, and a side-by-side light/dark comparison insheets-light-dark.png03/04(arg changed while open, reopen),05-*(confirm recording and frames),09-lite-dark-date-sheet.png10/11(stub path)20–26(Android)There's no fork remote, so I didn't upload them anywhere public from the CLI. I can attach them if wanted.
Notes
fillsometimes cleared or typed only part of a short text field; retyping worked. This is a driver quirk, not a Storybook issue.