Skip to content

Commit

Permalink
feat(ui): allow stories to be rendered in the main frame with the sto…
Browse files Browse the repository at this point in the history
…ry option
  • Loading branch information
itsjavi committed Sep 1, 2023
1 parent c0206d1 commit d1baef9
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 60 deletions.
2 changes: 1 addition & 1 deletion packages/storylite/src/app/stores/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
getDefaultToolbarAddons,
getToolbarAddonsAsParameters,
resolveToolbarAddons,
} from '@/components/toolbar/getToolbarAddons'
} from '@/components/addons/getToolbarAddons'
import { SLAddonsMap, SLParameters, SLUserDefinedAddons, StoryMap, StoryModuleMap } from '@/types'

import { StoryLiteActions, StoryLiteState } from './global.types'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
SLParameters,
SLUserDefinedAddons,
} from '../..'
import { isNotEmpty, isTruthy } from '../../utility'
import { isTruthy } from '../../utility'

export function getToolbarAddonsAsParameters(addons: SLAddonsMap): SLParameters {
const parameters: SLParameters = {}
Expand Down Expand Up @@ -56,6 +56,42 @@ function getDefaultLeftToolbarAddons(): AddonSetup[] {
},
]

const getCanvasRoot = (): HTMLElement | null => {
return window.document.querySelector('.storylite-canvas-root:first-of-type')
}

const updateCanvasRootWidth = (rootElement: HTMLElement, newWidth: string | false) => {
if (!(rootElement instanceof HTMLElement)) {
return
}
if (!newWidth) {
rootElement.style.width = ''

return
}

rootElement.style.width = newWidth
}

const updateCanvasRootResponsiveInfo = (rootElement: HTMLElement, newValue: string | false) => {
let infoElement: HTMLDivElement | null = rootElement.querySelector(
'.sl-responsive-info:first-of-type',
)
if (!infoElement) {
infoElement = document.createElement('div')
infoElement.className = 'sl-responsive-info'
rootElement.appendChild(infoElement)
}

if (!newValue) {
infoElement.innerText = ''

return
}

infoElement.innerText = newValue
}

