diff --git a/.storybook/main.ts b/.storybook/main.ts index e3d6d47a..a60e41f5 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -15,7 +15,7 @@ const storybookConfig: StorybookConfig = { } }, '@storybook/addon-storysource', - './theme-picker/register.ts' + './theme-settings/register.ts' ], core: { builder: 'webpack5' diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 088dfca6..70151b31 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,8 +1,8 @@ import { DecoratorFn, Parameters } from '@storybook/react'; import { withGlobalStyle } from './decorators/withGlobalStyle'; -import { withThemesProvider } from './theme-picker/ThemeProvider'; +import { withThemeSettings } from './theme-settings/ThemeProvider'; -export const decorators: DecoratorFn[] = [withGlobalStyle, withThemesProvider]; +export const decorators: DecoratorFn[] = [withGlobalStyle, withThemeSettings]; export const parameters: Parameters = { layout: 'fullscreen', diff --git a/.storybook/theme-picker/ThemeList.tsx b/.storybook/theme-picker/ThemeList.tsx deleted file mode 100644 index 87e111b7..00000000 --- a/.storybook/theme-picker/ThemeList.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useAddonState } from '@storybook/api'; -import React, { useCallback } from 'react'; -import styled from 'styled-components'; - -import themes from '../../src/common/themes'; -import { Theme } from '../../src/types'; -import { THEMES_ID } from './constants'; -import { ThemeButton } from './ThemeButton'; - -const { - original, - rainyDay, - vaporTeal, - theSixtiesUSA, - olive, - tokyoDark, - rose, - plum, - matrix, - travel, - ...otherThemes -} = themes; - -const themeList = [ - original, - rainyDay, - vaporTeal, - theSixtiesUSA, - olive, - tokyoDark, - rose, - plum, - matrix, - travel, - ...Object.values(otherThemes) -]; - -type ThemesProps = { - active?: boolean; -}; - -const Wrapper = styled.div<{ theme: Theme }>` - display: grid; - padding: 1em; - gap: 1em; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - grid-template-rows: repeat(auto-fill, 40px); - background-color: ${({ theme }) => theme.material}; -`; - -export function ThemeList({ active }: ThemesProps) { - const [themeName, setThemeName] = useAddonState(THEMES_ID, 'original'); - - const handleChoose = useCallback( - (newThemeName: string) => { - setThemeName(newThemeName); - }, - [setThemeName] - ); - - if (!active) { - return <>; - } - - return ( - - {themeList.map(theme => ( - - ))} - - ); -} diff --git a/.storybook/theme-picker/ThemeProvider.tsx b/.storybook/theme-picker/ThemeProvider.tsx deleted file mode 100644 index e8dd4e2a..00000000 --- a/.storybook/theme-picker/ThemeProvider.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useAddonState } from '@storybook/client-api'; -import { DecoratorFn } from '@storybook/react'; -import React from 'react'; -import { ThemeProvider } from 'styled-components'; - -import themes from '../../src/common/themes/index'; -import { THEMES_ID } from './constants'; - -export const withThemesProvider: DecoratorFn = story => { - const [themeName] = useAddonState(THEMES_ID, 'original'); - - return ( - - {story()} - - ); -}; diff --git a/.storybook/theme-picker/ThemeButton.tsx b/.storybook/theme-settings/ThemeButton.tsx similarity index 79% rename from .storybook/theme-picker/ThemeButton.tsx rename to .storybook/theme-settings/ThemeButton.tsx index 87c57329..53844113 100644 --- a/.storybook/theme-picker/ThemeButton.tsx +++ b/.storybook/theme-settings/ThemeButton.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; import { ThemeProvider } from 'styled-components'; -import { Button } from '../../src/Button/Button'; +import { Button } from '../../src/index'; import { Theme } from '../../src/types'; export function ThemeButton({ @@ -9,11 +9,11 @@ export function ThemeButton({ theme }: { active: boolean; - onChoose: (themeName: string) => void; + onChoose: (theme: Theme) => void; theme: Theme; }) { const handleClick = useCallback(() => { - onChoose(theme.name); + onChoose(theme); }, []); return ( diff --git a/.storybook/theme-settings/ThemeProvider.tsx b/.storybook/theme-settings/ThemeProvider.tsx new file mode 100644 index 00000000..a537d3c9 --- /dev/null +++ b/.storybook/theme-settings/ThemeProvider.tsx @@ -0,0 +1,41 @@ +import { useAddonState } from '@storybook/client-api'; +import { DecoratorFn } from '@storybook/react'; +import React from 'react'; +import { createGlobalStyle, ThemeProvider } from 'styled-components'; + +import themes from '../../src/common/themes/index'; +import { SCALE } from '../../src/common/constants'; +import { Theme } from '../../src/common/themes/types'; +import { THEMES_ID } from './constants'; + +const GlobalScaledFont = createGlobalStyle` + html { + font-size: ${({ theme }: { theme: Theme }) => + (theme?.scale ?? SCALE) * 2 + 12}px; + } +`; + +export const withThemeSettings: DecoratorFn = story => { + const [themeName] = useAddonState(`${THEMES_ID}/themeName`, 'original'); + const [themeScale] = useAddonState( + `${THEMES_ID}/themeScale`, + themes.original.scale + ); + const [themeShadow] = useAddonState( + `${THEMES_ID}/themeShadow`, + themes.original.shadow + ); + + const theme: Theme = { + ...themes[themeName], + scale: themeScale, + shadow: themeShadow + }; + + return ( + + + {story()} + + ); +}; diff --git a/.storybook/theme-settings/ThemeSettings.tsx b/.storybook/theme-settings/ThemeSettings.tsx new file mode 100644 index 00000000..4f1f6b18 --- /dev/null +++ b/.storybook/theme-settings/ThemeSettings.tsx @@ -0,0 +1,135 @@ +import { useAddonState } from '@storybook/api'; +import React, { useCallback } from 'react'; +import styled, { ThemeProvider } from 'styled-components'; +import { SCALE } from '../../src/common/constants'; + +import themes from '../../src/common/themes'; +import { Checkbox, GroupBox, Slider } from '../../src/index'; +import { Theme } from '../../src/types'; +import { THEMES_ID } from './constants'; +import { ThemeButton } from './ThemeButton'; + +const { + original, + rainyDay, + vaporTeal, + theSixtiesUSA, + olive, + tokyoDark, + rose, + plum, + matrix, + travel, + ...otherThemes +} = themes; + +const themeList = [ + original, + rainyDay, + vaporTeal, + theSixtiesUSA, + olive, + tokyoDark, + rose, + plum, + matrix, + travel, + ...Object.values(otherThemes) +]; + +type ThemesProps = { + active?: boolean; +}; + +const Wrapper = styled.div<{ theme: Theme }>` + padding: 1em; + background-color: ${({ theme }) => theme.material}; +`; + +const ThemesGrid = styled.div` + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + grid-template-rows: repeat(auto-fill, 40px); + gap: 1em; + margin-bottom: 1em; +`; + +const marks = [ + { label: '1', value: 1 }, + { label: '2', value: 2 }, + { label: '3', value: 3 }, + { label: '4', value: 4 }, + { label: '5', value: 5 } +]; + +export function ThemeSettings({ active }: ThemesProps) { + const [themeName, setThemeName] = useAddonState( + `${THEMES_ID}/themeName`, + 'original' + ); + const [themeScale, setThemeScale] = useAddonState( + `${THEMES_ID}/themeScale`, + themes.original.scale + ); + const [themeShadow, setThemeShadow] = useAddonState( + `${THEMES_ID}/themeShadow`, + themes.original.shadow + ); + + const handleChoose = useCallback( + (newTheme: Theme) => { + setThemeName(newTheme.name); + }, + [setThemeName] + ); + + const handleShadowChange = useCallback( + (event: React.ChangeEvent) => { + const checked = (event.target as HTMLInputElement).checked; + setThemeShadow(checked); + }, + [] + ); + + const handleScaleChange = useCallback( + (_: Event | React.SyntheticEvent, value: number) => { + setThemeScale(value); + }, + [] + ); + + if (!active) { + return <>; + } + + return ( + + + {themeList.map(theme => ( + + ))} + + + + + + + + + ); +} diff --git a/.storybook/theme-picker/constants.ts b/.storybook/theme-settings/constants.ts similarity index 100% rename from .storybook/theme-picker/constants.ts rename to .storybook/theme-settings/constants.ts diff --git a/.storybook/theme-picker/register.ts b/.storybook/theme-settings/register.ts similarity index 70% rename from .storybook/theme-picker/register.ts rename to .storybook/theme-settings/register.ts index 6c5d1834..60154c71 100644 --- a/.storybook/theme-picker/register.ts +++ b/.storybook/theme-settings/register.ts @@ -1,17 +1,17 @@ import addons, { makeDecorator, types } from '@storybook/addons'; import { THEMES_ID } from './constants'; -import { ThemeList } from './ThemeList'; +import { ThemeSettings } from './ThemeSettings'; addons.register(THEMES_ID, () => { addons.addPanel(`${THEMES_ID}/panel`, { - title: 'Themes', + title: 'Theme Settings', type: types.PANEL, - render: ThemeList + render: ThemeSettings }); }); export default makeDecorator({ - name: 'withThemesProvider', + name: 'withThemeSettingsProvider', parameterName: 'theme', wrapper: (getStory, context) => getStory(context) }); diff --git a/src/Avatar/Avatar.tsx b/src/Avatar/Avatar.tsx index 89990fd4..c88ab53f 100644 --- a/src/Avatar/Avatar.tsx +++ b/src/Avatar/Avatar.tsx @@ -1,5 +1,6 @@ import React, { forwardRef } from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; +import { styledDimension } from '../common'; import { getSize } from '../common/utils'; import { CommonStyledProps } from '../types'; @@ -28,13 +29,13 @@ const StyledAvatar = styled.div< overflow: hidden; ${({ noBorder, theme }) => !noBorder && - ` - border-top: 2px solid ${theme.borderDark}; - border-left: 2px solid ${theme.borderDark}; - border-bottom: 2px solid ${theme.borderLightest}; - border-right: 2px solid ${theme.borderLightest}; - background: ${theme.material}; - `} + css` + border-top: ${styledDimension(1)} solid ${theme.borderDark}; + border-left: ${styledDimension(1)} solid ${theme.borderDark}; + border-bottom: ${styledDimension(1)} solid ${theme.borderLightest}; + border-right: ${styledDimension(1)} solid ${theme.borderLightest}; + background: ${theme.material}; + `} ${({ src }) => !src && ` diff --git a/src/Button/Button.spec.tsx b/src/Button/Button.spec.tsx index a980b3c1..963df13d 100644 --- a/src/Button/Button.spec.tsx +++ b/src/Button/Button.spec.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { renderWithTheme, theme } from '../../test/utils'; import { blockSizes } from '../common/system'; +import { SCALE } from '../common/constants'; import { Button } from './Button'; @@ -10,6 +11,12 @@ const defaultProps = { children: 'click me' }; +const expectedBlockSizes = { + sm: `${blockSizes.sm * SCALE}px`, + md: `${blockSizes.md * SCALE}px`, + lg: `${blockSizes.lg * SCALE}px` +}; + describe('