Skip to content

Commit

Permalink
docs(storybook): add theme playground
Browse files Browse the repository at this point in the history
fix #35
  • Loading branch information
morewings committed Aug 11, 2024
1 parent 7e51c5f commit 3bc8192
Show file tree
Hide file tree
Showing 17 changed files with 695 additions and 226 deletions.
20 changes: 19 additions & 1 deletion .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import {addons} from '@storybook/manager-api';
import {addons, types} from '@storybook/manager-api';

import {TOOL_ID, PANEL_ID} from '../src/env/theme-playground/constants';
import {ThemeSwitcherTool} from '../src/env/theme-playground/ThemeSwitcherTool';
import {Panel} from '../src/env/theme-playground/PlaygroundPanel';
import kovalTheme from './kovalTheme';

addons.setConfig({
theme: kovalTheme,
});

addons.add(TOOL_ID, {
type: types.TOOL,
title: 'Theme provider',
match: ({viewMode}) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
render: ThemeSwitcherTool,
});

// Register the panel
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'Theme playground',
match: ({viewMode}) => viewMode === 'story',
render: Panel,
});
5 changes: 3 additions & 2 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type {Preview} from '@storybook/react';
import {withThemeProvider} from 'storybook-addon-theme-provider';
// import {withThemeProvider} from 'storybook-addon-theme-provider';

import {theme as themePodil} from '../src/lib/Theme/themePodil.ts';
import {theme as themeDnipro} from '../src/lib/Theme/themeDnipro.ts';
import {Provider} from './../src/lib/Provider/Provider.tsx';
import {withThemeProvider} from './../src/env/theme-playground/ThemeSwitcherTool';

import '@/lib/CSSReset/reset.css';