const responsiveAddon: AddonSetup = [
SLCoreAddon.Responsive,
{
Expand All @@ -64,34 +100,27 @@ function getDefaultLeftToolbarAddons(): AddonSetup[] {
stateful: true,
persistent: true,
defaultValue: false,
isVisible: ctx => isNotEmpty(ctx.canvas.element),
onClick: (ctx, [value, setValue]) => {
if (!ctx.canvas.element) {
return
}
const mobileWidth = '375px' // like an iPhone 12 Mini
const oppositeValue = value ? false : mobileWidth

if (!value) {
ctx.canvas.element.style.width = mobileWidth
const div = document.createElement('div')
div.className = 'sl-responsive-info'
div.innerText = mobileWidth
ctx.canvas.element.parentElement?.appendChild(div)
setValue(mobileWidth, { persist: true })

const canvasRoot = getCanvasRoot()
if (!canvasRoot) {
return
}
ctx.canvas.element.style.width = ''
ctx.canvas.element.parentElement?.querySelector('.sl-responsive-info')?.remove()
setValue(false, { persist: true })

updateCanvasRootWidth(canvasRoot, oppositeValue)
updateCanvasRootResponsiveInfo(canvasRoot, oppositeValue)
setValue(oppositeValue, { persist: true })
},
onRender: (ctx, [value]) => {
if (!ctx.canvas.element) {
const canvasRoot = getCanvasRoot()
if (!canvasRoot) {
return
}
if (value) {
ctx.canvas.element.style.width = value as string
}
const _val = value ? String(value) : false
updateCanvasRootWidth(canvasRoot, _val)
updateCanvasRootResponsiveInfo(canvasRoot, _val)
},
isActive: (_, [value]) => isTruthy(value),
} satisfies SLAddonPropsWithoutId<true>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ export function CanvasIframeBody(props: CanvasIframeBodyProps) {
const isStandalone = searchParams.standalone ? true : false

return (
<div className={cn('SandboxLayout', [isStandalone, 'StandaloneSandboxLayout'])} {...rest}>
<div
className={cn('storylite-sandbox-layout', [
isStandalone,
'storylite-sandbox-layout--standalone',
])}
{...rest}
>
<Story storyId={storyId} />
</div>
)
Expand Down
32 changes: 32 additions & 0 deletions packages/storylite/src/components/canvas/CanvasRoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useStoryLiteStore } from '@/app/stores/global'
import { parametersToDataProps } from '@/utility/parametersToDataProps'

import { CanvasIframe } from './CanvasIframe'
import { CanvasIframeBody } from './CanvasIframeBody'

export function CanvasRoot({ storyId }: { storyId?: string }) {
const [stories, defaultStory, currentParams] = useStoryLiteStore(state => [
state.stories,
state.config.defaultStory,
state.parameters,
])

const _storyId = storyId ?? defaultStory

const renderFrame = stories.get(_storyId)?.renderFrame ?? 'iframe'
const paramsDataProps = parametersToDataProps(currentParams)

if (renderFrame === 'root') {
return (
<div id="storylite_canvas_root" className="storylite-canvas-root" {...paramsDataProps}>
<CanvasIframeBody storyId={_storyId} />
</div>
)
}

return (
<div id="storylite_canvas_root" className="storylite-canvas-root" {...paramsDataProps}>
<CanvasIframe storyId={_storyId} />
</div>
)
}
4 changes: 2 additions & 2 deletions packages/storylite/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CanvasIframe } from '@/components/canvas/CanvasIframe'
import { CanvasRoot } from '@/components/canvas/CanvasRoot'
import TopFrameLayout from '@/components/layouts/TopFrameLayout'

export default function HomePage() {
return <CanvasIframe />
return <CanvasRoot />
}

export const Layout = TopFrameLayout
4 changes: 0 additions & 4 deletions packages/storylite/src/pages/preview/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import IframeLayout from '@/components/layouts/IframeLayout'
import { SLStoryPageProps } from '@/types'

export default function StoryPage({ storyId }: SLStoryPageProps) {
if (!storyId) {
return <div>Error: story route segment is empty</div>
}

return <CanvasIframeBody storyId={storyId} />
}

Expand Down
8 changes: 2 additions & 6 deletions packages/storylite/src/pages/stories/story.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { CanvasIframe } from '@/components/canvas/CanvasIframe'
import { CanvasRoot } from '@/components/canvas/CanvasRoot'
import TopFrameLayout from '@/components/layouts/TopFrameLayout'
import { SLStoryPageProps } from '@/types'

export default function StoryPage({ storyId }: SLStoryPageProps) {
if (!storyId) {
return <div>Error: story route segment is empty</div>
}

return <CanvasIframe storyId={storyId} />
return <CanvasRoot storyId={storyId} />
}

export const Layout = TopFrameLayout
2 changes: 1 addition & 1 deletion packages/storylite/src/styles/addons/grid.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[data-sl-grid='true'] .storylite-iframe-element {
[data-sl-grid='true'] .storylite-canvas-root {
background-size:
8rem 8rem,
8rem 8rem,
Expand Down
14 changes: 7 additions & 7 deletions packages/storylite/src/styles/addons/outline.css
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
[data-sl-outline='true']
.storylite-story-component
:where(div, section, article, aside, header, footer, form, table, ul, ol, details) {
outline: 1px solid rgba(255, 87, 126, 0.5) !important;
:where(div, section, article, aside, header, footer, form, table, ul, ol, details, pre) {
outline: 2px solid rgba(255, 87, 126, 0.65) !important;
outline-offset: 0px !important;
}

[data-sl-outline='true'] .storylite-story-component :where(a, button, input, textarea, select) {
outline: 1px solid rgba(255, 230, 9, 0.7) !important;
outline: 2px solid rgba(255, 230, 9, 0.65) !important;
outline-offset: 0px !important;
}

[data-sl-outline='true']
.storylite-story-component
:where(span, i, image, svg, canvas, video, audio) {
outline: 1px solid rgba(87, 238, 49, 0.5) !important;
:where(span, i, image, svg, canvas, video, audio, var, code) {
outline: 2px solid rgba(87, 238, 49, 0.65) !important;
outline-offset: 0px !important;
}

[data-sl-outline='true'] .storylite-story-component :where(li, dt, dd, tr, th, td, summary) {
outline: 1px solid rgba(55, 202, 255, 0.5) !important;
outline: 2px solid rgba(55, 202, 255, 0.65) !important;
outline-offset: 0px !important;
}

[data-sl-outline='true'] .storylite-story-component :where(h1, h2, h3, h4, h5, h6) {
outline: 1px solid rgba(255, 111, 0, 0.5) !important;
outline: 2px solid rgba(255, 111, 0, 0.65) !important;
outline-offset: 0px !important;
}
8 changes: 4 additions & 4 deletions packages/storylite/src/styles/addons/responsive.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[data-sl-responsive*='px'].storylite-iframe-element {
[data-sl-responsive*='px'].storylite-canvas-root {
border: 1px solid rgba(80, 80, 80, 0.5);
box-shadow: 0 0 6px 1px rgba(80, 80, 80, 0.3);
border-radius: 16px;
Expand All @@ -10,11 +10,11 @@

.sl-responsive-info {
position: absolute;
top: 1rem;
right: 1rem;
top: 0.6rem;
right: 0.6rem;
user-select: none;
color: var(--storylite-font-color-muted);
font-size: 0.8rem;
font-size: 0.75rem;
font-family: var(--storylite-font-family-mono);
z-index: 1000;
}
13 changes: 0 additions & 13 deletions packages/storylite/src/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,3 @@
:where(.storylite-iframe:not(.storylite-iframe--default-styles)) {
background: transparent;
}

.storylite-iframe-element {
position: relative;
/* border: 1px solid var(--storylite-border-color-muted); */
border: none;
display: block;
margin: 0 auto;
width: 100%;
height: 100%;
background: transparent;
transition: width 0.3s ease-in-out;
overflow: auto;
}
25 changes: 25 additions & 0 deletions packages/storylite/src/styles/layouts.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,28 @@
color: var(--storylite-font-color);
background-color: var(--storylite-bg-color);
}

.storylite-iframe-element {
position: relative;
/* border: 1px solid var(--storylite-border-color-muted); */
border: none;
display: block;
margin: 0 auto;
width: 100%;
height: 100%;
background: transparent;
transition: width 0.3s ease-in-out;
overflow: auto;
}

.storylite-canvas-root {
position: relative;
width: 100%;
min-height: 100vh;
box-sizing: border-box;
display: flex;
background: transparent;
transition: width 0.3s ease-in-out;
overflow: auto;
margin: 0 auto;
}
13 changes: 12 additions & 1 deletion packages/storylite/src/types/story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type SLStoryContext<P extends SLFunctionComponent = SLFunctionComponent<{
/**
* The basic component data.
*
* NOTE: This property is not common to other CSF implementations.
* > 💅 This is a StoryLite-only feature.
*/
story: BaseStory<P> & { id: string; component: P }
args: Story<P>['args']
Expand Down Expand Up @@ -133,6 +133,17 @@ export interface Story<P extends SLFunctionComponent = SLFunctionComponent<{}>>
* @see https://storybook.js.org/docs/react/api/csf
*/
render?: (args: SLComponentProps<P>, context?: SLStoryContext<P>) => SLNode
/**
* Controls how the story is rendered inside the preview.
*
* - `root` renders the story in the same window / DOM tree as StoryLite's UI.
* - `iframe` renders the story in an iframe, in isolation from StoryLite's UI.
*
* > 💅 This is a StoryLite-only feature.
*
* @default 'iframe'
*/
renderFrame?: 'root' | 'iframe'
/**
* Function to execute after the story is rendered (e.g. running tests).
*
Expand Down

0 comments on commit d1baef9

Please sign in to comment.