Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
82d377e
feat: add Storybook component playground (apps/playground)
franco-chan Jul 11, 2026
ad23bc0
fix: default an editor for Storybook sidebar open-in-editor
franco-chan Jul 11, 2026
6ccf057
feat: add on-device Storybook shell for iOS (spike)
franco-chan Jul 11, 2026
6ffcb4d
fix: namespace Metro transform cache per storybook mode
franco-chan Jul 11, 2026
2bce9c4
feat: add Select/Checkbox/Switch/Dialog stories with portal-ready pre…
franco-chan Jul 11, 2026
c547e8f
feat: add Toast/Popover/ActionList/Radio/Alert/IconButton stories; dr…
franco-chan Jul 12, 2026
7dd00d6
feat: add Slider/TextArea/SegmentControl/Tooltip/Spinner/Skeleton sto…
franco-chan Jul 12, 2026
3ff404d
feat: add Empty/Progress/Divider/NumberSizeableText/QRCode/SearchBar …
franco-chan Jul 12, 2026
4eb6064
feat: add Form/OTPInput/ColorPicker/Accordion/Stepper/DescriptionList…
franco-chan Jul 12, 2026
704f486
feat: add DatePicker/Tabs/Pagination/Breadcrumb/UnorderedList/DashTex…
franco-chan Jul 12, 2026
5eb6186
feat: add Table/Carousel/SegmentSlider/InteractiveIcon/MatchSizeableT…
franco-chan Jul 12, 2026
ad2c36a
feat: add Icon/Image/SizeableText/Heading/Anchor/Label stories
franco-chan Jul 12, 2026
c27ccf3
feat: add ButtonGroup/Group/Illustration/GradientMask stories
franco-chan Jul 12, 2026
f73657d
feat: add ListView/SectionList/SwipeableCell/Swiper/SortableListView …
franco-chan Jul 12, 2026
a11c868
feat: add content sweep stories (Lottie/NetworkStatus/AdCorner/Gradie…
franco-chan Jul 12, 2026
3dc85fc
feat: add ConfirmHighlighter/InnerStroke/ListEndIndicator/SheetGrabbe…
franco-chan Jul 13, 2026
a5d0056
feat: add ScrollView/SortableSectionList stories
franco-chan Jul 13, 2026
fbe1284
feat: add Stack/Haptics/Trigger/Banner stories
franco-chan Jul 13, 2026
46cf844
docs: note batch 13-15 findings and exclusions in playground README
franco-chan Jul 13, 2026
135e29f
refactor: drop kit Gallery demos covered by CSF stories
franco-chan Jul 13, 2026
a7f9532
docs: note Gallery second cut in playground README
franco-chan Jul 13, 2026
7abd938
fix: import-group lint errors in Stepper/DescriptionList stories
franco-chan Jul 13, 2026
e3cda75
chore: merge origin/x (oxfmt migration, rspack v2, node integrity fix)
franco-chan Jul 17, 2026
138c973
style: apply oxfmt to generated storybook.requires
franco-chan Jul 17, 2026
69dd1ac
chore: merge origin/x (TypeScript 7.0.2, staged-check consolidation)
franco-chan Jul 20, 2026
f7206ab
fix: isolate on-device storybook entry from wallet bootstrap
franco-chan Jul 20, 2026
1ba08dc
Merge branch 'x' into claude/implementation-plan-12dc01
franco-chan Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "playground",
"runtimeExecutable": "yarn",
"runtimeArgs": [
"workspace",
"@onekeyhq/playground",
"sb:dev",
"--no-open",
"--ci"
],
"port": 6006
},
{
"name": "web",
"runtimeExecutable": "npx",
"runtimeArgs": [
"cross-env",
"NODE_OPTIONS=--max-old-space-size=10240",
"rspack",
"serve",
"--config",
"apps/web/rspack.config.ts"
],
"port": 3000
}
]
}
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ const resolveExtensions = (platform) =>

