diff --git a/static/app/stories/view/index.tsx b/static/app/stories/view/index.tsx index 6641528a1e28c5..f324314ac9ef0a 100644 --- a/static/app/stories/view/index.tsx +++ b/static/app/stories/view/index.tsx @@ -1,3 +1,5 @@ +import {Fragment, type PropsWithChildren} from 'react'; +import {css, Global, useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import {Alert} from 'sentry/components/core/alert'; @@ -12,6 +14,7 @@ import RouteAnalyticsContextProvider from 'sentry/views/routeAnalyticsContextPro import {StoryLanding} from './landing'; import {StoryExports} from './storyExports'; import {StoryHeader} from './storyHeader'; +import {useStoryDarkModeTheme} from './useStoriesDarkMode'; import {useStoriesLoader} from './useStoriesLoader'; export default function Stories() { @@ -25,18 +28,11 @@ function isLandingPage(location: ReturnType) { function StoriesLanding() { return ( - - - - - - - - - - - - + + + + + ); } @@ -47,44 +43,90 @@ function StoryDetail() { const story = useStoriesLoader({files}); return ( - - - - - - - - - - {story.isLoading ? ( - - - - ) : story.isError ? ( - - - - {story.error.name}: {story.error.message} - - - - ) : story.isSuccess ? ( - - {story.data.map(s => { - return ; - })} - - ) : ( - - The file you selected does not export a story. - - )} - - - + + {story.isLoading ? ( + + + + ) : story.isError ? ( + + + + {story.error.name}: {story.error.message} + + + + ) : story.isSuccess ? ( + + {story.data.map(s => { + return ; + })} + + ) : ( + + The file you selected does not export a story. + + )} + ); } +function StoriesLayout(props: PropsWithChildren) { + return ( + + + + + + + + + + + + {props.children} + + + + + ); +} + +function GlobalStoryStyles() { + const theme = useTheme(); + const darkTheme = useStoryDarkModeTheme(); + const location = useLocation(); + const isIndex = isLandingPage(location); + const styles = css` + /* match body background with header story styles */ + body { + background-color: ${isIndex + ? darkTheme.tokens.background.secondary + : theme.tokens.background.secondary}; + } + /* fixed position color block to match overscroll color to story background */ + body::after { + content: ''; + display: block; + position: fixed; + inset: 0; + top: unset; + background-color: ${theme.tokens.background.primary}; + height: 50vh; + z-index: -1; + pointer-events: none; + } + /* adjust position of global .messages-container element */ + .messages-container { + margin-top: 52px; + margin-left: 256px; + z-index: ${theme.zIndex.header}; + background: ${theme.tokens.background.primary}; + } + `; + return ; +} + const Layout = styled('div')` background: ${p => p.theme.tokens.background.primary}; --stories-grid-space: 0; diff --git a/static/app/stories/view/landing/index.tsx b/static/app/stories/view/landing/index.tsx index 3aa9a19c6e0605..108c96cfbee5e0 100644 --- a/static/app/stories/view/landing/index.tsx +++ b/static/app/stories/view/landing/index.tsx @@ -1,6 +1,6 @@ import type {PropsWithChildren} from 'react'; -import {Fragment, useMemo} from 'react'; -import {ThemeProvider, useTheme} from '@emotion/react'; +import {Fragment} from 'react'; +import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import performanceWaitingForSpan from 'sentry-images/spot/performance-waiting-for-span.svg'; @@ -11,12 +11,8 @@ import {LinkButton} from 'sentry/components/core/button/linkButton'; import {Flex} from 'sentry/components/core/layout'; import {Link} from 'sentry/components/core/link'; import {IconOpen} from 'sentry/icons'; +import {StoryDarkModeProvider} from 'sentry/stories/view/useStoriesDarkMode'; import {space} from 'sentry/styles/space'; -import type {Theme} from 'sentry/utils/theme'; -// we need the hero to always use values from the dark theme -// eslint-disable-next-line no-restricted-imports -import {darkTheme} from 'sentry/utils/theme'; -import {DO_NOT_USE_darkChonkTheme} from 'sentry/utils/theme/theme.chonk'; import {Colors, Icons, Typography} from './figures'; @@ -49,7 +45,7 @@ const frontmatter = { export function StoryLanding() { return ( - + @@ -74,7 +70,7 @@ export function StoryLanding() { /> - + @@ -132,17 +128,6 @@ function Border() { ); } -function AlwaysDarkThemeProvider(props: PropsWithChildren) { - const theme = useTheme(); - - const localThemeValue = useMemo( - () => (theme.isChonk ? DO_NOT_USE_darkChonkTheme : darkTheme), - [theme] - ); - - return {props.children}; -} - const TitleEmphasis = styled('em')` font-style: normal; display: inline-block; @@ -151,13 +136,13 @@ const TitleEmphasis = styled('em')` `; const Hero = styled('div')` - width: 100vw; - padding: 48px 16px; + padding: 48px 0; gap: ${space(4)}; display: flex; align-items: center; - background: ${p => p.theme.tokens.background.tertiary}; + background: ${p => p.theme.tokens.background.secondary}; color: ${p => p.theme.tokens.content.primary}; + border-bottom: 1px solid ${p => p.theme.tokens.border.primary}; h1 { font-size: 36px; @@ -174,15 +159,13 @@ const Hero = styled('div')` min-width: 320px; height: auto; } - - @media (min-width: ${p => p.theme.breakpoints.md}) { - padding: 48px 92px; - } `; const Container = styled('div')` - max-width: 1134px; - width: calc(100vw - 32px); + max-width: 1080px; + width: 100%; + flex-grow: 1; + flex-shrink: 1; margin-inline: auto; display: flex; flex-direction: column; @@ -198,13 +181,9 @@ const Container = styled('div')` `; const CardGrid = styled('div')` - display: grid; - grid-template-columns: minmax(0, 1fr); + display: flex; + flex-flow: row wrap; gap: ${space(2)}; - - @media (min-width: ${p => p.theme.breakpoints.md}) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } `; interface CardProps { @@ -224,12 +203,13 @@ const CardLink = styled(Link)` color: ${p => p.theme.tokens.content.primary}; display: flex; flex-direction: column; - width: 100%; - height: 256px; + flex-grow: 1; + width: calc(100% * 3 / 5); + aspect-ratio: 2/1; padding: ${space(2)}; border: 1px solid ${p => p.theme.tokens.border.muted}; border-radius: ${p => p.theme.borderRadius}; - transition: initial 80ms ease-out; + transition: all 80ms ease-out; transition-property: background-color, color, border-color; &:hover, @@ -245,15 +225,20 @@ const CardLink = styled(Link)` max-width: 509px; max-height: 170px; } + + @media screen and (min-width: ${p => p.theme.breakpoints.md}) { + max-width: calc(50% - 32px); + } `; const CardTitle = styled('span')` margin: 0; - margin-top: ${space(1)}; + margin-top: auto; + margin-bottom: ${space(2)}; + padding: ${space(1)} ${space(2)}; width: 100%; height: 24px; font-size: 24px; - padding: ${space(1)} ${space(2)}; font-weight: ${p => p.theme.fontWeight.bold}; color: currentColor; `; diff --git a/static/app/stories/view/useStoriesDarkMode.tsx b/static/app/stories/view/useStoriesDarkMode.tsx new file mode 100644 index 00000000000000..36e10bd98d26c2 --- /dev/null +++ b/static/app/stories/view/useStoriesDarkMode.tsx @@ -0,0 +1,32 @@ +import type {PropsWithChildren} from 'react'; +import {type Theme, ThemeProvider, useTheme} from '@emotion/react'; + +// these utils are for stories that have forced dark mode +// which is a very specific sanctioned use case +// eslint-disable-next-line no-restricted-imports +import {darkTheme} from 'sentry/utils/theme'; +import {DO_NOT_USE_darkChonkTheme} from 'sentry/utils/theme/theme.chonk'; + +/** + * Access the raw values from the dark theme + * + * ⚠️ DO NOT USE OUTSIDE OF STORIES + */ +export const useStoryDarkModeTheme = (): Theme => { + const theme = useTheme(); + if (theme.isChonk) { + return DO_NOT_USE_darkChonkTheme as any; + } + return darkTheme; +}; + +/** + * Forces all `children` to be rendered in dark mode, + * regardless of user preferences. + * + * ⚠️ DO NOT USE OUTSIDE OF STORIES + */ +export function StoryDarkModeProvider(props: PropsWithChildren) { + const theme = useStoryDarkModeTheme(); + return {props.children}; +}