Skip to content

Commit 0fdfbcf

Browse files
authored
Merge pull request #1932 from SUI-Components/theme-mode-studio-dev-runner
feat(packages/sui-studio): add theme mode to dev runner
2 parents cf000c2 + 2fadd2e commit 0fdfbcf

File tree

12 files changed

+142
-54
lines changed

12 files changed

+142
-54
lines changed

packages/sui-studio/src/components/theme-mode/index.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,31 @@ import PropTypes from 'prop-types'
22
import {forwardRef} from 'react'
33
import {DarkModeSwitch} from 'react-toggle-dark-mode'
44

5-
const ThemeMode = forwardRef(({className, onChange, mode, ...props}, ref) => {
5+
const COLORS = ['white', 'black']
6+
7+
const ThemeMode = forwardRef(({className, onChange, mode, size = 24, design, ...props}, ref) => {
8+
const colors = design === 'inverted' ? {light: COLORS[0], dark: COLORS[1]} : {light: COLORS[1], dark: COLORS[0]}
69
return (
710
<button className="theme-mode" aria-label={`set ${mode === 'light' ? 'dark' : 'light'} mode`} ref={ref}>
8-
<DarkModeSwitch onChange={onChange} checked={mode === 'dark'} size={24} />
11+
<DarkModeSwitch
12+
className={className}
13+
onChange={onChange}
14+
checked={mode === 'dark'}
15+
size={size}
16+
sunColor={colors.light}
17+
moonColor={colors.dark}
18+
{...props}
19+
/>
920
</button>
1021
)
1122
})
1223

1324
ThemeMode.propTypes = {
1425
className: PropTypes.string,
1526
onChange: PropTypes.func,
16-
mode: PropTypes.oneOf(['light', 'dark'])
27+
mode: PropTypes.oneOf(['light', 'dark']),
28+
size: PropTypes.number,
29+
design: PropTypes.oneOf(['inverted'])
1730
}
1831

1932
export default ThemeMode

packages/sui-studio/workbench/src/app.js

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,13 @@
11
/* eslint-disable no-console, no-undef, import/no-webpack-loader-syntax */
2-
import ReactDOM from 'react-dom'
2+
import {render, browserHistory} from 'react-dom'
3+
import {Router, Route, Redirect} from '@s-ui/react-router'
34

45
import {isFunction} from '../../src/components/demo/utilities.js'
56
import {importGlobals} from '../../src/components/globals.js'
6-
import Raw from './components/Raw/index.js'
7-
import Root from './components/Root/index.js'
7+
import Workbench from './components/Workbench/index.js'
88

99
import './styles.scss'
1010

11-
const queryStringToJSON = queryString => {
12-
if (queryString.indexOf('?') > -1) {
13-
queryString = queryString.split('?')[1]
14-
}
15-
const pairs = queryString.split('&')
16-
const result = {}
17-
pairs.forEach(function (pair) {
18-
pair = pair.split('=')
19-
result[pair[0]] = decodeURIComponent(pair[1] || '')
20-
})
21-
return result
22-
}
23-
24-
const params = queryStringToJSON(window.location.href)
25-
2611
const importAll = requireContext => requireContext.keys().map(requireContext)
2712

2813
;(async () => {
@@ -72,18 +57,28 @@ const importAll = requireContext => requireContext.keys().map(requireContext)
7257
return acc
7358
}, {})
7459

75-
const {raw} = params
76-
const ComponentToRender = raw ? Raw : Root
77-
7860
await importGlobals()
7961

80-
ReactDOM.render(
81-
<ComponentToRender
82-
componentID={__COMPONENT_ID__}
83-
contexts={contexts}
84-
demo={DemoComponent}
85-
themes={{...themes, default: demoStyles?.default || defaultStyle}}
86-
{...params}
62+
render(
63+
<Router
64+
history={browserHistory}
65+
routes={
66+
<Route>
67+
<Route
68+
path="/"
69+
component={(...props) => (
70+
<Workbench
71+
componentID={__COMPONENT_ID__}
72+
contexts={contexts}
73+
demo={DemoComponent}
74+
themes={{...themes, default: demoStyles?.default || defaultStyle}}
75+
{...props}
76+
/>
77+
)}
78+
/>
79+
<Redirect from="**" to="/" />
80+
</Route>
81+
}
8782
/>,
8883
document.getElementById('app')
8984
)

packages/sui-studio/workbench/src/components/Header/index.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
1-
import {useState} from 'react'
1+
import React, {useState} from 'react'
22

33
import PropTypes from 'prop-types'
44

5-
export default function Header({children, componentID, iframeSrc}) {
5+
import ThemeMode from '../../../../src/components/theme-mode/index.js'
6+
7+
export default function Header({children, componentID, iframeSrc, themeMode, setThemeMode}) {
68
const [show, setShow] = useState(true)
79

810
if (!show) return null
911

1012
return (
1113
<header className="Header">
1214
<div className="Header-actions">
15+
<span className="Header-link">
16+
<ThemeMode
17+
mode={themeMode}
18+
onChange={checked => setThemeMode(checked ? 'dark' : 'light')}
19+
size={16}
20+
design="inverted"
21+
/>
22+
</span>
1323
<a
1424
className="Header-link"
1525
href={iframeSrc}
@@ -18,7 +28,7 @@ export default function Header({children, componentID, iframeSrc}) {
1828
title="Open Iframe in a new window"
1929
>
2030
<svg height="21" viewBox="0 0 21 21" width="21">
21-
<g fill="none" stroke="#2a2e3b" transform="translate(3 4)">
31+
<g fill="none" stroke="currentColor" transform="translate(3 4)">
2232
<path
2333
d="m4.17157288 4.87867966v-1.41421357c0-1.56209716 1.26632995-2.82842712 2.82842712-2.82842712s2.82842712 1.26632996 2.82842712 2.82842712v1.41421357m0 2.82842712v2.82842712c0 1.5620972-1.26632995 2.8284271-2.82842712 2.8284271s-2.82842712-1.2663299-2.82842712-2.8284271v-2.82842712"
2434
transform="matrix(.70710678 .70710678 -.70710678 .70710678 7 -2.899495)"
@@ -37,7 +47,7 @@ export default function Header({children, componentID, iframeSrc}) {
3747
title="Close box"
3848
>
3949
<svg height="16" viewBox="0 0 21 21" width="16">
40-
<g fill="none" stroke="#2a2e3b" transform="translate(2 2)">
50+
<g fill="none" stroke="currentColor" transform="translate(2 2)">
4151
<circle cx="8.5" cy="8.5" r="8" />
4252
<g transform="matrix(0 1 -1 0 17 0)">
4353
<path d="m5.5 11.5 6-6" />
@@ -56,6 +66,8 @@ export default function Header({children, componentID, iframeSrc}) {
5666

5767
Header.propTypes = {
5868
children: PropTypes.array,
69+
themeMode: PropTypes.string,
70+
setThemeMode: PropTypes.func,
5971
componentID: PropTypes.string,
6072
iframeSrc: PropTypes.string
6173
}

packages/sui-studio/workbench/src/components/Header/index.scss

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
@import '../../../../src/components/preview/style';
2+
13
.Header {
24
align-items: center;
3-
background: rgba(0, 132, 255, 0.8);
5+
background: color-mix(
6+
in srgb,
7+
color-mix(in srgb, var(--studio-c-on-surface) 60%, var(--studio-c-primary)),
8+
transparent 10%
9+
);
410
border-radius: 8px;
511
bottom: 16px;
6-
color: #fff;
12+
color: var(--studio-c-surface, #000);
713
display: flex;
814
flex-direction: column;
915
justify-content: center;
@@ -20,10 +26,11 @@
2026
display: flex;
2127
justify-content: center;
2228
padding: 8px 0 0 0;
29+
color: var(--studio-c-surface, #000);
2330
}
2431

2532
&-link {
26-
color: #fff;
33+
color: inherit;
2734
font-size: 12px;
2835
padding-left: 8px;
2936
text-decoration: none;

packages/sui-studio/workbench/src/components/Raw/index.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* eslint import/no-webpack-loader-syntax:0 */
2-
import React, {useEffect, useState} from 'react'
2+
import React, {useEffect, useRef, useState} from 'react'
33

44
import Component, * as named from 'component'
55
import PropTypes from 'prop-types'
66

77
import SUIContext from '@s-ui/react-context'
8+
import {useRouter} from '@s-ui/react-router'
89

910
import {cleanDisplayName, createContextByType, removeDefaultContext} from '../../../../src/components/demo/utilities.js'
1011
import Preview from '../../../../src/components/preview/index.js'
@@ -23,11 +24,17 @@ export default function Raw({
2324
demo: DemoComponent,
2425
themes
2526
}) {
27+
const {
28+
location: {
29+
query: {mode: themeMode = 'light'}
30+
}
31+
} = useRouter()
32+
const rawRef = useRef()
2633
const [playground, setPlayground] = useState(null)
2734

2835
const context = Object.keys(contexts).length && createContextByType(contexts, actualContext)
2936

30-
// check if is a normal component or it's wrapped with a React.memo method
37+
// check if is a normal component, or it's wrapped with a React.memo method
3138
const ComponentToRender = Component.type ? Component.type : Component
3239

3340
useEffect(() => {
@@ -36,7 +43,7 @@ export default function Raw({
3643
}, [componentID])
3744

3845
return (
39-
<div className="Raw">
46+
<div className="Raw" ref={rawRef} data-theme-mode={themeMode}>
4047
<Style id="sui-studio-raw-theme">{themes[actualStyle]}</Style>
4148

4249
<div className="Raw-center">
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
@import '../../../../src/styles/settings';
22

3-
body {
4-
background-color: $bgc-main;
5-
}
6-
73
.Raw {
8-
padding: 16px;
4+
background-color: var(--studio-c-surface);
5+
color: var(--studio-c-on-surface);
6+
padding: 0;
97
}

packages/sui-studio/workbench/src/components/Root/index.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ const updateOnChange = (setState, sessionKey) => nextValue => {
2020
setState(nextValue)
2121
}
2222

23-
export default function Root({componentID, contexts = {}, themes}) {
23+
export default function Root({componentID, contexts = {}, themes, themeMode, setThemeMode}) {
2424
const [actualContext, setActualContext] = useState(() => getFromStorage('actualContext', 'default'))
2525
const [actualStyle, setActualStyle] = useState(() => getFromStorage('actualStyle', 'default'))
2626
const [showTests, setShowTests] = useState(() => getFromStorage('showTests', 'show'))
2727

28-
const iframeSrc = `/?raw=true&actualStyle=${actualStyle}&actualContext=${actualContext}`
28+
const iframeSrc = `/?raw=true&actualStyle=${actualStyle}&actualContext=${actualContext}&mode=${themeMode}`
2929

3030
return (
31-
<div className="Root">
32-
<Header componentID={componentID} iframeSrc={iframeSrc}>
31+
<div className="Root" data-theme-mode={themeMode}>
32+
<Header componentID={componentID} iframeSrc={iframeSrc} themeMode={themeMode} setThemeMode={setThemeMode}>
3333
<Select
3434
label="contexts"
3535
options={contexts}
@@ -67,5 +67,7 @@ export default function Root({componentID, contexts = {}, themes}) {
6767
Root.propTypes = {
6868
componentID: PropTypes.string,
6969
contexts: PropTypes.object,
70-
themes: PropTypes.object
70+
themes: PropTypes.object,
71+
themeMode: PropTypes.string,
72+
setThemeMode: PropTypes.func
7173
}

packages/sui-studio/workbench/src/components/Root/index.scss

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,23 @@
1616
}
1717

1818
&-test {
19-
background: #fafafa;
19+
background: var(--studio-c-on-surface);
20+
color: var(--studio-c-surface);
2021
height: 100vh;
2122
position: fixed;
2223
right: 0;
2324
top: 0;
2425
z-index: 1;
26+
#mocha,
27+
#mocha-stats {
28+
color: inherit;
29+
a {
30+
color: color-mix(in srgb, var(--studio-c-surface) 70%, var(--studio-c-primary));
31+
}
32+
em {
33+
color: inherit;
34+
}
35+
}
2536
}
2637

2738
&-testSwitch {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {useState} from 'react'
2+
3+
import usePrefersColorScheme from 'use-prefers-color-scheme'
4+
5+
import Raw from '../Raw/index.js'
6+
import Root from '../Root/index.js'
7+
import queryStringToJSON from './queryStringToJSON.js'
8+
9+
const Workbench = ({...rest}) => {
10+
const colorScheme = usePrefersColorScheme()
11+
const [theme, setTheme] = useState(colorScheme)
12+
13+
const params = queryStringToJSON(window.location.href)
14+
15+
const {raw} = params
16+
17+
const [Component, props] = raw
18+
? [Raw, {themeMode: theme, ...rest}]
19+
: [Root, {themeMode: theme, setThemeMode: setTheme, ...rest}]
20+
21+
return <Component {...props} />
22+
}
23+
24+
Workbench.displayName = 'Workbench'
25+
26+
Workbench.propTypes = {}
27+
28+
export default Workbench
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Workbench from './Workbench.js'
2+
3+
export default Workbench
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default queryString => {
2+
if (queryString.indexOf('?') > -1) {
3+
queryString = queryString.split('?')[1]
4+
}
5+
const pairs = queryString.split('&')
6+
const result = {}
7+
pairs.forEach(function (pair) {
8+
pair = pair.split('=')
9+
result[pair[0]] = decodeURIComponent(pair[1] || '')
10+
})
11+
return result
12+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
@import '../../src/styles/custom-properties';
12
@import '../../src/styles/settings';
23
@import '../../src/styles/default';
4+
@import '../../src/components/theme-mode/style';
35

46
@import './components/Header/index';
57
@import './components/Root/index';
68
@import './components/Select/index';
79
@import './components/Suite/index';
8-
9-
@import '../../src/components/preview/style';

0 commit comments

Comments
 (0)