module.exports = {
root: true,
// Added by the Storybook upgrade CLI (eslintPlugin auto-migration): activates
// eslint-plugin-storybook's own overrides for *.stories.* files only.
extends: ['plugin:storybook/recommended'],
plugins: [
'import-path',
'use-effect-no-deps',
Expand Down
13 changes: 13 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,18 @@
"onekey/require-testid": "off"
}
},
{
// Component CSF stories + Storybook playground config — dev workbench, not user-facing UI
"files": [
"packages/components/src/**/*.stories.tsx",
"packages/components/src/**/*.stories.ts",
"apps/playground/.storybook/**/*.tsx",
"apps/playground/.storybook/**/*.ts"
],
"rules": {
"onekey/require-testid": "off"
}
},
{
// Non-UI packages — no interactive components, testID rule doesn't apply
"files": [
Expand Down Expand Up @@ -752,6 +764,7 @@
"packages/core/src/chains/xmr/sdkXmr/moneroCore/moneroCore.js",
"packages/shared/src/locale/enum/translations.ts",
"packages/shared/src/locale/localeJsonMap.ts",
"apps/mobile/.rnstorybook/storybook.requires.ts",
"packages/shared/src/locale/json/*",
"node_modules/**",
".history/**",
Expand Down
31 changes: 31 additions & 0 deletions apps/mobile/.rnstorybook/HyperlinkTextStub.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { SizableText } from '@onekeyhq/components/src/primitives/SizeableText';
import type { HyperlinkText } from '@onekeyhq/kit/src/components/HyperlinkText';

// ConfigProvider requires a HyperlinkText implementation (normally injected by
// kit at app bootstrap). Some components (e.g. Toast's RenderLines) call it
// with `translationId`/`defaultMessage` and NO children, so the stub falls
// back through that chain — story text is literal English, never a real
// translation key. Custom props are destructured off so only text-style props
// reach SizableText. The type-only kit import keeps the runtime graph free of
// kit.
export const HyperlinkTextStub: typeof HyperlinkText = ({
children,
translationId,
defaultMessage,
onAction,
messages,
values,
autoExecuteParsedAction,
urlTextProps,
actionTextProps,
underlineTextProps,
boldTextProps,
textProps,
subscriptsTextProps,
scoped,
...textStyleProps
}) => (
<SizableText {...textStyleProps}>
{children ?? defaultMessage ?? translationId}
</SizableText>
);
52 changes: 52 additions & 0 deletions apps/mobile/.rnstorybook/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useEffect } from 'react';

import { hideAsync } from 'expo-splash-screen';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

import BootRecovery from '@onekeyhq/shared/src/modules/BootRecovery';

// storybook.requires is generated by the withStorybook Metro wrapper on every
// STORYBOOK_ENABLED start; it's committed because its content is stable (story
// discovery happens via require.context, not an embedded file list).
import { view } from './storybook.requires';

const StorybookUIRoot = view.getStorybookUI({
// Deterministic cold launches for the spike; revisit for daily use.
shouldPersistSelection: false,
});

const rootStyle = { flex: 1 } as const;

function StorybookRoot() {
useEffect(() => {
// Some component modules call expo-splash-screen's preventAutoHideAsync at
// module scope (content/Splash, pulled in via the content barrel from e.g.
// Checkbox). The wallet hides the splash from its SplashProvider; this
// shell has no such provider, so dismiss it after first mount or the
// Storybook UI stays stuck behind the native launch screen.
void hideAsync().catch(() => undefined);

// The native watchdog (AppDelegate) counts every launch as failed until
// markBootSuccess resets it; the wallet does this in Bootstrap after a 5s
// stability window. Without mirroring it here, a few storybook relaunches
// in a row trip the "We hit a snag" recovery screen.
const timer = setTimeout(() => {
try {
BootRecovery.markBootSuccess();
} catch {
// Recovery bookkeeping must never crash the dev shell.
}
}, 5000);
return () => clearTimeout(timer);
}, []);
return (
// Gesture context for swipe-driven overlay components (backpackapp
// toasts, sheets). The wallet mounts its GestureHandlerRootView in kit's
// provider tree, which this shell doesn't use, so it owns one here.
<GestureHandlerRootView style={rootStyle}>
<StorybookUIRoot />
</GestureHandlerRootView>
);
}

export default StorybookRoot;
13 changes: 13 additions & 0 deletions apps/mobile/.rnstorybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { StorybookConfig } from '@storybook/react-native';

const main: StorybookConfig = {
// Same colocated CSF files the web playground (apps/playground) consumes —
// one story set, two shells.
stories: ['../../../packages/components/src/**/*.stories.@(ts|tsx)'],
deviceAddons: [
'@storybook/addon-ondevice-controls',
'@storybook/addon-ondevice-actions',
],
};

export default main;
53 changes: 53 additions & 0 deletions apps/mobile/.rnstorybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
ShowToastProvider,
Toaster,
} from '@onekeyhq/components/src/actions/Toast';
import { Portal } from '@onekeyhq/components/src/hocs/Portal';
import { ConfigProvider } from '@onekeyhq/components/src/hocs/Provider';
import { OverlayContainer } from '@onekeyhq/components/src/layouts/OverlayContainer';
import { Stack } from '@onekeyhq/components/src/primitives/Stack';