Expand Down Expand Up @@ -63,7 +64,7 @@ const preview: Preview = {

decorators: [withThemeProvider(Provider)],

globals: {
initialGlobals: {
// Set initially selected theme name
selectedTheme: 'Podil',
// Provide a list of available themes
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export default [
'**/*.stories.*',
'*.config.{js,ts}',
'.storybook/*.{js,jsx,ts,tsx}',
'src/env/**/*.{ts,tsx}',
],
plugins: {
import: pluginImport,
Expand Down
29 changes: 16 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,21 @@
"@semantic-release/github": "10.1.3",
"@semantic-release/npm": "12.0.1",
"@semantic-release/release-notes-generator": "14.0.1",
"@storybook/addon-essentials": "8.2.7",
"@storybook/addon-interactions": "8.2.7",
"@storybook/addon-links": "8.2.7",
"@storybook/addon-storysource": "8.2.7",
"@storybook/blocks": "8.2.7",
"@storybook/builder-vite": "8.2.7",
"@storybook/manager-api": "8.2.7",
"@storybook/react": "8.2.7",
"@storybook/react-vite": "8.2.7",
"@storybook/test": "8.2.7",
"@storybook/theming": "8.2.7",
"@storybook/types": "8.2.7",
"@storybook/addon-essentials": "8.2.8",
"@storybook/addon-interactions": "8.2.8",
"@storybook/addon-links": "8.2.8",
"@storybook/addon-storysource": "8.2.8",
"@storybook/blocks": "8.2.8",
"@storybook/builder-vite": "8.2.8",
"@storybook/components": "8.2.8",
"@storybook/icons": "^1.2.10",
"@storybook/manager-api": "8.2.8",
"@storybook/preview-api": "8.2.8",
"@storybook/react": "8.2.8",
"@storybook/react-vite": "8.2.8",
"@storybook/test": "8.2.8",
"@storybook/theming": "8.2.8",
"@storybook/types": "8.2.8",
"@testing-library/jest-dom": "6.4.8",
"@testing-library/react": "16.0.0",
"@types/jest": "29.5.12",
Expand Down Expand Up @@ -143,7 +146,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"semantic-release": "24.0.0",
"storybook": "8.2.7",
"storybook": "8.2.8",
"storybook-addon-theme-provider": "0.2.2",
"stylelint": "16.8.1",
"stylelint-config-standard": "36.0.1",
Expand Down
429 changes: 219 additions & 210 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions src/env/theme-playground/PlaygroundPanel/Control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type {FC} from 'react';
// @ts-expect-error TODO: maybe fix later
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React, {useState, useEffect, useCallback} from 'react';
import {styled} from '@storybook/theming';
import {ColorControl} from '@storybook/blocks';

export type Props = {
onChange: (name: string, value?: string) => void;
value?: string;
name: string;
};

const Wrapper = styled.div`
display: flex;
flex-direction: row;
text-align: left;
flex-shrink: 1;
align-items: center;
gap: 12px;
& input {
width: 210px;
}
`;

const Label = styled.label`
font-weight: bold;
font-size: 14px;
`;

export const Control: FC<Props> = ({onChange, value: valueProp, name}) => {
const [value, setValue] = useState<string | undefined>(valueProp);
useEffect(() => {
setValue(valueProp);
}, [valueProp]);
const handleChange = useCallback(
(value?: string) => {
setValue(value);
onChange(name, value);
},
[name, onChange]
);
return (
<Wrapper>
<Label htmlFor="foo" style={{color: value}}>
{name.replace('color', '')}:
</Label>
<ColorControl value={value} name={name} onChange={handleChange} />
</Wrapper>
);
};
67 changes: 67 additions & 0 deletions src/env/theme-playground/PlaygroundPanel/Panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type {FC} from 'react';
// @ts-expect-error TODO: maybe fix later
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React, {useCallback, useEffect} from 'react';
import {useChannel} from '@storybook/manager-api';
import {AddonPanel, ActionBar} from '@storybook/components';
import {useAddonState} from '@storybook/manager-api';

import {useGlobalThemesManager} from './../useGlobalThemes.ts';
import {EVENTS} from '../constants';
import {Playground} from './Playground';
import {ADDON_ID} from '../constants';

type PanelProps = {
active: boolean;
};

export const Panel: FC<PanelProps> = props => {
const [theme, setTheme] = useAddonState(ADDON_ID, {});

const {themes, selectedTheme} = useGlobalThemesManager();

const vanillaTheme = themes?.find(
({name}) => Boolean(selectedTheme) && name === selectedTheme
)?.themeObject;

const handleThemeReset = useCallback(() => {
setTheme({});
}, [setTheme]);

const emit = useChannel({[EVENTS.RESET_THEME]: handleThemeReset});

useEffect(() => {
emit(EVENTS.SET_THEME, theme);
}, [emit, theme]);

const handleColorChange = useCallback(
(change: Record<string, string>) => {
change && setTheme({...theme, ...change});
},
[setTheme, theme]
);

const handleBackgroundInvert = useCallback((nextBgColors: object) => {
console.log('invert', nextBgColors);
}, []);

const handleReset = useCallback(() => {
emit(EVENTS.RESET_THEME);
}, [emit]);

return (
<AddonPanel {...props}>
<Playground
vanillaTheme={vanillaTheme}
theme={theme}
onColorChange={handleColorChange}
onBackgroundInvert={handleBackgroundInvert}
/>
<ActionBar
actionItems={[
// {title: 'Save theme', onClick: () => {}},
{title: 'Reset theme', onClick: handleReset},
]}></ActionBar>
</AddonPanel>
);
};
140 changes: 140 additions & 0 deletions src/env/theme-playground/PlaygroundPanel/Playground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import type {FC} from 'react';
// @ts-expect-error TODO: maybe fix later
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React, {useCallback} from 'react';
import {styled} from '@storybook/theming';
// import {BooleanControl} from '@storybook/blocks';

import type {PublicThemeType} from './../../../lib/Theme';
import {ColorsList} from '../types';
import {Control} from './Control';

type PanelContentProps = {
onColorChange: (arg0: Record<string, string>) => void;
onBackgroundInvert: (bgColors: object) => void;
vanillaTheme?: PublicThemeType;
theme: Partial<PublicThemeType>;
};

const Wrapper = styled.div`
padding: 12px;
`;

const FieldSet = styled.fieldset`
display: flex;
flex-direction: row;
gap: 36px;
max-width: 100%;
align-items: center;
flex-wrap: wrap;
margin-bottom: 24px;
border-color: hsla(203, 50%, 30%, 0.15);
`;

const Legend = styled.legend`
font-size: 16px;
color: #73828c;
font-weight: bolder;
`;

// const BooleanWrapper = styled.div`
// display: flex;
// flex-direction: row;
// gap: 12px;
// align-items: center;
// & > span {
// font-weight: bold;
// }
// & > label {
// margin-bottom: 0;
// }
// `;

// type BackgroundColors = {
// background000?: string;
// background100?: string;
// background200?: string;
// background300?: string;
// background400?: string;
// background500?: string;
// background600?: string;
// };
//
// const getBackground = (theme: BackgroundColors & unknown): BackgroundColors => {
// return {
// background000: theme['background000'],
// background100: theme['background100'],
// background200: theme['background200'],
// background300: theme['background300'],
// background400: theme['background400'],
// background500: theme['background500'],
// background600: theme['background600'],
// };
// };
//
// const getInvertedBackground = (theme: BackgroundColors & unknown): BackgroundColors => {
// return {
// background000: theme['background600'],
// background100: theme['background500'],
// background200: theme['background400'],
// background300: theme['background300'],
// background400: theme['background200'],
// background500: theme['background100'],
// background600: theme['background000'],
// };
// };

export const Playground: FC<PanelContentProps> = ({onColorChange, vanillaTheme, theme}) => {
// const [inverted, setInverted] = useState<undefined | boolean>(false);

const handleChange = useCallback(
(name?: string, value?: string) => {
name && value && onColorChange({[name]: value});
},
[onColorChange]
);

// TODO: mode logic to Panel. Fix state problem
// const handleInvert = useCallback(
// (value?: boolean) => {
// setInverted(value);
// if (vanillaTheme) {
// const nextTheme = value
// ? getInvertedBackground(vanillaTheme)
// : getBackground(vanillaTheme);
// onColorChange(nextTheme);
// }
// },
// [onColorChange, setInverted, vanillaTheme]
// );

return (
<Wrapper>
<FieldSet>
<Legend>Brand colors</Legend>
{Object.values(ColorsList).map(colorToken => {
return (
<Control
key={colorToken}
value={{...vanillaTheme, ...theme}[colorToken]}
name={colorToken}
onChange={handleChange}
/>
);
})}
</FieldSet>
<FieldSet>
<Legend>Other colors</Legend>
{/*<BooleanWrapper>*/}
{/* <span>Invert background colors:</span>*/}
{/* <BooleanControl value={inverted} onChange={handleInvert} name="Hello" />*/}
{/*</BooleanWrapper>*/}
<Control
value={{...vanillaTheme, ...theme}.textColor}
name="Text"
onChange={handleChange}
/>
</FieldSet>
</Wrapper>
);
};
1 change: 1 addition & 0 deletions src/env/theme-playground/PlaygroundPanel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {Panel} from './Panel';
16 changes: 16 additions & 0 deletions src/env/theme-playground/ThemeSwitcherTool/Color.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type {FC} from 'react';
import {styled} from '@storybook/theming';

const gradient =
'linear-gradient(-45deg, transparent, transparent 45%, pink 45%, pink 55%, transparent 55%, transparent 100%)';

export const Color: FC<{colorName?: string}> = ({colorName = gradient}) => {
return <ColorDiv style={{background: colorName}} />;
};

export const ColorDiv = styled.div({
width: '16px',
height: '16px',
borderRadius: '8px',
border: '1px solid lightgray',
});
Loading

0 comments on commit 3bc8192

Please sign in to comment.