Skip to content

Commit eb2afee

Browse files
Initial commit
0 parents  commit eb2afee

File tree

1,055 files changed

+67765
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,055 files changed

+67765
-0
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Build & deploy Storybook
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: "pages"
16+
cancel-in-progress: true
17+
18+
jobs:
19+
build:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Set up Node
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: "20"
29+
cache: "npm"
30+
31+
- name: Install dependencies
32+
run: npm ci
33+
34+
- name: Build Storybook
35+
run: npm run build-storybook --output-dir storybook-static
36+
37+
- name: Upload Pages artifact
38+
uses: actions/upload-pages-artifact@v3
39+
with:
40+
path: ./storybook-static
41+
42+
deploy:
43+
needs: build
44+
runs-on: ubuntu-latest
45+
environment:
46+
name: github-pages
47+
url: ${{ steps.deployment.outputs.page_url }}
48+
steps:
49+
- name: Deploy to GitHub Pages
50+
id: deployment
51+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
*.egg-info
2+
*.tgz
3+
*.tsbuildinfo
4+
.DS_STORE
5+
6+
__pycache__
7+
.egg-info
8+
.git
9+
.mypy_cache
10+
.next
11+
.pytest_cache
12+
.rollup.cache
13+
.ruff_cache
14+
15+
dist
16+
node_modules

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.mdx
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// organize-imports-ignore
2+
// @ts-expect-error -- React import is required here
3+
import React from "react"
4+
import { IconButton } from "@storybook/components"
5+
import { addons, types, useStorybookApi } from "@storybook/manager-api"
6+
7+
addons.register("oai/back-to-docs", () => {
8+
addons.add("oai/back-to-docs/tool", {
9+
title: "Back to docs",
10+
type: types.TOOL,
11+
12+
/* only show the button when someone is *inside* story view */
13+
match: ({ viewMode }) => viewMode === "story",
14+
15+
render: () => <BackToDocs />,
16+
})
17+
})
18+
19+
export const BackToDocs = () => {
20+
const api = useStorybookApi()
21+
const story = api.getCurrentStoryData() // active story
22+
23+
if (!story) return null
24+
25+
// transform "...--base" → "...--docs"
26+
const docsId = story.id.replace(/--[^-]+$/, "--docs")
27+
28+
// If the docs page actually exists, render a button
29+
if (!api.getData(docsId, story.refId)) return null
30+
31+
return (
32+
<span className="story-back-link">
33+
<IconButton onClick={() => api.selectStory(docsId, undefined, { viewMode: "docs" })}>
34+
<ArrowLeft /> Back to docs
35+
</IconButton>
36+
</span>
37+
)
38+
}
39+
40+
export const ArrowLeft = () => (
41+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
42+
<path
43+
d="M5.354 2.146a.5.5 0 010 .708L1.707 6.5H13.5a.5.5 0 010 1H1.707l3.647 3.646a.5.5 0 01-.708.708l-4.5-4.5a.5.5 0 010-.708l4.5-4.5a.5.5 0 01.708 0z"
44+
fill="currentColor"
45+
></path>
46+
</svg>
47+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// organize-imports-ignore
2+
// @ts-expect-error -- React import is required here
3+
import React from "react"
4+
import { IconButton } from "@storybook/components"
5+
import { type API, useGlobals } from "@storybook/manager-api"
6+
import { memo, useLayoutEffect, useState } from "react"
7+
import { type Theme } from "./constants"
8+
import { THEMES } from "./themes"
9+
import { applyManagerThemeClass, getThemeStore, setThemeStore } from "./themeStore"
10+
11+
export const Tool = memo(({ api }: { api: API }) => {
12+
const [, updateGlobals] = useGlobals()
13+
const [theme, setTheme] = useState<Theme>(getThemeStore())
14+
15+
const toggleTheme = () => {
16+
const nextTheme = theme === "light" ? "dark" : "light"
17+
// Enable stories and docs to retrieve this value
18+
updateGlobals({ theme: nextTheme })
19+
// Visually apply the next theme
20+
applyTheme(nextTheme)
21+
}
22+
23+
const applyTheme = (themeToApply: Theme) => {
24+
setTheme(themeToApply)
25+
// Update persistent storage
26+
setThemeStore(themeToApply)
27+
// Update manager frame class
28+
applyManagerThemeClass(themeToApply)
29+
// Update global theme config styles
30+
api.setOptions({ theme: THEMES[themeToApply] })
31+
}
32+
33+
useLayoutEffect(() => {
34+
// Ensure the correct class is applied to manager state on load
35+
applyManagerThemeClass(theme)
36+
// eslint-disable-next-line react-hooks/exhaustive-deps
37+
}, [])
38+
39+
return (
40+
<IconButton
41+
active={false}
42+
title={`Switch to ${theme === "light" ? "Dark" : "Light"} Mode`}
43+
onClick={toggleTheme}
44+
>
45+
{theme === "light" ? <LightMode /> : <DarkMode />}
46+
</IconButton>
47+
)
48+
})
49+
50+
// Re-imported icons from our library, since we don't have the SVG Loader
51+
const DarkMode = () => (
52+
<svg
53+
xmlns="http://www.w3.org/2000/svg"
54+
width="16px"
55+
height="16px"
56+
fill="currentColor"
57+
viewBox="0 0 24 24"
58+
>
59+
<path
60+
fillRule="evenodd"
61+
d="M12.784 2.47a1 1 0 0 1 .047.975A8 8 0 0 0 20 15h.057a1 1 0 0 1 .902 1.445A10 10 0 0 1 12 22C6.477 22 2 17.523 2 12c0-5.499 4.438-9.961 9.928-10a1 1 0 0 1 .856.47ZM10.41 4.158a8 8 0 1 0 7.942 12.707C13.613 16.079 10 11.96 10 7c0-.986.143-1.94.41-2.842Z"
62+
clipRule="evenodd"
63+
></path>
64+
</svg>
65+
)
66+
67+
const LightMode = () => (
68+
<svg
69+
xmlns="http://www.w3.org/2000/svg"
70+
width="16px"
71+
height="16px"
72+
fill="currentColor"
73+
viewBox="0 0 24 24"
74+
>
75+
<path
76+
fillRule="evenodd"
77+
d="M12 1a1 1 0 0 1 1 1v1a1 1 0 1 1-2 0V2a1 1 0 0 1 1-1ZM1 12a1 1 0 0 1 1-1h1a1 1 0 1 1 0 2H2a1 1 0 0 1-1-1Zm19 0a1 1 0 0 1 1-1h1a1 1 0 1 1 0 2h-1a1 1 0 0 1-1-1Zm-8 8a1 1 0 0 1 1 1v1a1 1 0 1 1-2 0v-1a1 1 0 0 1 1-1Zm0-12a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm-6 4a6 6 0 1 1 12 0 6 6 0 0 1-12 0Zm-.364-7.778a1 1 0 1 0-1.414 1.414l.707.707A1 1 0 0 0 6.343 4.93l-.707-.707ZM4.222 18.364a1 1 0 1 0 1.414 1.414l.707-.707a1 1 0 1 0-1.414-1.414l-.707.707ZM17.657 4.929a1 1 0 1 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-.707.707Zm1.414 12.728a1 1 0 1 0-1.414 1.414l.707.707a1 1 0 0 0 1.414-1.414l-.707-.707Z"
78+
clipRule="evenodd"
79+
></path>
80+
</svg>
81+
)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type Theme = "light" | "dark"
2+
export const DEFAULT_THEME: Theme = "light"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// organize-imports-ignore
2+
// @ts-expect-error -- React import is required here
3+
import React from "react"
4+
import { addons, types } from "@storybook/manager-api"
5+
import { THEMES } from "./themes"
6+
import { getThemeStore } from "./themeStore"
7+
import { Tool } from "./Tool"
8+
9+
export const init = () => {
10+
// Get initial theme from storage
11+
const initialTheme = getThemeStore()
12+
13+
// Apply initial theme
14+
addons.setConfig({
15+
theme: {
16+
...THEMES[initialTheme],
17+
},
18+
})
19+
20+
// Register the toolbar action
21+
addons.register("oai-storybook/theme-toggle", (api) => {
22+
addons.add("oai-storybook/theme-toggle", {
23+
title: "Theme toggle",
24+
type: types.TOOL,
25+
match: ({ viewMode }) => viewMode === "story" || viewMode === "docs",
26+
render: () => <Tool api={api} />,
27+
})
28+
})
29+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export type { Theme } from "./constants"
2+
import { DEFAULT_THEME, type Theme } from "./constants"
3+
4+
const STORAGE_KEY = "sb-addon-oai-theme-1"
5+
6+
const getThemeFromUrl = (urlString: string): string | null => {
7+
const url = new URL(urlString)
8+
const params = url.searchParams
9+
const globals = params.get("globals")
10+
if (globals) {
11+
const pairs = globals.split(";")
12+
for (const pair of pairs) {
13+
const [key, value] = pair.split(":")
14+
if (key === "theme") {
15+
return value
16+
}
17+
}
18+
}
19+
return null
20+
}
21+
22+
export const setThemeStore = (theme: Theme) => {
23+
window.localStorage.setItem(STORAGE_KEY, theme)
24+
}
25+
26+
export const getThemeStore = (): Theme => {
27+
const themeFromURL = getThemeFromUrl(window.location.href)
28+
if (themeFromURL === "light" || themeFromURL === "dark") {
29+
setThemeStore(themeFromURL)
30+
return themeFromURL
31+
}
32+
33+
const themeFromStorage = window.localStorage.getItem(STORAGE_KEY) as Theme | undefined
34+
return themeFromStorage || DEFAULT_THEME
35+
}
36+
37+
export const applyManagerThemeClass = (nextTheme: Theme) => {
38+
const htmlTag = document.documentElement
39+
htmlTag.setAttribute("data-theme", nextTheme)
40+
htmlTag.style.colorScheme = nextTheme
41+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { create, themes, type ThemeVars } from "@storybook/theming"
2+
import { type Theme } from "./constants"
3+
4+
const light = create({
5+
base: "light",
6+
// Logo
7+
brandTitle: "Apps SDK UI",
8+
brandImage: "https://openai.github.io/apps-sdk-ui/logo-storybook.svg",
9+
brandUrl: "https://developers.openai.com",
10+
brandTarget: "_self",
11+
12+
// Typography
13+
fontBase: `ui-sans-serif, -apple-system, system-ui, "Segoe UI", "Noto Sans", "Helvetica",
14+
"Arial", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif`,
15+
fontCode: `ui-monospace, "SFMono-Regular", "SF Mono", "Menlo", "Monaco", "Consolas",
16+
"Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace`,
17+
18+
// Variables
19+
colorPrimary: "#3A10E5",
20+
colorSecondary: "#585C6D",
21+
22+
// UI
23+
appBg: "#ffffff",
24+
appContentBg: "#ffffff",
25+
appPreviewBg: "#ffffff",
26+
appBorderColor: "#ededed",
27+
appBorderRadius: 6,
28+
29+
// Toolbar default and active colors
30+
barTextColor: "#6e6e80",
31+
barBg: "#ffffff",
32+
})
33+
34+
const dark = create({
35+
...themes.dark,
36+
// Logo
37+
brandTitle: "Apps SDK UI",
38+
brandImage: "https://openai.github.io/apps-sdk-ui/logo-storybook-dark.svg",
39+
brandUrl: "https://platform.openai.com",
40+
brandTarget: "_self",
41+
42+
// Typography
43+
fontBase: `ui-sans-serif, -apple-system, system-ui, "Segoe UI", "Noto Sans", "Helvetica",
44+
"Arial", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif`,
45+
fontCode: `ui-monospace, "SFMono-Regular", "SF Mono", "Menlo", "Monaco", "Consolas",
46+
"Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace`,
47+
48+
// Variables
49+
colorPrimary: "#3A10E5",
50+
colorSecondary: "#585C6D",
51+
52+
// UI
53+
appBg: "#212121",
54+
appContentBg: "#212121",
55+
appPreviewBg: "#212121",
56+
appBorderColor: "#393939",
57+
appBorderRadius: 6,
58+
59+
// Toolbar default and active colors
60+
barTextColor: "#c1c1c1",
61+
barBg: "#212121",
62+
})
63+
64+
export const THEMES: Record<Theme, ThemeVars> = {
65+
light,
66+
dark,
67+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { addons } from "@storybook/manager-api"
2+
3+
// SOURCE: https://github.com/storybookjs/storybook/issues/6339
4+
5+
addons.register("TitleAddon", (api) => {
6+
const STORYBOOK_TITLE = "Apps SDK UI"
7+
8+
const setTitle = () => {
9+
let sectionTitle = ""
10+
try {
11+
const storyData = api.getCurrentStoryData()
12+
// Grab the last piece of the title, which will exclude section headings (e.g., Overview, Concepts, Components)
13+
const pageTitleArr = storyData.title.split("/")
14+
sectionTitle = pageTitleArr[storyData.depth - 1]
15+
16+
// Add story suffix, if present
17+
if (storyData.type === "story") {
18+
sectionTitle += `/${storyData.name}`
19+
}
20+
} catch (e) {
21+
// do nothing
22+
}
23+
24+
document.title = sectionTitle ? `${sectionTitle} - ${STORYBOOK_TITLE}` : STORYBOOK_TITLE
25+
}
26+
27+
return new MutationObserver(() => {
28+
if (document.title.endsWith("Storybook")) {
29+
setTitle()
30+
}
31+
}).observe(document.querySelector("title")!, {
32+
childList: true,
33+
subtree: true,
34+
characterData: true,
35+
})
36+
})

0 commit comments

Comments
 (0)