diff --git a/code/ui/.storybook/main.ts b/code/ui/.storybook/main.ts index 23a70dc16661..541b0f2c69e1 100644 --- a/code/ui/.storybook/main.ts +++ b/code/ui/.storybook/main.ts @@ -50,6 +50,7 @@ const config: StorybookConfig = { '@storybook/addon-essentials', '@storybook/addon-interactions', '@storybook/addon-storysource', + '@storybook/addon-designs', ], framework: { name: '@storybook/react-vite', diff --git a/code/ui/components/src/clipboard/ClipboardCode.tsx b/code/ui/components/src/clipboard/ClipboardCode.tsx new file mode 100644 index 000000000000..577a6674f5d4 --- /dev/null +++ b/code/ui/components/src/clipboard/ClipboardCode.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { color, styled, typography } from '@storybook/theming'; + +const Code = styled.pre` + line-height: 18px; + padding: 11px 1rem; + white-space: pre-wrap; + background: rgba(0, 0, 0, 0.05); + color: ${color.darkest}; + border-radius: 3px; + margin: 1rem 0; + width: 100%; + display: block; + overflow: hidden; + font-family: ${typography.fonts.mono}; + font-size: ${typography.size.s2 - 1}px; +`; + +interface ClipboardCodeProps { + code: string; +} + +export const ClipboardCode = ({ code, ...props }: ClipboardCodeProps) => ( + + {code} + +); diff --git a/code/ui/components/src/index.ts b/code/ui/components/src/index.ts index c654094f1b61..292a9c7067dd 100644 --- a/code/ui/components/src/index.ts +++ b/code/ui/components/src/index.ts @@ -26,7 +26,6 @@ export { Span } from './typography/elements/Span'; export { Table } from './typography/elements/Table'; export { TT } from './typography/elements/TT'; export { UL } from './typography/elements/UL'; - export { Badge } from './Badge/Badge'; // Typography @@ -85,6 +84,8 @@ export * from './typography/ResetWrapper'; export { withReset, codeCommon } from './typography/lib/common'; +export { ClipboardCode } from './clipboard/ClipboardCode'; + // eslint-disable-next-line prefer-destructuring export const components = typography.components; diff --git a/code/ui/manager/package.json b/code/ui/manager/package.json index 1b4dd1152305..cb86ca81fd9f 100644 --- a/code/ui/manager/package.json +++ b/code/ui/manager/package.json @@ -51,6 +51,7 @@ }, "devDependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", + "@storybook/addon-designs": "^7.0.0", "@storybook/addons": "7.1.0-beta.1", "@storybook/api": "7.1.0-beta.1", "@storybook/channel-postmessage": "7.1.0-beta.1", diff --git a/code/ui/manager/src/settings/about.stories.jsx b/code/ui/manager/src/settings/about.stories.jsx deleted file mode 100644 index 7bb06a45ad0b..000000000000 --- a/code/ui/manager/src/settings/about.stories.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { actions as createActions } from '@storybook/addon-actions'; - -import { AboutScreen } from './about'; - -const info = { - plain: `- upgrade webpack & babel to latest\n- new addParameters and third argument to .add to pass data to addons\n- added the ability to theme storybook\n- improved ui for mobile devices\n- improved performance of addon-knobs`, -}; - -export default { - component: AboutScreen, - title: 'Settings/AboutScreen', - decorators: [ - (storyFn) => ( -
- {storyFn()} -
- ), - ], -}; - -const actions = createActions('onClose'); - -export const UpToDate = () => ( - -); - -export const OldVersionRaceCondition = () => ( - -); - -export const NewVersionRequired = () => ( - -); - -export const FailedToFetchNewVersion = () => ( - -); diff --git a/code/ui/manager/src/settings/about.stories.tsx b/code/ui/manager/src/settings/about.stories.tsx new file mode 100644 index 000000000000..67fcabcf6667 --- /dev/null +++ b/code/ui/manager/src/settings/about.stories.tsx @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; +import { AboutScreen } from './about'; + +const meta = { + component: AboutScreen, + title: 'Settings/AboutScreen', + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + current: { + version: '7.0.1', + }, + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/ur4kydUbRqdDyfoZWzdiIw/Storybook-app?type=design&node-id=9564-120444&mode=design&t=0TPINZFpwgFQFQeX-4', + }, + }, +}; diff --git a/code/ui/manager/src/settings/about.tsx b/code/ui/manager/src/settings/about.tsx index fccd4ee15111..c6f3552d6117 100644 --- a/code/ui/manager/src/settings/about.tsx +++ b/code/ui/manager/src/settings/about.tsx @@ -1,114 +1,172 @@ +/* eslint-disable no-nested-ternary */ import type { FC } from 'react'; -import React from 'react'; -import semver from 'semver'; -import { styled } from '@storybook/theming'; +import React, { useState } from 'react'; +import { styled, typography } from '@storybook/theming'; import type { State } from '@storybook/manager-api'; -import { StorybookIcon, SyntaxHighlighter, DocumentWrapper } from '@storybook/components'; - -import SettingsFooter from './SettingsFooter'; +import { Button as BaseButton, Icons, StorybookIcon } from '@storybook/components'; const Header = styled.header(({ theme }) => ({ - marginBottom: 20, - fontSize: theme.typography.size.m3, + marginBottom: 32, + fontSize: theme.typography.size.l2, color: theme.base === 'light' ? theme.color.darkest : theme.color.lightest, fontWeight: theme.typography.weight.bold, alignItems: 'center', display: 'flex', '> svg': { - height: 32, + height: 48, width: 'auto', marginRight: 8, }, })); -const UpdateMessage = styled.div<{ status: 'positive' | 'negative' | string }>( - ({ status, theme }) => { - if (status === 'positive') { - return { background: theme.background.positive, color: theme.color.positiveText }; - } - if (status === 'negative') { - return { background: theme.background.negative, color: theme.color.negativeText }; - } - return { - background: theme.base === 'light' ? '#EAF3FC' : theme.color.darkest, - color: theme.base === 'light' ? theme.color.darkest : theme.defaultText, - }; +const Container = styled.div({ + display: `flex`, + alignItems: 'center', + justifyContent: 'center', + height: 'calc(100% - 40px)', + flexDirection: 'column', +}); + +export const UpgradeBlock = styled.div(({ theme }) => { + return { + border: '1px solid', + borderRadius: 5, + padding: 20, + margin: 20, + maxWidth: 400, + borderColor: theme.color.border, + fontSize: theme.typography.size.s2, + }; +}); + +const Code = styled.pre` + background: rgba(0, 0, 0, 0.05); + font-size: ${typography.size.s2 - 1}px; + margin: 4px 0 16px; +`; + +const Footer = styled.div(({ theme }) => ({ + marginBottom: 24, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + color: theme.base === 'light' ? theme.color.dark : theme.color.lightest, + fontWeight: theme.typography.weight.regular, + fontSize: theme.typography.size.s2, +})); + +export const Button = styled(BaseButton)(({ theme }) => ({ + '&&': { + borderRadius: 4, + fontSize: '13px', + lineHeight: '14px', + color: theme.base === 'light' ? theme.color.darker : theme.color.lightest, + padding: '9px 12px', + svg: { + marginRight: 6, + }, + }, +})); + +const Tab = styled(Button)<{ active: boolean }>(({ theme, active }) => ({ + '&&': { + padding: 2, + paddingRight: 8, + margin: 0, + color: active + ? theme.color.secondary + : theme.base === 'light' + ? theme.color.dark + : theme.color.lightest, }, +})); - ({ theme }) => ({ - fontWeight: theme.typography.weight.bold, +const BlueLinkButton = styled(Button)(({ theme }) => ({ + '&&': { + padding: 0, + paddingRight: 8, + margin: 0, + color: theme.color.secondary, fontSize: theme.typography.size.s2, - padding: '10px 20px', - marginBottom: 24, - borderRadius: theme.appBorderRadius, - border: `1px solid ${theme.appBorderColor}`, - textAlign: 'center', - }) -); - -const Upgrade = styled.div(({ theme }) => ({ - marginTop: 20, - borderTop: `1px solid ${theme.appBorderColor}`, + fontWeight: theme.typography.weight.regular, + }, })); -const Container = styled.div({ - padding: `3rem 20px`, - maxWidth: 600, - margin: '0 auto', -}); +export const StyledA = styled.a(({ theme }) => ({ + '&&': { + textDecoration: 'none', + fontWeight: theme.typography.weight.bold, + color: theme.base === 'light' ? theme.color.dark : theme.color.light, + }, + '&:hover': { + color: theme.base === 'light' ? theme.color.darkest : theme.color.lightest, + }, +})); const AboutScreen: FC<{ - latest: State['versions']['latest']; current: State['versions']['current']; -}> = ({ latest = null, current }) => { - const canUpdate = latest && semver.gt(latest.version, current.version); - - let updateMessage; - if (latest) { - if (canUpdate) { - updateMessage = ( - - Storybook {latest.version} is available. Upgrade from {current.version} now. - - ); - } else { - updateMessage = ( - Looking good! You're up to date. - ); - } - } else { - updateMessage = ( - - Oops! The latest version of Storybook couldn't be fetched. - - ); - } - + onNavigateToWhatsNew?: () => void; +}> = ({ current, onNavigateToWhatsNew }) => { + const [activeTab, setActiveTab] = useState<'npm' | 'pnpm'>('npm'); return ( +
- - Storybook {current.version} + Storybook
+ + You are on Storybook {current.version} +

Run the following script to check for updates and upgrade to the latest version.

+
+ setActiveTab('npm')}> + npm + + setActiveTab('pnpm')}> + pnpm + +
+ + + {activeTab === 'npm' + ? 'npx storybook@latest upgrade' + : 'pnpm dlx storybook@latest upgrade'} + + {onNavigateToWhatsNew && ( + + See what's new in Storybook + + )} +
+ +
+
+
+ - {updateMessage} - - {canUpdate && ( - - -

- Upgrade all Storybook packages to latest: -

- - npx storybook@latest upgrade - -
-
- )} - - + +
+
+ Open source software maintained by{' '} + Chromatic and the{' '} + + Storybook Community + +
+
); }; diff --git a/code/ui/manager/src/settings/about_page.tsx b/code/ui/manager/src/settings/about_page.tsx index 34640957ad18..e20d89d5362a 100644 --- a/code/ui/manager/src/settings/about_page.tsx +++ b/code/ui/manager/src/settings/about_page.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react'; -import React, { Component } from 'react'; +import React, { Component, useCallback } from 'react'; -import { type API, useStorybookApi } from '@storybook/manager-api'; +import { type API, useStorybookApi, useStorybookState } from '@storybook/manager-api'; import { AboutScreen } from './about'; @@ -20,10 +20,19 @@ class NotificationClearer extends Component<{ api: API; notificationId: string } const AboutPage: FC = () => { const api = useStorybookApi(); + const state = useStorybookState(); + const onNavigateToWhatsNew = useCallback(() => { + api.changeSettingsTab('whats-new'); + }, [api]); return ( - + ); };