import { HyperlinkTextStub } from './HyperlinkTextStub';

import type { Preview } from '@storybook/react';

const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
decorators: [
(Story) => (
// Fixed theme/locale for the spike — the on-device UI has no toolbar
// globals like the web playground's; wire real switching in a build-out.
<ConfigProvider
theme="light"
locale="en-US"
HyperlinkText={HyperlinkTextStub}
>
<Stack bg="$bgApp" p="$5" flex={1}>
<Story />
</Stack>
{/* Overlay mount points for portal-based components — the minimal
slice of the app's FullWindowOverlayContainer, in the same order:
the FULL_WINDOW_OVERLAY portal (Dialog.show, Popover/Select
sheets), ShowToastProvider (Toast.show custom toasts), and
Toaster (Toast.success/error/… via backpackapp; it needs the
GestureHandlerRootView the shell root mounts). OverlayContainer
puts them on the iOS FullWindowOverlay layer, above the
Storybook UI. */}
<OverlayContainer>
<Portal.Container name={Portal.Constant.FULL_WINDOW_OVERLAY_PORTAL} />
<ShowToastProvider />
<Toaster />
</OverlayContainer>
</ConfigProvider>
),
],
};

export default preview;
63 changes: 63 additions & 0 deletions apps/mobile/.rnstorybook/storybook.requires.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* do not change this file, it is auto generated by storybook. */
/// <reference types="@storybook/react-native/metro-env" />
import {
start,
updateView,
type View,
type Features,
} from '@storybook/react-native';

import '@storybook/addon-ondevice-controls/register';
import '@storybook/addon-ondevice-actions/register';

const normalizedStories = [
{
titlePrefix: '',
directory: '../../packages/components/src',
files: '**/*.stories.@(ts|tsx)',
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(ts|tsx))$/,
req: require.context(
'../../../packages/components/src',
true,
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(ts|tsx))$/,
),
},
];

declare global {
var view: View;
var STORIES: typeof normalizedStories;
var STORYBOOK_WEBSOCKET:
| { host?: string; port?: number; secured?: boolean }
| undefined;
var FEATURES: Features;
}

const annotations = [
require('./preview'),
require('@storybook/react-native/preview'),
];

globalThis.STORIES = normalizedStories;
globalThis.STORYBOOK_WEBSOCKET = {
host: 'localhost',
port: 7007,
secured: false,
};

module?.hot?.accept?.();

const options = {};

if (!globalThis.view) {
globalThis.view = start({
annotations,
storyEntries: normalizedStories,
options,
});
} else {
updateView(globalThis.view, annotations, normalizedStories, options);
}

export const view: View = globalThis.view;
Loading
Loading