Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support branding #639

Merged
merged 14 commits into from
Nov 28, 2024
7 changes: 5 additions & 2 deletions packages/react-native-theming/src/web-theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TextStyle } from 'react-native';
import type { ImageSourcePropType, TextStyle } from 'react-native';
import { transparentize } from 'polished';

export const color = {
Expand Down Expand Up @@ -171,9 +171,12 @@ export type Typography = typeof typography;

export type TextSize = number | string;
export interface Brand {
// Will replace the storybook logo with this title
title: string | undefined;
// This url we be opened when clicking the branded logo or title
url: string | null | undefined;
image: string | null | undefined;
// Either define a url or an image source to replace storybook logo with
image: string | ImageSourcePropType | null | undefined;
target: string | null | undefined;
}

Expand Down
7 changes: 2 additions & 5 deletions packages/react-native-ui/src/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { BottomBarToggleIcon } from './icon/BottomBarToggleIcon';
import { DarkLogo } from './icon/DarkLogo';
import { Logo } from './icon/Logo';
import { MenuIcon } from './icon/MenuIcon';
import { StorybookLogo } from './StorybookLogo';

export const Layout = ({
storyHash,
Expand Down Expand Up @@ -72,11 +73,7 @@ export const Layout = ({
justifyContent: 'space-between',
}}
>
{theme.base === 'light' ? (
<Logo height={25} width={125} />
) : (
<DarkLogo height={25} width={125} />
)}
<StorybookLogo theme={theme} />

<IconButton onPress={() => setDesktopSidebarOpen(false)} Icon={MenuIcon} />
</View>
Expand Down
81 changes: 81 additions & 0 deletions packages/react-native-ui/src/StorybookLogo.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { StoryObj, Meta } from '@storybook/react';
import { StorybookLogo } from './StorybookLogo';
import { useTheme } from '@storybook/react-native-theming';

const meta = {
component: StorybookLogo,
title: 'UI/StorybookLogo',
args: {
theme: null,
},
} satisfies Meta<typeof StorybookLogo>;

export default meta;

type Story = StoryObj<typeof meta>;

export const TitleLogo: Story = {
decorators: [
(Story) => {
const theme = useTheme();
return <Story args={{ theme: { ...theme, brand: { title: 'React Native' } } }} />;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like having this decorator in every story just to make typescript happy. Essentially the problem is only that theme requires a lot of properties that we don't want to define here.
Maybe I missed something to make it easier?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/trajano/expo-experiments/blob/4287571d3a9fe568700ce97ecc266d5a5bff621e/packages/my-app/.storybook/index.ts#L11-L16 shows what I did on mine to pass it in, but I cheated with the any because of the imageSource thing which you resolved in web-theme.ts I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I think what you have looks correct. At least for the context of the story.

},
],
};

export const ImageLogo: Story = {
decorators: [
(Story) => {
const theme = useTheme();
return (
<Story
args={{
theme: { ...theme, brand: { image: 'https://reactnative.dev/img/oss_logo.svg' } },
}}
/>
);
},
],
};

export const ImageUrlLogo: Story = {
decorators: [
(Story) => {
const theme = useTheme();
return (
<Story
args={{
theme: {
...theme,
brand: {
image: 'https://reactnative.dev/img/oss_logo.svg',
url: 'https://reactnative.dev',
},
},
}}
/>
);
},
],
};

export const ImageSourceLogo: Story = {
decorators: [
(Story) => {
const theme = useTheme();
return (
<Story
args={{
theme: {
...theme,
brand: {
imageSource: require('./assets/react-native-logo.png'),
url: 'https://reactnative.dev',
},
},
}}
/>
);
},
],
};
82 changes: 82 additions & 0 deletions packages/react-native-ui/src/StorybookLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Theme } from '@storybook/react-native-theming';
import { FC, useMemo } from 'react';
import { Image, Linking, StyleProp, Text, TextStyle, TouchableOpacity } from 'react-native';
import { DarkLogo } from './icon/DarkLogo';
import { Logo } from './icon/Logo';

const WIDTH = 125;
const HEIGHT = 25;

const NoBrandLogo: FC<{ theme: Theme }> = ({ theme }) =>
theme.base === 'light' ? (
<Logo height={HEIGHT} width={WIDTH} />
) : (
<DarkLogo height={HEIGHT} width={WIDTH} />
);

const BrandLogo: FC<{ theme: Theme & { brand: NonNullable<Theme['brand']> } }> = ({ theme }) => {
const image = (
<Image
source={
typeof theme.brand.image === 'string' ? { uri: theme.brand.image } : theme.brand.image
}
resizeMode="contain"
style={{ height: HEIGHT, width: WIDTH }}
/>
);

if (theme.brand.url) {
return (
<TouchableOpacity
onPress={() => {
if (theme.brand.url) Linking.openURL(theme.brand.url);
}}
>
{image}
</TouchableOpacity>
);
} else {
return image;
}
};

const BrandTitle: FC<{ theme: Theme & { brand: NonNullable<Theme['brand']> } }> = ({ theme }) => {
const brandTitleStyle = useMemo<StyleProp<TextStyle>>(() => {
return {
width: WIDTH,
height: HEIGHT,
color: theme.color.defaultText,
fontSize: theme.typography.size.m1,
};
}, [theme]);

const title = (
<Text style={brandTitleStyle} numberOfLines={1} ellipsizeMode="tail">
{theme.brand.title}
</Text>
);

if (theme.brand.url) {
return (
<TouchableOpacity
onPress={() => {
if (theme.brand.url) Linking.openURL(theme.brand.url);
}}
>
{title}
</TouchableOpacity>
);
} else {
return title;
}
};

export const StorybookLogo: FC<{ theme: Theme }> = ({ theme }) => {
if (theme.brand.image) {
return <BrandLogo theme={theme} />;
} else if (theme.brand.title) {
return <BrandTitle theme={theme} />;
} else {
return <NoBrandLogo theme={theme} />;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just use the existing Storybook logo to prevent having another asset,

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.