@northlight/ui 2.39.4 → 2.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/northlight.d.ts +29 -5
- package/dist/es/northlight.js +2265 -2197
- package/dist/es/northlight.js.map +1 -1
- package/dist/umd/northlight.cjs +2261 -2196
- package/dist/umd/northlight.cjs.map +1 -1
- package/dist/umd/northlight.min.cjs +3 -3
- package/dist/umd/northlight.min.cjs.map +1 -1
- package/package.json +18 -8
- package/sandbox/bin/sandbox.sh +3 -0
- package/sandbox/bin/sandbox.ts +133 -0
- package/sandbox/lib/index.ts +3 -0
- package/sandbox/lib/run-scenarios.ts +60 -0
- package/sandbox/lib/types.ts +35 -0
- package/sandbox/lib/viewer/error-boundary.tsx +34 -0
- package/sandbox/lib/viewer/sandbox-viewer.css +328 -0
- package/sandbox/lib/viewer/sandbox-viewer.tsx +308 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@northlight/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.41.0",
|
|
4
4
|
"description": "Northlight UI library, based on Chakra-ui",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Camphouse",
|
|
@@ -11,17 +11,23 @@
|
|
|
11
11
|
"types": "./dist/es/northlight.d.ts",
|
|
12
12
|
"import": "./dist/es/northlight.js",
|
|
13
13
|
"require": "./dist/umd/northlight.cjs"
|
|
14
|
-
}
|
|
14
|
+
},
|
|
15
|
+
"./sandbox": "./sandbox/lib/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"sandbox": "./sandbox/bin/sandbox.sh"
|
|
15
19
|
},
|
|
16
20
|
"main": "./dist/es/northlight.js",
|
|
17
21
|
"types": "./dist/es/northlight.d.ts",
|
|
18
22
|
"files": [
|
|
19
|
-
"dist"
|
|
23
|
+
"dist",
|
|
24
|
+
"sandbox"
|
|
20
25
|
],
|
|
21
26
|
"scripts": {
|
|
22
27
|
"clean": "rm -rf dist",
|
|
23
|
-
"lint": "eslint --ext ts,tsx lib/ test/",
|
|
28
|
+
"lint": "eslint --ext ts,tsx lib/ test/ sandbox/ northlight-sandbox/",
|
|
24
29
|
"prepublishOnly": "yarn clean && yarn transpile",
|
|
30
|
+
"sandbox": "./sandbox/bin/sandbox.sh northlight-sandbox/scenarios.tsx",
|
|
25
31
|
"test": "yarn mtft test",
|
|
26
32
|
"transpile": "rollup -c",
|
|
27
33
|
"watch": "yarn transpile -w"
|
|
@@ -45,13 +51,13 @@
|
|
|
45
51
|
"@emotion/styled": "^11.11.0",
|
|
46
52
|
"@hookform/resolvers": "^3.3.2",
|
|
47
53
|
"@internationalized/date": "^3.5.0",
|
|
48
|
-
"@northlight/icons": "^1.
|
|
54
|
+
"@northlight/icons": "^1.7.0",
|
|
49
55
|
"@northlight/tokens": "^1.4.7",
|
|
50
56
|
"@react-aria/button": "^3.8.2",
|
|
51
57
|
"@react-aria/calendar": "^3.5.0",
|
|
52
58
|
"@react-aria/checkbox": "^3.11.0",
|
|
53
59
|
"@react-aria/datepicker": "^3.7.0",
|
|
54
|
-
"@react-aria/focus": "
|
|
60
|
+
"@react-aria/focus": "3.14.1",
|
|
55
61
|
"@react-aria/i18n": "^3.8.2",
|
|
56
62
|
"@react-aria/radio": "^3.8.0",
|
|
57
63
|
"@react-aria/utils": "^3.20.0",
|
|
@@ -65,6 +71,7 @@
|
|
|
65
71
|
"@types/react-router-dom": "^5.3.2",
|
|
66
72
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
|
67
73
|
"@types/react-window": "^1.8.5",
|
|
74
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
68
75
|
"chakra-react-select": "4.7.2",
|
|
69
76
|
"framer-motion": "^4",
|
|
70
77
|
"joi": "^17.11.0",
|
|
@@ -78,6 +85,8 @@
|
|
|
78
85
|
"react-virtualized-auto-sizer": "^1.0.20",
|
|
79
86
|
"react-window": "^1.8.9",
|
|
80
87
|
"sinon": "^15.2.0",
|
|
88
|
+
"tsx": "^4.21.0",
|
|
89
|
+
"vite": "^6.0.0",
|
|
81
90
|
"yafu": "^2.1.4"
|
|
82
91
|
},
|
|
83
92
|
"devDependencies": {
|
|
@@ -101,12 +110,13 @@
|
|
|
101
110
|
"rollup-plugin-dts": "^5.2.0",
|
|
102
111
|
"rollup-plugin-esbuild": "^5.0.0",
|
|
103
112
|
"sinon": "^15.2.0",
|
|
104
|
-
"typescript": "^5.2.2"
|
|
113
|
+
"typescript": "^5.2.2",
|
|
114
|
+
"vitest": "^2.0.0"
|
|
105
115
|
},
|
|
106
116
|
"peerDependencies": {
|
|
107
117
|
"react": ">=18.0.0",
|
|
108
118
|
"react-dom": ">=18.0.0",
|
|
109
119
|
"react-router-dom": "^5.0.0"
|
|
110
120
|
},
|
|
111
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "fd559db249b74856dcf44ba2615913607647637a"
|
|
112
122
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-syntax */
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { dirname, resolve } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import react from '@vitejs/plugin-react'
|
|
6
|
+
import { createServer } from 'vite'
|
|
7
|
+
|
|
8
|
+
const DEFAULT_SCENARIOS_PATHS = [ 'sandbox/scenarios.ts', 'sandbox/scenarios.tsx' ]
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
|
|
12
|
+
function findScenariosFile (cwd: string, scenariosArg?: string): string | undefined {
|
|
13
|
+
if (scenariosArg) {
|
|
14
|
+
const absolutePath = resolve(cwd, scenariosArg)
|
|
15
|
+
return existsSync(absolutePath) ? absolutePath : undefined
|
|
16
|
+
}
|
|
17
|
+
for (const path of DEFAULT_SCENARIOS_PATHS) {
|
|
18
|
+
const absolutePath = resolve(cwd, path)
|
|
19
|
+
if (existsSync(absolutePath)) return absolutePath
|
|
20
|
+
}
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function main () {
|
|
25
|
+
const cwd = process.cwd()
|
|
26
|
+
const scenariosArg = process.argv[2]
|
|
27
|
+
const absoluteScenariosPath = findScenariosFile(cwd, scenariosArg)
|
|
28
|
+
|
|
29
|
+
if (!absoluteScenariosPath) {
|
|
30
|
+
console.error('Scenarios file not found.')
|
|
31
|
+
console.error(
|
|
32
|
+
scenariosArg
|
|
33
|
+
? `Please check the path: ${resolve(cwd, scenariosArg)}`
|
|
34
|
+
: `Create one of: ${DEFAULT_SCENARIOS_PATHS.join(', ')}`
|
|
35
|
+
)
|
|
36
|
+
process.exit(1)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sandboxPackageDir = resolve(__dirname, '..')
|
|
40
|
+
|
|
41
|
+
const virtualEntryId = 'virtual:sandbox-entry.tsx'
|
|
42
|
+
const resolvedVirtualEntryId = `\0${virtualEntryId}`
|
|
43
|
+
|
|
44
|
+
const virtualHtmlId = 'virtual:sandbox-html'
|
|
45
|
+
|
|
46
|
+
const server = await createServer({
|
|
47
|
+
configFile: false,
|
|
48
|
+
root: cwd,
|
|
49
|
+
server: {
|
|
50
|
+
port: 5000,
|
|
51
|
+
strictPort: false,
|
|
52
|
+
},
|
|
53
|
+
plugins: [
|
|
54
|
+
react(),
|
|
55
|
+
{
|
|
56
|
+
name: 'sandbox-virtual-entry',
|
|
57
|
+
resolveId (id) {
|
|
58
|
+
if (id === virtualEntryId) return resolvedVirtualEntryId
|
|
59
|
+
if (id === '/' || id === '/index.html') return virtualHtmlId
|
|
60
|
+
return undefined
|
|
61
|
+
},
|
|
62
|
+
load (id) {
|
|
63
|
+
if (id === resolvedVirtualEntryId) {
|
|
64
|
+
return `
|
|
65
|
+
import { createElement } from 'react'
|
|
66
|
+
import { createRoot } from 'react-dom/client'
|
|
67
|
+
import { SandboxViewer } from '${sandboxPackageDir}/lib/viewer/sandbox-viewer.tsx'
|
|
68
|
+
import { scenarios } from '${absoluteScenariosPath}'
|
|
69
|
+
|
|
70
|
+
const root = document.getElementById('root')
|
|
71
|
+
createRoot(root).render(createElement(SandboxViewer, { scenarios }))
|
|
72
|
+
`
|
|
73
|
+
}
|
|
74
|
+
if (id === virtualHtmlId) {
|
|
75
|
+
return `
|
|
76
|
+
<!DOCTYPE html>
|
|
77
|
+
<html>
|
|
78
|
+
<head>
|
|
79
|
+
<meta charset="UTF-8" />
|
|
80
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
81
|
+
<title>Sandbox</title>
|
|
82
|
+
</head>
|
|
83
|
+
<body>
|
|
84
|
+
<div id="root"></div>
|
|
85
|
+
<script type="module" src="/@id/__x00__${virtualEntryId}"></script>
|
|
86
|
+
</body>
|
|
87
|
+
</html>
|
|
88
|
+
`
|
|
89
|
+
}
|
|
90
|
+
return undefined
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'sandbox-html-middleware',
|
|
95
|
+
configureServer (devServer) {
|
|
96
|
+
devServer.middlewares.use((req, res, next) => {
|
|
97
|
+
const url = req.url ?? ''
|
|
98
|
+
const isAsset =
|
|
99
|
+
url.startsWith('/@') || url.startsWith('/node_modules') || url.includes('.')
|
|
100
|
+
|
|
101
|
+
if (!isAsset) {
|
|
102
|
+
const html = `
|
|
103
|
+
<!DOCTYPE html>
|
|
104
|
+
<html>
|
|
105
|
+
<head>
|
|
106
|
+
<meta charset="UTF-8" />
|
|
107
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
108
|
+
<title>Sandbox</title>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<div id="root"></div>
|
|
112
|
+
<script type="module" src="/@id/__x00__${virtualEntryId}"></script>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
115
|
+
`
|
|
116
|
+
devServer.transformIndexHtml(url, html).then((transformed) => {
|
|
117
|
+
res.setHeader('Content-Type', 'text/html')
|
|
118
|
+
res.end(transformed)
|
|
119
|
+
})
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
next()
|
|
123
|
+
})
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await server.listen()
|
|
130
|
+
server.printUrls()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
main()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-syntax */
|
|
2
|
+
import { cleanup, render, screen, within } from '@testing-library/react'
|
|
3
|
+
import userEvent from '@testing-library/user-event'
|
|
4
|
+
import { createElement } from 'react'
|
|
5
|
+
import { MediatoolThemeProvider, theme } from '../../lib'
|
|
6
|
+
import type { ComponentScenarios, PlayContext } from './types.ts'
|
|
7
|
+
|
|
8
|
+
declare const describe: (name: string, fn: () => void) => void
|
|
9
|
+
declare const it: (name: string, fn: () => void | Promise<void>) => void
|
|
10
|
+
|
|
11
|
+
if (typeof window !== 'undefined' && !window.matchMedia) {
|
|
12
|
+
window.matchMedia = (query: string) => ({
|
|
13
|
+
matches: false,
|
|
14
|
+
media: query,
|
|
15
|
+
onchange: null,
|
|
16
|
+
addListener: () => {},
|
|
17
|
+
removeListener: () => {},
|
|
18
|
+
addEventListener: () => {},
|
|
19
|
+
removeEventListener: () => {},
|
|
20
|
+
dispatchEvent: () => false,
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runScenarios (allScenarios: ComponentScenarios[]): void {
|
|
25
|
+
for (const componentScenarios of allScenarios) {
|
|
26
|
+
const { name: componentName, component: defaultComponent, scenarios } = componentScenarios
|
|
27
|
+
|
|
28
|
+
describe(componentName, () => {
|
|
29
|
+
for (const scenario of scenarios) {
|
|
30
|
+
const { name: scenarioName, props, component: scenarioComponent, play } = scenario
|
|
31
|
+
const component = scenarioComponent ?? defaultComponent
|
|
32
|
+
|
|
33
|
+
it(scenarioName, async () => {
|
|
34
|
+
const user = userEvent.setup()
|
|
35
|
+
const { container } = render(
|
|
36
|
+
createElement(
|
|
37
|
+
MediatoolThemeProvider,
|
|
38
|
+
{ theme },
|
|
39
|
+
createElement(component, props as Record<string, unknown>)
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if (play) {
|
|
44
|
+
const context: PlayContext = {
|
|
45
|
+
screen: within(container),
|
|
46
|
+
documentScreen: screen,
|
|
47
|
+
user,
|
|
48
|
+
container,
|
|
49
|
+
}
|
|
50
|
+
await play(context)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cleanup()
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { runScenarios }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { screen, within } from '@testing-library/react'
|
|
2
|
+
import type userEvent from '@testing-library/user-event'
|
|
3
|
+
import type { ComponentType } from 'react'
|
|
4
|
+
|
|
5
|
+
type PlayContext = {
|
|
6
|
+
/** Queries scoped to the scenario container - use for non-portaled content */
|
|
7
|
+
screen: ReturnType<typeof within>
|
|
8
|
+
/** Queries scoped to document.body - use for portaled content (dropdowns, modals) */
|
|
9
|
+
documentScreen: typeof screen
|
|
10
|
+
user: ReturnType<typeof userEvent.setup>
|
|
11
|
+
container: HTMLElement
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type Scenario<P> =
|
|
15
|
+
| {
|
|
16
|
+
name: string
|
|
17
|
+
props: P
|
|
18
|
+
component?: undefined
|
|
19
|
+
play?: (context: PlayContext) => Promise<void>
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
name: string
|
|
23
|
+
props: Record<string, unknown>
|
|
24
|
+
component: ComponentType<any>
|
|
25
|
+
play?: (context: PlayContext) => Promise<void>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ComponentScenarios<P = any> = {
|
|
29
|
+
name: string
|
|
30
|
+
component: ComponentType<P>
|
|
31
|
+
scenarios: Scenario<P>[]
|
|
32
|
+
inline?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type { PlayContext, Scenario, ComponentScenarios }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Component, type ComponentType, type ReactNode, createElement } from 'react'
|
|
2
|
+
|
|
3
|
+
type ErrorBoundaryProps = {
|
|
4
|
+
children: ReactNode
|
|
5
|
+
fallback: ComponentType<{ error: Error }>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type ErrorBoundaryState = {
|
|
9
|
+
error: Error | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
13
|
+
constructor (props: ErrorBoundaryProps) {
|
|
14
|
+
super(props)
|
|
15
|
+
this.state = { error: null }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static getDerivedStateFromError (error: Error): ErrorBoundaryState {
|
|
19
|
+
return { error }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
render () {
|
|
23
|
+
const { error } = this.state
|
|
24
|
+
const { children, fallback } = this.props
|
|
25
|
+
|
|
26
|
+
if (error) {
|
|
27
|
+
return createElement(fallback, { error })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return children
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { ErrorBoundary }
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
body {
|
|
6
|
+
margin: 0;
|
|
7
|
+
background: #f8fafc;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.sandbox-viewer {
|
|
11
|
+
display: flex;
|
|
12
|
+
height: 100vh;
|
|
13
|
+
font-family:
|
|
14
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
15
|
+
color: #1e293b;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.sandbox-sidebar {
|
|
19
|
+
width: 220px;
|
|
20
|
+
background: #ffffff;
|
|
21
|
+
border-right: 1px solid #e2e8f0;
|
|
22
|
+
padding: 20px 0;
|
|
23
|
+
overflow-y: auto;
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.sandbox-search {
|
|
28
|
+
padding: 0 12px 16px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.sandbox-search-input {
|
|
32
|
+
width: 100%;
|
|
33
|
+
padding: 8px 12px;
|
|
34
|
+
font-size: 13px;
|
|
35
|
+
border: 1px solid #e2e8f0;
|
|
36
|
+
border-radius: 6px;
|
|
37
|
+
background: #f8fafc;
|
|
38
|
+
color: #1e293b;
|
|
39
|
+
outline: none;
|
|
40
|
+
transition: all 0.15s ease;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sandbox-search-input::placeholder {
|
|
44
|
+
color: #94a3b8;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sandbox-search-input:focus {
|
|
48
|
+
border-color: #7c3aed;
|
|
49
|
+
background: #ffffff;
|
|
50
|
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sandbox-title {
|
|
54
|
+
font-size: 11px;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
text-transform: uppercase;
|
|
57
|
+
color: #94a3b8;
|
|
58
|
+
padding: 0 20px 16px;
|
|
59
|
+
letter-spacing: 0.08em;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.sandbox-component {
|
|
63
|
+
display: block;
|
|
64
|
+
width: 100%;
|
|
65
|
+
padding: 10px 20px;
|
|
66
|
+
background: none;
|
|
67
|
+
border: none;
|
|
68
|
+
text-align: left;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
color: #475569;
|
|
72
|
+
transition: all 0.15s ease;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.sandbox-component:hover {
|
|
76
|
+
background: #f1f5f9;
|
|
77
|
+
color: #1e293b;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.sandbox-component.selected {
|
|
81
|
+
background: linear-gradient(to right, #ede9fe, #f1f5f9);
|
|
82
|
+
color: #6d28d9;
|
|
83
|
+
font-weight: 500;
|
|
84
|
+
border-left: 3px solid #7c3aed;
|
|
85
|
+
padding-left: 17px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.sandbox-main {
|
|
89
|
+
flex: 1;
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
background: #f8fafc;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.sandbox-tabs {
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
gap: 4px;
|
|
100
|
+
padding: 0 24px;
|
|
101
|
+
background: #ffffff;
|
|
102
|
+
border-bottom: 1px solid #e2e8f0;
|
|
103
|
+
min-height: 52px;
|
|
104
|
+
overflow-x: auto;
|
|
105
|
+
flex-wrap: wrap;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.sandbox-tab {
|
|
109
|
+
padding: 14px 16px;
|
|
110
|
+
min-width: 100px;
|
|
111
|
+
text-align: center;
|
|
112
|
+
background: none;
|
|
113
|
+
border: none;
|
|
114
|
+
border-bottom: 2px solid transparent;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
font-size: 13px;
|
|
117
|
+
font-weight: 500;
|
|
118
|
+
color: #64748b;
|
|
119
|
+
margin-bottom: -1px;
|
|
120
|
+
transition: all 0.15s ease;
|
|
121
|
+
border-radius: 6px 6px 0 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.sandbox-tab:hover {
|
|
125
|
+
color: #475569;
|
|
126
|
+
background: #f8fafc;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sandbox-tab.selected {
|
|
130
|
+
color: #7c3aed;
|
|
131
|
+
border-bottom-color: #7c3aed;
|
|
132
|
+
background: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.sandbox-play {
|
|
136
|
+
margin-left: auto;
|
|
137
|
+
padding: 8px 16px;
|
|
138
|
+
background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
|
|
139
|
+
color: white;
|
|
140
|
+
border: none;
|
|
141
|
+
border-radius: 6px;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
font-size: 13px;
|
|
144
|
+
font-weight: 500;
|
|
145
|
+
transition: all 0.15s ease;
|
|
146
|
+
box-shadow: 0 1px 2px rgba(124, 58, 237, 0.2);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.sandbox-play:hover {
|
|
150
|
+
background: linear-gradient(135deg, #6d28d9 0%, #5b21b6 100%);
|
|
151
|
+
box-shadow: 0 2px 4px rgba(124, 58, 237, 0.3);
|
|
152
|
+
transform: translateY(-1px);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.sandbox-play:disabled {
|
|
156
|
+
background: #cbd5e1;
|
|
157
|
+
cursor: not-allowed;
|
|
158
|
+
box-shadow: none;
|
|
159
|
+
transform: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.sandbox-preview {
|
|
163
|
+
flex: 1;
|
|
164
|
+
padding: 32px;
|
|
165
|
+
overflow: auto;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.sandbox-error {
|
|
169
|
+
background: #fef2f2;
|
|
170
|
+
border: 1px solid #fecaca;
|
|
171
|
+
border-radius: 8px;
|
|
172
|
+
padding: 20px;
|
|
173
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.sandbox-error-title {
|
|
177
|
+
color: #dc2626;
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
font-size: 14px;
|
|
180
|
+
margin-bottom: 12px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.sandbox-error-message {
|
|
184
|
+
color: #b91c1c;
|
|
185
|
+
font-family: ui-monospace, monospace;
|
|
186
|
+
font-size: 13px;
|
|
187
|
+
margin: 0 0 12px;
|
|
188
|
+
padding: 12px;
|
|
189
|
+
background: #fff;
|
|
190
|
+
border-radius: 4px;
|
|
191
|
+
border: 1px solid #fecaca;
|
|
192
|
+
white-space: pre-wrap;
|
|
193
|
+
word-break: break-word;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.sandbox-error-stack {
|
|
197
|
+
color: #6b7280;
|
|
198
|
+
font-family: ui-monospace, monospace;
|
|
199
|
+
font-size: 11px;
|
|
200
|
+
margin: 0;
|
|
201
|
+
padding: 12px;
|
|
202
|
+
background: #fff;
|
|
203
|
+
border-radius: 4px;
|
|
204
|
+
border: 1px solid #e5e7eb;
|
|
205
|
+
white-space: pre-wrap;
|
|
206
|
+
word-break: break-word;
|
|
207
|
+
max-height: 300px;
|
|
208
|
+
overflow: auto;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.sandbox-inline {
|
|
212
|
+
flex: 1;
|
|
213
|
+
padding: 24px;
|
|
214
|
+
overflow: auto;
|
|
215
|
+
display: flex;
|
|
216
|
+
flex-direction: column;
|
|
217
|
+
gap: 16px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.sandbox-inline-scenario {
|
|
221
|
+
display: flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
gap: 16px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.sandbox-inline-label {
|
|
227
|
+
font-size: 12px;
|
|
228
|
+
font-weight: 500;
|
|
229
|
+
color: #64748b;
|
|
230
|
+
text-transform: uppercase;
|
|
231
|
+
letter-spacing: 0.05em;
|
|
232
|
+
width: 100px;
|
|
233
|
+
flex-shrink: 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.sandbox-inline-preview {
|
|
237
|
+
flex: 1;
|
|
238
|
+
background: #ffffff;
|
|
239
|
+
border-radius: 8px;
|
|
240
|
+
padding: 16px;
|
|
241
|
+
border: 1px solid #e2e8f0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.sandbox-play-slot {
|
|
245
|
+
width: 32px;
|
|
246
|
+
flex-shrink: 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.sandbox-play-inline {
|
|
250
|
+
width: 32px;
|
|
251
|
+
height: 32px;
|
|
252
|
+
padding: 0;
|
|
253
|
+
background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
|
|
254
|
+
color: white;
|
|
255
|
+
border: none;
|
|
256
|
+
border-radius: 6px;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
font-size: 12px;
|
|
259
|
+
transition: all 0.15s ease;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.sandbox-play-inline:hover {
|
|
263
|
+
background: linear-gradient(135deg, #6d28d9 0%, #5b21b6 100%);
|
|
264
|
+
transform: translateY(-1px);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.sandbox-play-inline:disabled {
|
|
268
|
+
background: #cbd5e1;
|
|
269
|
+
cursor: not-allowed;
|
|
270
|
+
transform: none;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.sandbox-play-inline.error {
|
|
274
|
+
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.sandbox-play.error {
|
|
278
|
+
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.sandbox-play.error:hover {
|
|
282
|
+
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.sandbox-play-error {
|
|
286
|
+
margin-top: 16px;
|
|
287
|
+
background: #fef2f2;
|
|
288
|
+
border: 1px solid #fecaca;
|
|
289
|
+
border-radius: 8px;
|
|
290
|
+
padding: 16px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.sandbox-play-error-title {
|
|
294
|
+
color: #dc2626;
|
|
295
|
+
font-weight: 600;
|
|
296
|
+
font-size: 13px;
|
|
297
|
+
margin-bottom: 8px;
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
gap: 6px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.sandbox-play-error-title::before {
|
|
304
|
+
content: "✕";
|
|
305
|
+
display: inline-flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
justify-content: center;
|
|
308
|
+
width: 18px;
|
|
309
|
+
height: 18px;
|
|
310
|
+
background: #dc2626;
|
|
311
|
+
color: white;
|
|
312
|
+
border-radius: 50%;
|
|
313
|
+
font-size: 10px;
|
|
314
|
+
font-weight: bold;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.sandbox-play-error-message {
|
|
318
|
+
color: #b91c1c;
|
|
319
|
+
font-family: ui-monospace, monospace;
|
|
320
|
+
font-size: 12px;
|
|
321
|
+
margin: 0;
|
|
322
|
+
padding: 10px;
|
|
323
|
+
background: #fff;
|
|
324
|
+
border-radius: 4px;
|
|
325
|
+
border: 1px solid #fecaca;
|
|
326
|
+
white-space: pre-wrap;
|
|
327
|
+
word-break: break-word;
|
|
328
|
+
}
|