@storybook/react-native-ui 8.0.0-alpha.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/LICENSE +21 -0
- package/dist/index.d.ts +492 -0
- package/dist/index.js +2860 -0
- package/package.json +85 -0
- package/src/Button.stories.tsx +134 -0
- package/src/Button.tsx +243 -0
- package/src/Explorer.stories.tsx +46 -0
- package/src/Explorer.tsx +54 -0
- package/src/IconButton.tsx +11 -0
- package/src/Layout.stories.tsx +38 -0
- package/src/Layout.tsx +103 -0
- package/src/LayoutProvider.tsx +90 -0
- package/src/MobileAddonsPanel.tsx +166 -0
- package/src/MobileMenuDrawer.tsx +75 -0
- package/src/Refs.tsx +142 -0
- package/src/Search.tsx +336 -0
- package/src/SearchResults.tsx +315 -0
- package/src/Sidebar.stories.tsx +262 -0
- package/src/Sidebar.tsx +200 -0
- package/src/Tree.stories.tsx +139 -0
- package/src/Tree.tsx +441 -0
- package/src/TreeNode.stories.tsx +122 -0
- package/src/TreeNode.tsx +146 -0
- package/src/constants.ts +4 -0
- package/src/icon/BottomBarToggleIcon.tsx +23 -0
- package/src/icon/CloseIcon.tsx +22 -0
- package/src/icon/CollapseAllIcon.tsx +17 -0
- package/src/icon/CollapseIcon.tsx +39 -0
- package/src/icon/ComponentIcon.tsx +14 -0
- package/src/icon/ExpandAllIcon.tsx +17 -0
- package/src/icon/FaceHappyIcon.tsx +18 -0
- package/src/icon/GroupIcon.tsx +14 -0
- package/src/icon/MenuIcon.tsx +18 -0
- package/src/icon/SearchIcon.tsx +17 -0
- package/src/icon/StoryIcon.tsx +14 -0
- package/src/index.tsx +9 -0
- package/src/mockdata.large.ts +25217 -0
- package/src/mockdata.ts +287 -0
- package/src/types.ts +78 -0
- package/src/useExpanded.ts +130 -0
- package/src/useLastViewed.ts +48 -0
- package/src/util/status.tsx +70 -0
- package/src/util/tree.ts +91 -0
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@storybook/react-native-ui",
|
|
3
|
+
"version": "8.0.0-alpha.3",
|
|
4
|
+
"description": "ui components for react native storybook",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"react-native",
|
|
8
|
+
"storybook"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://storybook.js.org/",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/storybookjs/react-native/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/storybookjs/react-native.git",
|
|
17
|
+
"directory": "packages/react-native-ui"
|
|
18
|
+
},
|
|
19
|
+
"react-native": "src/index.tsx",
|
|
20
|
+
"main": "dist/index.js",
|
|
21
|
+
"types": "src/index.tsx",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/**/*",
|
|
25
|
+
"README.md",
|
|
26
|
+
"*.js",
|
|
27
|
+
"*.d.ts",
|
|
28
|
+
"src/**/*"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "tsup --watch",
|
|
32
|
+
"prepare": "tsup",
|
|
33
|
+
"test": "jest --passWithNoTests",
|
|
34
|
+
"test:ci": "jest"
|
|
35
|
+
},
|
|
36
|
+
"jest": {
|
|
37
|
+
"modulePathIgnorePatterns": [
|
|
38
|
+
"dist/"
|
|
39
|
+
],
|
|
40
|
+
"moduleFileExtensions": [
|
|
41
|
+
"ts",
|
|
42
|
+
"tsx",
|
|
43
|
+
"js",
|
|
44
|
+
"jsx",
|
|
45
|
+
"json",
|
|
46
|
+
"node"
|
|
47
|
+
],
|
|
48
|
+
"preset": "react-native"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@storybook/react": "^8",
|
|
52
|
+
"@storybook/types": "^8",
|
|
53
|
+
"@types/jest": "^29.4.3",
|
|
54
|
+
"@types/react": "~18.2.14",
|
|
55
|
+
"babel-jest": "^29.4.3",
|
|
56
|
+
"jest": "^29.7.0",
|
|
57
|
+
"react-test-renderer": "18.2.0",
|
|
58
|
+
"tsup": "^7.2.0",
|
|
59
|
+
"typescript": "^5.3.3"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@storybook/core-events": "^8",
|
|
63
|
+
"@storybook/manager-api": "^8",
|
|
64
|
+
"@storybook/react-native-theming": "^8.0.0-alpha.3",
|
|
65
|
+
"fuse.js": "^7.0.0",
|
|
66
|
+
"memoizerific": "^1.11.3",
|
|
67
|
+
"polished": "^4.3.1"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@gorhom/bottom-sheet": ">=4",
|
|
71
|
+
"react": "*",
|
|
72
|
+
"react-native": ">=0.57.0",
|
|
73
|
+
"react-native-gesture-handler": ">=2",
|
|
74
|
+
"react-native-reanimated": ">=3",
|
|
75
|
+
"react-native-safe-area-context": "*",
|
|
76
|
+
"react-native-svg": ">=14"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=18.0.0"
|
|
80
|
+
},
|
|
81
|
+
"publishConfig": {
|
|
82
|
+
"access": "public"
|
|
83
|
+
},
|
|
84
|
+
"gitHead": "b7c2a24d21c0a38bd5e31349b3ba370af7c3d863"
|
|
85
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
|
|
6
|
+
import { Button } from './Button';
|
|
7
|
+
import { FaceHappyIcon } from './icon/FaceHappyIcon';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'UI/Button',
|
|
11
|
+
component: Button,
|
|
12
|
+
args: {},
|
|
13
|
+
} satisfies Meta<typeof Button>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
const Stack = ({ children }: { children: ReactNode }) => (
|
|
19
|
+
<View style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>{children}</View>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const Row = ({ children }: { children: ReactNode }) => (
|
|
23
|
+
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 16 }}>
|
|
24
|
+
{children}
|
|
25
|
+
</View>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export const Base: Story = {};
|
|
29
|
+
|
|
30
|
+
export const Variants: Story = {
|
|
31
|
+
render: (args) => (
|
|
32
|
+
<Stack>
|
|
33
|
+
<Row>
|
|
34
|
+
<Button variant="solid" text="Solid" {...args} />
|
|
35
|
+
<Button variant="outline" text="Outline" {...args} />
|
|
36
|
+
<Button variant="ghost" text="Ghost" {...args} />
|
|
37
|
+
</Row>
|
|
38
|
+
<Row>
|
|
39
|
+
<Button variant="solid" {...args} Icon={FaceHappyIcon} text="Solid" />
|
|
40
|
+
<Button variant="outline" Icon={FaceHappyIcon} text="Outline" {...args} />
|
|
41
|
+
<Button variant="ghost" Icon={FaceHappyIcon} text="Ghost" {...args} />
|
|
42
|
+
</Row>
|
|
43
|
+
<Row>
|
|
44
|
+
<Button variant="solid" padding="small" Icon={FaceHappyIcon} {...args} />
|
|
45
|
+
<Button variant="outline" padding="small" Icon={FaceHappyIcon} {...args} />
|
|
46
|
+
<Button variant="ghost" padding="small" Icon={FaceHappyIcon} {...args} />
|
|
47
|
+
</Row>
|
|
48
|
+
</Stack>
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Active: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
active: true,
|
|
55
|
+
text: 'Button',
|
|
56
|
+
Icon: FaceHappyIcon,
|
|
57
|
+
},
|
|
58
|
+
render: (args) => (
|
|
59
|
+
<Row>
|
|
60
|
+
<Button variant="solid" {...args} />
|
|
61
|
+
<Button variant="outline" {...args} />
|
|
62
|
+
<Button variant="ghost" {...args} />
|
|
63
|
+
</Row>
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const WithIcon: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
Icon: FaceHappyIcon,
|
|
70
|
+
text: 'Button',
|
|
71
|
+
},
|
|
72
|
+
render: (args) => (
|
|
73
|
+
<Row>
|
|
74
|
+
<Button variant="solid" {...args} />
|
|
75
|
+
<Button variant="outline" {...args} />
|
|
76
|
+
<Button variant="ghost" {...args} />
|
|
77
|
+
</Row>
|
|
78
|
+
),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const IconOnly: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
padding: 'small',
|
|
84
|
+
Icon: FaceHappyIcon,
|
|
85
|
+
},
|
|
86
|
+
render: (args) => (
|
|
87
|
+
<Row>
|
|
88
|
+
<Button variant="solid" {...args} />
|
|
89
|
+
<Button variant="outline" {...args} />
|
|
90
|
+
<Button variant="ghost" {...args} />
|
|
91
|
+
</Row>
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Sizes: Story = {
|
|
96
|
+
render: () => (
|
|
97
|
+
<Row>
|
|
98
|
+
<Button size="small" text="Small Button" />
|
|
99
|
+
<Button size="medium" text="Medium Button" />
|
|
100
|
+
</Row>
|
|
101
|
+
),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const Disabled: Story = {
|
|
105
|
+
args: {
|
|
106
|
+
disabled: true,
|
|
107
|
+
text: 'Disabled Button',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const Animated: Story = {
|
|
112
|
+
args: {
|
|
113
|
+
variant: 'outline',
|
|
114
|
+
},
|
|
115
|
+
render: (args) => (
|
|
116
|
+
<Stack>
|
|
117
|
+
<Row>
|
|
118
|
+
<Button animation="glow" text="Button" {...args} />
|
|
119
|
+
<Button animation="jiggle" text="Button" {...args} />
|
|
120
|
+
<Button animation="rotate360" text="Button" {...args} />
|
|
121
|
+
</Row>
|
|
122
|
+
<Row>
|
|
123
|
+
<Button animation="glow" text="Button" Icon={FaceHappyIcon} {...args} />
|
|
124
|
+
<Button animation="jiggle" text="Button" Icon={FaceHappyIcon} {...args} />
|
|
125
|
+
<Button animation="rotate360" Icon={FaceHappyIcon} text="Button" {...args} />
|
|
126
|
+
</Row>
|
|
127
|
+
<Row>
|
|
128
|
+
<Button animation="glow" padding="small" Icon={FaceHappyIcon} {...args} />
|
|
129
|
+
<Button animation="jiggle" padding="small" Icon={FaceHappyIcon} {...args} />
|
|
130
|
+
<Button animation="rotate360" padding="small" Icon={FaceHappyIcon} {...args} />
|
|
131
|
+
</Row>
|
|
132
|
+
</Stack>
|
|
133
|
+
),
|
|
134
|
+
};
|
package/src/Button.tsx
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { styled, useTheme } from '@storybook/react-native-theming';
|
|
2
|
+
import { ReactElement, forwardRef, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { TouchableOpacity, TouchableOpacityProps } from 'react-native';
|
|
4
|
+
import { SvgProps } from 'react-native-svg';
|
|
5
|
+
|
|
6
|
+
export interface ButtonProps extends TouchableOpacityProps {
|
|
7
|
+
asChild?: boolean;
|
|
8
|
+
size?: 'small' | 'medium';
|
|
9
|
+
padding?: 'small' | 'medium';
|
|
10
|
+
variant?: 'outline' | 'solid' | 'ghost';
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
active?: boolean;
|
|
13
|
+
animation?: 'none' | 'rotate360' | 'glow' | 'jiggle';
|
|
14
|
+
text?: string;
|
|
15
|
+
Icon?: (props: SvgProps) => ReactElement;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Button = forwardRef<TouchableOpacity, ButtonProps>(
|
|
19
|
+
(
|
|
20
|
+
{
|
|
21
|
+
// asChild = false,
|
|
22
|
+
Icon,
|
|
23
|
+
animation = 'none',
|
|
24
|
+
size = 'small',
|
|
25
|
+
variant = 'outline',
|
|
26
|
+
padding = 'medium',
|
|
27
|
+
disabled = false,
|
|
28
|
+
active = false,
|
|
29
|
+
onPress,
|
|
30
|
+
children,
|
|
31
|
+
text,
|
|
32
|
+
...props
|
|
33
|
+
},
|
|
34
|
+
ref
|
|
35
|
+
) => {
|
|
36
|
+
// let Comp: 'button' | 'a' | typeof Slot = 'button';
|
|
37
|
+
// if (props.isLink) Comp = 'a';
|
|
38
|
+
// if (asChild) Comp = Slot;
|
|
39
|
+
// let localVariant = variant;
|
|
40
|
+
// let localSize = size;
|
|
41
|
+
|
|
42
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
43
|
+
|
|
44
|
+
const handleClick = (event) => {
|
|
45
|
+
if (onPress) onPress(event);
|
|
46
|
+
if (animation === 'none') return;
|
|
47
|
+
setIsAnimating(true);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
if (isAnimating) setIsAnimating(false);
|
|
53
|
+
}, 1000);
|
|
54
|
+
return () => clearTimeout(timer);
|
|
55
|
+
}, [isAnimating]);
|
|
56
|
+
|
|
57
|
+
// Match the old API with the new API.
|
|
58
|
+
// TODO: Remove this after 9.0.
|
|
59
|
+
// if (props.primary) {
|
|
60
|
+
// localVariant = 'solid';
|
|
61
|
+
// localSize = 'medium';
|
|
62
|
+
// }
|
|
63
|
+
|
|
64
|
+
// Match the old API with the new API.
|
|
65
|
+
// TODO: Remove this after 9.0.
|
|
66
|
+
// if (props.secondary || props.tertiary || props.gray || props.outline || props.inForm) {
|
|
67
|
+
// localVariant = 'outline';
|
|
68
|
+
// localSize = 'medium';
|
|
69
|
+
// }
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<StyledButton
|
|
73
|
+
// as={Comp}
|
|
74
|
+
ref={ref}
|
|
75
|
+
variant={variant}
|
|
76
|
+
size={size}
|
|
77
|
+
padding={padding}
|
|
78
|
+
disabled={disabled}
|
|
79
|
+
active={active}
|
|
80
|
+
animating={isAnimating}
|
|
81
|
+
animation={animation}
|
|
82
|
+
onPress={handleClick}
|
|
83
|
+
{...props}
|
|
84
|
+
>
|
|
85
|
+
{Icon && <ButtonIcon Icon={Icon} variant={variant} active={active} />}
|
|
86
|
+
{text && (
|
|
87
|
+
<ButtonText variant={variant} active={active}>
|
|
88
|
+
{text}
|
|
89
|
+
</ButtonText>
|
|
90
|
+
)}
|
|
91
|
+
{children}
|
|
92
|
+
</StyledButton>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
Button.displayName = 'Button';
|
|
98
|
+
|
|
99
|
+
// const StyledButton = styled('TouchableOpacity', {
|
|
100
|
+
// shouldForwardProp: (prop) => isPropValid(prop),
|
|
101
|
+
// })<
|
|
102
|
+
// ButtonProps & {
|
|
103
|
+
// animating: boolean;
|
|
104
|
+
// animation: ButtonProps['animation'];
|
|
105
|
+
// }
|
|
106
|
+
// >(({ theme, variant, size, disabled, active, animating, animation, padding }) => ({
|
|
107
|
+
const StyledButton = styled.TouchableOpacity<
|
|
108
|
+
ButtonProps & { animating: boolean; animation: ButtonProps['animation'] }
|
|
109
|
+
>(({ theme, variant, size, disabled, active, /* animating, animation, */ padding }) => ({
|
|
110
|
+
border: 0,
|
|
111
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
112
|
+
display: 'flex',
|
|
113
|
+
flexDirection: 'row',
|
|
114
|
+
gap: 6,
|
|
115
|
+
alignItems: 'center',
|
|
116
|
+
justifyContent: 'center',
|
|
117
|
+
overflow: 'hidden',
|
|
118
|
+
paddingHorizontal: (() => {
|
|
119
|
+
if (padding === 'small' && size === 'small') return 7;
|
|
120
|
+
if (padding === 'small' && size === 'medium') return 9;
|
|
121
|
+
if (size === 'small') return 10;
|
|
122
|
+
if (size === 'medium') return 12;
|
|
123
|
+
return 0;
|
|
124
|
+
})(),
|
|
125
|
+
paddingVertical: 0,
|
|
126
|
+
height: size === 'small' ? 28 : 32,
|
|
127
|
+
position: 'relative',
|
|
128
|
+
|
|
129
|
+
// textDecoration: 'none',
|
|
130
|
+
transitionProperty: 'background, box-shadow',
|
|
131
|
+
transitionDuration: '150ms',
|
|
132
|
+
transitionTimingFunction: 'ease-out',
|
|
133
|
+
// verticalAlign: 'top',
|
|
134
|
+
whiteSpace: 'nowrap',
|
|
135
|
+
userSelect: 'none',
|
|
136
|
+
opacity: disabled ? 0.5 : 1,
|
|
137
|
+
margin: 0,
|
|
138
|
+
|
|
139
|
+
backgroundColor: (() => {
|
|
140
|
+
if (variant === 'solid') return theme.color.secondary;
|
|
141
|
+
if (variant === 'outline') return theme.button$.background;
|
|
142
|
+
if (variant === 'ghost' && active) return theme.background.hoverable;
|
|
143
|
+
|
|
144
|
+
return 'transparent';
|
|
145
|
+
})(),
|
|
146
|
+
|
|
147
|
+
boxShadow: variant === 'outline' ? `${theme.button$.border} 0 0 0 1px inset` : 'none',
|
|
148
|
+
borderRadius: theme.input.borderRadius,
|
|
149
|
+
// Making sure that the button never shrinks below its minimum size
|
|
150
|
+
flexShrink: 0,
|
|
151
|
+
|
|
152
|
+
// '&:hover': {
|
|
153
|
+
// color: variant === 'ghost' ? theme.color.secondary : null,
|
|
154
|
+
// background: (() => {
|
|
155
|
+
// let bgColor = theme.color.secondary;
|
|
156
|
+
// if (variant === 'solid') bgColor = theme.color.secondary;
|
|
157
|
+
// if (variant === 'outline') bgColor = theme.button.background;
|
|
158
|
+
|
|
159
|
+
// if (variant === 'ghost') return transparentize(0.86, theme.color.secondary);
|
|
160
|
+
// return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
|
|
161
|
+
// })(),
|
|
162
|
+
// },
|
|
163
|
+
|
|
164
|
+
// '&:active': {
|
|
165
|
+
// color: variant === 'ghost' ? theme.color.secondary : null,
|
|
166
|
+
// background: (() => {
|
|
167
|
+
// let bgColor = theme.color.secondary;
|
|
168
|
+
// if (variant === 'solid') bgColor = theme.color.secondary;
|
|
169
|
+
// if (variant === 'outline') bgColor = theme.button.background;
|
|
170
|
+
|
|
171
|
+
// if (variant === 'ghost') return theme.background.hoverable;
|
|
172
|
+
// return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
|
|
173
|
+
// })(),
|
|
174
|
+
// },
|
|
175
|
+
|
|
176
|
+
// '&:focus': {
|
|
177
|
+
// boxShadow: `${rgba(theme.color.secondary, 1)} 0 0 0 1px inset`,
|
|
178
|
+
// outline: 'none',
|
|
179
|
+
// },
|
|
180
|
+
|
|
181
|
+
// '> svg': {
|
|
182
|
+
// animation:
|
|
183
|
+
// animating && animation !== 'none' ? `${theme.animation[animation]} 1000ms ease-out` : '',
|
|
184
|
+
// },
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
export const ButtonText = styled.Text<{
|
|
188
|
+
variant: ButtonProps['variant'];
|
|
189
|
+
active: ButtonProps['active'];
|
|
190
|
+
}>(({ theme, variant, active }) => ({
|
|
191
|
+
color: (() => {
|
|
192
|
+
if (variant === 'solid') return theme.color.lightest;
|
|
193
|
+
if (variant === 'outline') return theme.input.color;
|
|
194
|
+
if (variant === 'ghost' && active) return theme.color.secondary;
|
|
195
|
+
if (variant === 'ghost') return theme.color.mediumdark;
|
|
196
|
+
return theme.input.color;
|
|
197
|
+
})(),
|
|
198
|
+
flexDirection: 'row',
|
|
199
|
+
gap: 6,
|
|
200
|
+
textAlign: 'center',
|
|
201
|
+
// lineHeight: theme.typography.size.s1,
|
|
202
|
+
fontSize: theme.typography.size.s1,
|
|
203
|
+
fontWeight: theme.typography.weight.bold,
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
export const ButtonIcon = ({
|
|
207
|
+
Icon,
|
|
208
|
+
active,
|
|
209
|
+
variant,
|
|
210
|
+
}: {
|
|
211
|
+
Icon: (props: SvgProps) => ReactElement;
|
|
212
|
+
variant: ButtonProps['variant'];
|
|
213
|
+
active: ButtonProps['active'];
|
|
214
|
+
}) => {
|
|
215
|
+
const theme = useTheme();
|
|
216
|
+
|
|
217
|
+
const color = useMemo(() => {
|
|
218
|
+
if (variant === 'solid') return theme.color.lightest;
|
|
219
|
+
if (variant === 'outline') return theme.input.color;
|
|
220
|
+
if (variant === 'ghost' && active) return theme.color.secondary;
|
|
221
|
+
if (variant === 'ghost') return theme.color.mediumdark;
|
|
222
|
+
return theme.input.color;
|
|
223
|
+
}, [
|
|
224
|
+
active,
|
|
225
|
+
theme.color.lightest,
|
|
226
|
+
theme.color.mediumdark,
|
|
227
|
+
theme.color.secondary,
|
|
228
|
+
theme.input.color,
|
|
229
|
+
variant,
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
return <Icon color={color} />;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// color: variant === 'ghost' ? theme.color.secondary : null,
|
|
236
|
+
// background: (() => {
|
|
237
|
+
// let bgColor = theme.color.secondary;
|
|
238
|
+
// if (variant === 'solid') bgColor = theme.color.secondary;
|
|
239
|
+
// if (variant === 'outline') bgColor = theme.button.background;
|
|
240
|
+
|
|
241
|
+
// if (variant === 'ghost') return theme.background.hoverable;
|
|
242
|
+
// return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
|
|
243
|
+
// })(),
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ManagerContext } from '@storybook/manager-api';
|
|
2
|
+
import { Explorer } from './Explorer';
|
|
3
|
+
import { mockDataset } from './mockdata';
|
|
4
|
+
import type { RefType } from './types';
|
|
5
|
+
import { View } from 'react-native';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
component: Explorer,
|
|
9
|
+
title: 'UI/Sidebar/Explorer',
|
|
10
|
+
parameters: { layout: 'fullscreen', theme: 'side-by-side' },
|
|
11
|
+
decorators: [
|
|
12
|
+
(storyFn: any) => (
|
|
13
|
+
<ManagerContext.Provider value={{ state: { docsOptions: {} } } as any}>
|
|
14
|
+
{storyFn()}
|
|
15
|
+
</ManagerContext.Provider>
|
|
16
|
+
),
|
|
17
|
+
(storyFn: any) => <View style={{ paddingHorizontal: 20 }}>{storyFn()}</View>,
|
|
18
|
+
(storyFn: any) => <View style={{ paddingHorizontal: 20 }}>{storyFn()}</View>,
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const selected = {
|
|
23
|
+
refId: 'storybook_internal',
|
|
24
|
+
storyId: 'root-1-child-a2--grandchild-a1-1',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const simple: Record<string, RefType> = {
|
|
28
|
+
storybook_internal: {
|
|
29
|
+
title: undefined,
|
|
30
|
+
id: 'storybook_internal',
|
|
31
|
+
url: 'iframe.html',
|
|
32
|
+
previewInitialized: true,
|
|
33
|
+
// @ts-expect-error (invalid input)
|
|
34
|
+
index: mockDataset.withRoot,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Simple = () => (
|
|
39
|
+
<Explorer
|
|
40
|
+
dataset={{ hash: simple, entries: Object.entries(simple) }}
|
|
41
|
+
selected={selected}
|
|
42
|
+
isLoading={false}
|
|
43
|
+
isBrowsing
|
|
44
|
+
setSelection={() => {}}
|
|
45
|
+
/>
|
|
46
|
+
);
|
package/src/Explorer.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import React, { useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Ref } from './Refs';
|
|
5
|
+
import type { CombinedDataset, Selection } from './types';
|
|
6
|
+
|
|
7
|
+
// import { useHighlighted } from './useHighlighted';
|
|
8
|
+
// import { HighlightStyles } from './HighlightStyles';
|
|
9
|
+
import { View } from 'react-native';
|
|
10
|
+
|
|
11
|
+
export interface ExplorerProps {
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
isBrowsing: boolean;
|
|
14
|
+
dataset: CombinedDataset;
|
|
15
|
+
selected: Selection;
|
|
16
|
+
setSelection: (selection: Selection) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Explorer: FC<ExplorerProps> = React.memo(function Explorer({
|
|
20
|
+
isLoading,
|
|
21
|
+
isBrowsing,
|
|
22
|
+
dataset,
|
|
23
|
+
selected,
|
|
24
|
+
setSelection,
|
|
25
|
+
}) {
|
|
26
|
+
const containerRef = useRef<View>(null);
|
|
27
|
+
|
|
28
|
+
// Track highlighted nodes, keep it in sync with props and enable keyboard navigation
|
|
29
|
+
// const [highlighted, setHighlighted, highlightedRef] = useHighlighted({
|
|
30
|
+
// containerRef,
|
|
31
|
+
// isLoading,
|
|
32
|
+
// isBrowsing,
|
|
33
|
+
// dataset,
|
|
34
|
+
// selected,
|
|
35
|
+
// });
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<View ref={containerRef} id="storybook-explorer-tree">
|
|
39
|
+
{/* {highlighted && <HighlightStyles {...highlighted} />} */}
|
|
40
|
+
{dataset.entries.map(([refId, ref]) => (
|
|
41
|
+
<Ref
|
|
42
|
+
{...ref}
|
|
43
|
+
key={refId}
|
|
44
|
+
isLoading={isLoading}
|
|
45
|
+
isBrowsing={isBrowsing}
|
|
46
|
+
selectedStoryId={selected?.refId === ref.id ? selected.storyId : null}
|
|
47
|
+
setSelection={setSelection}
|
|
48
|
+
// highlightedRef={highlightedRef}
|
|
49
|
+
// setHighlighted={setHighlighted}
|
|
50
|
+
/>
|
|
51
|
+
))}
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TouchableOpacity } from 'react-native';
|
|
2
|
+
import { Button, ButtonProps } from './Button';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
|
|
5
|
+
export const IconButton = forwardRef<TouchableOpacity, ButtonProps>(
|
|
6
|
+
({ padding = 'small', variant = 'ghost', ...props }, ref) => {
|
|
7
|
+
return <Button padding={padding} variant={variant} ref={ref} {...props} />;
|
|
8
|
+
}
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
IconButton.displayName = 'IconButton';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { Layout } from './Layout';
|
|
4
|
+
// import { mockDataset } from './mockdata';
|
|
5
|
+
// import { index } from './mockdata.large';
|
|
6
|
+
import { mockDataset } from './mockdata';
|
|
7
|
+
// import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
8
|
+
// import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
|
9
|
+
import { Text } from 'react-native';
|
|
10
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
11
|
+
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
|
12
|
+
|
|
13
|
+
const meta = {
|
|
14
|
+
title: 'components/Layout',
|
|
15
|
+
component: Layout,
|
|
16
|
+
decorators: [
|
|
17
|
+
(Story) => (
|
|
18
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
19
|
+
<BottomSheetModalProvider>
|
|
20
|
+
<Story />
|
|
21
|
+
</BottomSheetModalProvider>
|
|
22
|
+
</GestureHandlerRootView>
|
|
23
|
+
),
|
|
24
|
+
],
|
|
25
|
+
} satisfies Meta<typeof Layout>;
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
|
|
29
|
+
type Story = StoryObj<typeof meta>;
|
|
30
|
+
|
|
31
|
+
export const Basic: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
children: <Text>Testing</Text>,
|
|
34
|
+
//@ts-ignore
|
|
35
|
+
storyHash: mockDataset.withRoot,
|
|
36
|
+
storyId: 'emails-buildnotification--with-changes',
|
|
37
|
+
},
|
|
38
|
+
};
|