playroom 0.34.2 → 0.35.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/CHANGELOG.md +27 -0
- package/README.md +6 -0
- package/cypress/e2e/keymaps.cy.js +15 -0
- package/package.json +3 -1
- package/src/Playroom/CatchErrors/CatchErrors.tsx +5 -6
- package/src/Playroom/CodeEditor/keymaps/wrap.ts +4 -1
- package/src/Playroom/FramesPanel/FramesPanel.tsx +1 -1
- package/src/Playroom/Preview.tsx +9 -1
- package/src/Playroom/PreviewPanel/PreviewPanel.tsx +1 -1
- package/src/Playroom/SettingsPanel/SettingsPanel.css.ts +19 -0
- package/src/Playroom/SettingsPanel/SettingsPanel.tsx +133 -89
- package/src/Playroom/Stack/Stack.css.ts +4 -35
- package/src/Playroom/Stack/Stack.tsx +2 -9
- package/src/Playroom/sprinkles.css.ts +1 -0
- package/src/StoreContext/StoreContext.tsx +30 -3
- package/src/index.d.ts +2 -0
- package/src/utils/usePreviewUrl.ts +2 -1
- package/utils/index.d.ts +3 -0
- package/utils/index.js +21 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# playroom
|
|
2
2
|
|
|
3
|
+
## 0.35.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ad60e01: Add support for specifying default subsets of themes and screen widths via the config.
|
|
8
|
+
|
|
9
|
+
#### Example usage
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// playroom.config.js
|
|
13
|
+
module.exports = {
|
|
14
|
+
...,
|
|
15
|
+
defaultVisibleWidths: [
|
|
16
|
+
// subset of widths to display on first load
|
|
17
|
+
],
|
|
18
|
+
defaultVisibleThemes: [
|
|
19
|
+
// subset of themes to display on first load
|
|
20
|
+
],
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- f45dd04: Add ability to customise tab titles via a "Title" section in the settings panel.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- f491105: Fix bug in "Wrap selection in tag" command that caused the start cursor to occasionally be placed in the wrong postion.
|
|
29
|
+
|
|
3
30
|
## 0.34.2
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -80,6 +80,12 @@ module.exports = {
|
|
|
80
80
|
// Custom webpack config goes here...
|
|
81
81
|
}),
|
|
82
82
|
iframeSandbox: 'allow-scripts',
|
|
83
|
+
defaultVisibleWidths: [
|
|
84
|
+
// subset of widths to display on first load
|
|
85
|
+
],
|
|
86
|
+
defaultVisibleThemes: [
|
|
87
|
+
// subset of themes to display on first load
|
|
88
|
+
],
|
|
83
89
|
};
|
|
84
90
|
```
|
|
85
91
|
|
|
@@ -216,6 +216,21 @@ describe('Keymaps', () => {
|
|
|
216
216
|
`);
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
+
it('should ignore surrounding whitespace when wrapping a single line selection', () => {
|
|
220
|
+
typeCode(' ');
|
|
221
|
+
typeCode('{leftArrow}');
|
|
222
|
+
selectToEndOfLine();
|
|
223
|
+
|
|
224
|
+
typeCode(`{shift+${modifierKey}+,}`);
|
|
225
|
+
typeCode('span');
|
|
226
|
+
|
|
227
|
+
assertCodePaneContains(dedent`
|
|
228
|
+
<span> <div>First line</div></span>
|
|
229
|
+
<div>Second line</div>
|
|
230
|
+
<div>Third line</div>
|
|
231
|
+
`);
|
|
232
|
+
});
|
|
233
|
+
|
|
219
234
|
it('should wrap a multi-line selection', () => {
|
|
220
235
|
typeCode('{shift+downArrow}');
|
|
221
236
|
selectToEndOfLine();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playroom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "Design with code, powered by your own component library",
|
|
5
5
|
"main": "utils/index.js",
|
|
6
6
|
"types": "utils/index.d.ts",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"query-string": "^7.1.3",
|
|
73
73
|
"re-resizable": "^6.9.9",
|
|
74
74
|
"react-docgen-typescript": "^2.2.2",
|
|
75
|
+
"react-helmet": "^6.1.0",
|
|
75
76
|
"react-use": "^17.4.0",
|
|
76
77
|
"read-pkg-up": "^7.0.1",
|
|
77
78
|
"scope-eval": "^1.0.0",
|
|
@@ -87,6 +88,7 @@
|
|
|
87
88
|
"@changesets/cli": "^2.25.2",
|
|
88
89
|
"@octokit/rest": "^19.0.5",
|
|
89
90
|
"@types/jest": "^29.2.4",
|
|
91
|
+
"@types/react-helmet": "^6.1.6",
|
|
90
92
|
"concurrently": "^7.6.0",
|
|
91
93
|
"cypress": "^12.0.2",
|
|
92
94
|
"eslint": "^8.44.0",
|
|
@@ -34,12 +34,11 @@ export default class CatchErrors extends Component<Props, State> {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Ensure the stack only contains user-provided components
|
|
37
|
-
const componentStack =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
: [];
|
|
37
|
+
const componentStack =
|
|
38
|
+
errorInfo?.componentStack
|
|
39
|
+
?.split('\n')
|
|
40
|
+
.filter((line: string) => /RenderCode/.test(line))
|
|
41
|
+
.map((line: string) => line.replace(/ \(created by .*/g, '')) ?? [];
|
|
43
42
|
|
|
44
43
|
// Ignore the RenderCode container component
|
|
45
44
|
const lines = componentStack.slice(0, componentStack.length - 1);
|
|
@@ -36,10 +36,13 @@ export const wrapInTag = (cm: Editor) => {
|
|
|
36
36
|
existingIndent,
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
const startCursorCharacterPosition =
|
|
40
|
+
from.ch + 1 + (isMultiLineSelection ? existingIndent : 0);
|
|
39
41
|
const newStartCursor = new Pos(
|
|
40
42
|
from.line + linesAdded,
|
|
41
|
-
|
|
43
|
+
startCursorCharacterPosition
|
|
42
44
|
);
|
|
45
|
+
|
|
43
46
|
const newEndCursor = isMultiLineSelection
|
|
44
47
|
? new Pos(to.line + linesAdded + 2, from.ch + existingIndent + 2)
|
|
45
48
|
: new Pos(to.line + linesAdded, to.ch + 4);
|
|
@@ -90,7 +90,7 @@ export default ({ availableWidths, availableThemes }: FramesPanelProps) => {
|
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
92
|
<ToolbarPanel data-testid="frame-panel">
|
|
93
|
-
<Stack space="
|
|
93
|
+
<Stack space="xxxlarge">
|
|
94
94
|
<div data-testid="widthsPreferences">
|
|
95
95
|
<FrameHeading
|
|
96
96
|
showReset={hasFilteredWidths}
|
package/src/Playroom/Preview.tsx
CHANGED
|
@@ -9,10 +9,12 @@ import CatchErrors from './CatchErrors/CatchErrors';
|
|
|
9
9
|
import RenderCode from './RenderCode/RenderCode';
|
|
10
10
|
|
|
11
11
|
import * as styles from './Preview.css';
|
|
12
|
+
import { Helmet } from 'react-helmet';
|
|
12
13
|
|
|
13
14
|
interface PreviewState {
|
|
14
15
|
code?: string;
|
|
15
16
|
themeName?: string;
|
|
17
|
+
title?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export interface PreviewProps {
|
|
@@ -25,7 +27,7 @@ export interface PreviewProps {
|
|
|
25
27
|
}>;
|
|
26
28
|
}
|
|
27
29
|
export default ({ themes, components, FrameComponent }: PreviewProps) => {
|
|
28
|
-
const { themeName, code } = useParams((rawParams): PreviewState => {
|
|
30
|
+
const { themeName, code, title } = useParams((rawParams): PreviewState => {
|
|
29
31
|
if (rawParams.code) {
|
|
30
32
|
const result = JSON.parse(
|
|
31
33
|
lzString.decompressFromEncodedURIComponent(String(rawParams.code)) ?? ''
|
|
@@ -34,6 +36,7 @@ export default ({ themes, components, FrameComponent }: PreviewProps) => {
|
|
|
34
36
|
return {
|
|
35
37
|
code: compileJsx(result.code),
|
|
36
38
|
themeName: result.theme,
|
|
39
|
+
title: result.title,
|
|
37
40
|
};
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -44,6 +47,11 @@ export default ({ themes, components, FrameComponent }: PreviewProps) => {
|
|
|
44
47
|
|
|
45
48
|
return (
|
|
46
49
|
<CatchErrors code={code}>
|
|
50
|
+
<Helmet>
|
|
51
|
+
<title>
|
|
52
|
+
{title ? `${title} | Playroom Preview` : 'Playroom Preview'}
|
|
53
|
+
</title>
|
|
54
|
+
</Helmet>
|
|
47
55
|
<div className={styles.renderContainer}>
|
|
48
56
|
<FrameComponent
|
|
49
57
|
themeName={themeName || '__PLAYROOM__NO_THEME__'}
|
|
@@ -98,3 +98,22 @@ export const label = style([
|
|
|
98
98
|
},
|
|
99
99
|
},
|
|
100
100
|
]);
|
|
101
|
+
|
|
102
|
+
export const textField = style([
|
|
103
|
+
sprinkles({
|
|
104
|
+
font: 'large',
|
|
105
|
+
width: 'full',
|
|
106
|
+
paddingX: 'large',
|
|
107
|
+
boxSizing: 'border-box',
|
|
108
|
+
borderRadius: 'medium',
|
|
109
|
+
}),
|
|
110
|
+
{
|
|
111
|
+
color: colorPaletteVars.foreground.neutral,
|
|
112
|
+
height: vars.touchableSize,
|
|
113
|
+
background: colorPaletteVars.background.surface,
|
|
114
|
+
'::placeholder': {
|
|
115
|
+
color: colorPaletteVars.foreground.neutralSoft,
|
|
116
|
+
},
|
|
117
|
+
border: `1px solid ${colorPaletteVars.border.standard}`,
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useContext, type ReactChild } from 'react';
|
|
2
|
+
import { Helmet } from 'react-helmet';
|
|
2
3
|
import { Heading } from '../Heading/Heading';
|
|
3
4
|
import { ToolbarPanel } from '../ToolbarPanel/ToolbarPanel';
|
|
4
5
|
import {
|
|
@@ -73,101 +74,144 @@ const KeyboardShortcut = ({
|
|
|
73
74
|
);
|
|
74
75
|
};
|
|
75
76
|
|
|
77
|
+
const getTitle = (title: string | undefined) => {
|
|
78
|
+
if (title) {
|
|
79
|
+
return `${title} | Playroom`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const configTitle = window?.__playroomConfig__.title;
|
|
83
|
+
|
|
84
|
+
if (configTitle) {
|
|
85
|
+
return `${configTitle} | Playroom`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return 'Playroom';
|
|
89
|
+
};
|
|
90
|
+
|
|
76
91
|
export default React.memo(() => {
|
|
77
|
-
const [{ editorPosition, colorScheme }, dispatch] =
|
|
92
|
+
const [{ editorPosition, colorScheme, title }, dispatch] =
|
|
93
|
+
useContext(StoreContext);
|
|
78
94
|
|
|
79
95
|
const keybindings = getKeyBindings();
|
|
80
96
|
|
|
97
|
+
const displayedTitle = getTitle(title);
|
|
98
|
+
|
|
81
99
|
return (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
100
|
+
<>
|
|
101
|
+
{title === undefined ? null : (
|
|
102
|
+
<Helmet>
|
|
103
|
+
<title>{displayedTitle}</title>
|
|
104
|
+
</Helmet>
|
|
105
|
+
)}
|
|
106
|
+
<ToolbarPanel data-testid="settings-panel">
|
|
107
|
+
<Stack space="xxxlarge">
|
|
108
|
+
<label>
|
|
109
|
+
<Stack space="medium">
|
|
110
|
+
<Heading level="3">Title</Heading>
|
|
111
|
+
<input
|
|
112
|
+
type="text"
|
|
113
|
+
id="playroomTitleField"
|
|
114
|
+
placeholder="Enter a title for this Playroom..."
|
|
115
|
+
className={styles.textField}
|
|
116
|
+
value={title}
|
|
117
|
+
onChange={(e) =>
|
|
118
|
+
dispatch({
|
|
119
|
+
type: 'updateTitle',
|
|
120
|
+
payload: { title: e.target.value },
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
/>
|
|
124
|
+
</Stack>
|
|
125
|
+
</label>
|
|
126
|
+
|
|
127
|
+
<fieldset className={styles.fieldset}>
|
|
128
|
+
<legend>
|
|
129
|
+
<Heading level="3">Editor Position</Heading>
|
|
130
|
+
</legend>
|
|
131
|
+
<div className={styles.radioContainer}>
|
|
132
|
+
{['Bottom', 'Right'].map((option) => (
|
|
133
|
+
<div key={option}>
|
|
134
|
+
<input
|
|
135
|
+
type="radio"
|
|
136
|
+
name="editorPosition"
|
|
137
|
+
id={`editorPosition${option}`}
|
|
138
|
+
value={option.toLowerCase()}
|
|
139
|
+
title={option}
|
|
140
|
+
checked={option.toLowerCase() === editorPosition}
|
|
141
|
+
onChange={() =>
|
|
142
|
+
dispatch({
|
|
143
|
+
type: 'updateEditorPosition',
|
|
144
|
+
payload: {
|
|
145
|
+
position: option.toLowerCase() as EditorPosition,
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
className={styles.realRadio}
|
|
150
|
+
/>
|
|
151
|
+
<label
|
|
152
|
+
htmlFor={`editorPosition${option}`}
|
|
153
|
+
className={styles.label}
|
|
154
|
+
title={option}
|
|
155
|
+
>
|
|
156
|
+
<span className={styles.labelText}>
|
|
157
|
+
{positionIcon[option.toLowerCase() as EditorPosition]}
|
|
158
|
+
</span>
|
|
159
|
+
</label>
|
|
160
|
+
</div>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
</fieldset>
|
|
164
|
+
|
|
165
|
+
<fieldset className={styles.fieldset}>
|
|
166
|
+
<legend>
|
|
167
|
+
<Heading level="3">Color Scheme</Heading>
|
|
168
|
+
</legend>
|
|
169
|
+
<div className={styles.radioContainer}>
|
|
170
|
+
{['Light', 'Dark', 'System'].map((option) => (
|
|
171
|
+
<div key={option}>
|
|
172
|
+
<input
|
|
173
|
+
type="radio"
|
|
174
|
+
name="colorScheme"
|
|
175
|
+
id={`colorScheme${option}`}
|
|
176
|
+
value={option.toLowerCase()}
|
|
177
|
+
title={option}
|
|
178
|
+
checked={option.toLowerCase() === colorScheme}
|
|
179
|
+
onChange={() =>
|
|
180
|
+
dispatch({
|
|
181
|
+
type: 'updateColorScheme',
|
|
182
|
+
payload: {
|
|
183
|
+
colorScheme: option.toLowerCase() as ColorScheme,
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
className={styles.realRadio}
|
|
188
|
+
/>
|
|
189
|
+
<label
|
|
190
|
+
htmlFor={`colorScheme${option}`}
|
|
191
|
+
className={styles.label}
|
|
192
|
+
title={option}
|
|
193
|
+
>
|
|
194
|
+
<span className={styles.labelText}>
|
|
195
|
+
{colorModeIcon[option.toLowerCase() as ColorScheme]}
|
|
196
|
+
</span>
|
|
197
|
+
</label>
|
|
198
|
+
</div>
|
|
199
|
+
))}
|
|
200
|
+
</div>
|
|
201
|
+
</fieldset>
|
|
202
|
+
|
|
203
|
+
<Stack space="xlarge">
|
|
204
|
+
<Heading level="3">Keyboard Shortcuts</Heading>
|
|
205
|
+
{Object.entries(keybindings).map(([description, keybinding]) => (
|
|
206
|
+
<KeyboardShortcut
|
|
207
|
+
description={description}
|
|
208
|
+
keybinding={keybinding}
|
|
209
|
+
key={description}
|
|
210
|
+
/>
|
|
156
211
|
))}
|
|
157
|
-
</
|
|
158
|
-
</fieldset>
|
|
159
|
-
|
|
160
|
-
<Stack space="medium">
|
|
161
|
-
<Heading level="3">Keyboard Shortcuts</Heading>
|
|
162
|
-
{Object.entries(keybindings).map(([description, keybinding]) => (
|
|
163
|
-
<KeyboardShortcut
|
|
164
|
-
description={description}
|
|
165
|
-
keybinding={keybinding}
|
|
166
|
-
key={description}
|
|
167
|
-
/>
|
|
168
|
-
))}
|
|
212
|
+
</Stack>
|
|
169
213
|
</Stack>
|
|
170
|
-
</
|
|
171
|
-
|
|
214
|
+
</ToolbarPanel>
|
|
215
|
+
</>
|
|
172
216
|
);
|
|
173
217
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { style, createVar } from '@vanilla-extract/css';
|
|
1
|
+
import { style, createVar, styleVariants } from '@vanilla-extract/css';
|
|
3
2
|
import { vars } from '../sprinkles.css';
|
|
4
3
|
|
|
5
4
|
const size = createVar();
|
|
@@ -12,38 +11,8 @@ export const gap = style({
|
|
|
12
11
|
},
|
|
13
12
|
});
|
|
14
13
|
|
|
15
|
-
export const
|
|
14
|
+
export const spaceScale = styleVariants(vars.space, (space) => ({
|
|
16
15
|
vars: {
|
|
17
|
-
[size]:
|
|
16
|
+
[size]: space,
|
|
18
17
|
},
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
export const xsmall = style({
|
|
22
|
-
vars: {
|
|
23
|
-
[size]: calc(vars.grid).multiply(2).toString(),
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export const small = style({
|
|
28
|
-
vars: {
|
|
29
|
-
[size]: calc(vars.grid).multiply(3).toString(),
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export const medium = style({
|
|
34
|
-
vars: {
|
|
35
|
-
[size]: calc(vars.grid).multiply(4).toString(),
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const large = style({
|
|
40
|
-
vars: {
|
|
41
|
-
[size]: calc(vars.grid).multiply(6).toString(),
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
export const xlarge = style({
|
|
46
|
-
vars: {
|
|
47
|
-
[size]: calc(vars.grid).multiply(12).toString(),
|
|
48
|
-
},
|
|
49
|
-
});
|
|
18
|
+
}));
|
|
@@ -15,14 +15,7 @@ type ReactNodeNoStrings =
|
|
|
15
15
|
|
|
16
16
|
interface Props {
|
|
17
17
|
children: ReactNodeNoStrings;
|
|
18
|
-
space:
|
|
19
|
-
| 'none'
|
|
20
|
-
| 'xxsmall'
|
|
21
|
-
| 'xsmall'
|
|
22
|
-
| 'small'
|
|
23
|
-
| 'medium'
|
|
24
|
-
| 'large'
|
|
25
|
-
| 'xlarge';
|
|
18
|
+
space: keyof typeof styles.spaceScale;
|
|
26
19
|
dividers?: boolean;
|
|
27
20
|
}
|
|
28
21
|
|
|
@@ -31,7 +24,7 @@ export const Stack = ({ children, space, dividers = false }: Props) => (
|
|
|
31
24
|
{Children.toArray(children).map((item, index) => (
|
|
32
25
|
<div
|
|
33
26
|
key={index}
|
|
34
|
-
className={classnames(styles.gap,
|
|
27
|
+
className={classnames(styles.gap, styles.spaceScale[space])}
|
|
35
28
|
>
|
|
36
29
|
{dividers && index > 0 ? (
|
|
37
30
|
<div className={styles.gap}>
|
|
@@ -40,6 +40,7 @@ interface DebounceUpdateUrl {
|
|
|
40
40
|
code?: string;
|
|
41
41
|
themes?: string[];
|
|
42
42
|
widths?: number[];
|
|
43
|
+
title?: string;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export interface CursorPosition {
|
|
@@ -55,6 +56,7 @@ interface StatusMessage {
|
|
|
55
56
|
type ToolbarPanel = 'snippets' | 'frames' | 'preview' | 'settings';
|
|
56
57
|
interface State {
|
|
57
58
|
code: string;
|
|
59
|
+
title?: string;
|
|
58
60
|
previewRenderCode?: string;
|
|
59
61
|
previewEditorCode?: string;
|
|
60
62
|
highlightLineNumber?: number;
|
|
@@ -104,7 +106,8 @@ type Action =
|
|
|
104
106
|
| { type: 'updateVisibleThemes'; payload: { themes: string[] } }
|
|
105
107
|
| { type: 'resetVisibleThemes' }
|
|
106
108
|
| { type: 'updateVisibleWidths'; payload: { widths: number[] } }
|
|
107
|
-
| { type: 'resetVisibleWidths' }
|
|
109
|
+
| { type: 'resetVisibleWidths' }
|
|
110
|
+
| { type: 'updateTitle'; payload: { title: string } };
|
|
108
111
|
|
|
109
112
|
const resetPreview = ({
|
|
110
113
|
previewRenderCode,
|
|
@@ -383,6 +386,16 @@ const createReducer =
|
|
|
383
386
|
return restState;
|
|
384
387
|
}
|
|
385
388
|
|
|
389
|
+
case 'updateTitle': {
|
|
390
|
+
const { title } = action.payload;
|
|
391
|
+
store.setItem('title', title);
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
...state,
|
|
395
|
+
title,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
386
399
|
default:
|
|
387
400
|
return state;
|
|
388
401
|
}
|
|
@@ -439,12 +452,14 @@ export const StoreProvider = ({
|
|
|
439
452
|
let codeFromQuery: State['code'];
|
|
440
453
|
let themesFromQuery: State['visibleThemes'];
|
|
441
454
|
let widthsFromQuery: State['visibleWidths'];
|
|
455
|
+
let titleFromQuery: State['title'];
|
|
442
456
|
|
|
443
457
|
if (params.code) {
|
|
444
458
|
const {
|
|
445
459
|
code: parsedCode,
|
|
446
460
|
themes: parsedThemes,
|
|
447
461
|
widths: parsedWidths,
|
|
462
|
+
title: parsedTitle,
|
|
448
463
|
} = JSON.parse(
|
|
449
464
|
lzString.decompressFromEncodedURIComponent(String(params.code)) ?? ''
|
|
450
465
|
);
|
|
@@ -452,6 +467,7 @@ export const StoreProvider = ({
|
|
|
452
467
|
codeFromQuery = parsedCode;
|
|
453
468
|
themesFromQuery = parsedThemes;
|
|
454
469
|
widthsFromQuery = parsedWidths;
|
|
470
|
+
titleFromQuery = parsedTitle;
|
|
455
471
|
}
|
|
456
472
|
|
|
457
473
|
Promise.all([
|
|
@@ -462,6 +478,7 @@ export const StoreProvider = ({
|
|
|
462
478
|
store.getItem<number[]>('visibleWidths'),
|
|
463
479
|
store.getItem<string[]>('visibleThemes'),
|
|
464
480
|
store.getItem<ColorScheme>('colorScheme'),
|
|
481
|
+
store.getItem<string | undefined>('title'),
|
|
465
482
|
]).then(
|
|
466
483
|
([
|
|
467
484
|
storedCode,
|
|
@@ -471,14 +488,21 @@ export const StoreProvider = ({
|
|
|
471
488
|
storedVisibleWidths,
|
|
472
489
|
storedVisibleThemes,
|
|
473
490
|
storedColorScheme,
|
|
491
|
+
storedTitle,
|
|
474
492
|
]) => {
|
|
475
493
|
const code = codeFromQuery || storedCode || exampleCode;
|
|
476
494
|
const editorPosition = storedPosition;
|
|
477
495
|
const editorHeight = storedHeight;
|
|
478
496
|
const editorWidth = storedWidth;
|
|
479
|
-
const visibleWidths =
|
|
497
|
+
const visibleWidths =
|
|
498
|
+
widthsFromQuery ||
|
|
499
|
+
storedVisibleWidths ||
|
|
500
|
+
playroomConfig?.defaultVisibleWidths;
|
|
480
501
|
const visibleThemes =
|
|
481
|
-
hasThemesConfigured &&
|
|
502
|
+
hasThemesConfigured &&
|
|
503
|
+
(themesFromQuery ||
|
|
504
|
+
storedVisibleThemes ||
|
|
505
|
+
playroomConfig?.defaultVisibleThemes);
|
|
482
506
|
const colorScheme = storedColorScheme;
|
|
483
507
|
|
|
484
508
|
dispatch({
|
|
@@ -491,6 +515,7 @@ export const StoreProvider = ({
|
|
|
491
515
|
...(visibleThemes ? { visibleThemes } : {}),
|
|
492
516
|
...(visibleWidths ? { visibleWidths } : {}),
|
|
493
517
|
...(colorScheme ? { colorScheme } : {}),
|
|
518
|
+
title: titleFromQuery ?? storedTitle ?? undefined,
|
|
494
519
|
ready: true,
|
|
495
520
|
},
|
|
496
521
|
});
|
|
@@ -521,11 +546,13 @@ export const StoreProvider = ({
|
|
|
521
546
|
code: state.code,
|
|
522
547
|
themes: state.visibleThemes,
|
|
523
548
|
widths: state.visibleWidths,
|
|
549
|
+
title: state.title,
|
|
524
550
|
});
|
|
525
551
|
}, [
|
|
526
552
|
state.code,
|
|
527
553
|
state.visibleThemes,
|
|
528
554
|
state.visibleWidths,
|
|
555
|
+
state.title,
|
|
529
556
|
debouncedCodeUpdate,
|
|
530
557
|
]);
|
|
531
558
|
|
package/src/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ interface PlayroomConfig {
|
|
|
15
15
|
iframeSandbox?: string;
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
17
17
|
reactDocgenTypescriptConfig?: import('react-docgen-typescript').ParserOptions;
|
|
18
|
+
defaultVisibleThemes?: string[];
|
|
19
|
+
defaultVisibleWidths?: number[];
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
interface InternalPlayroomConfig extends PlayroomConfig {
|
|
@@ -9,7 +9,7 @@ const baseUrl = window.location.href
|
|
|
9
9
|
.split('index.html')[0];
|
|
10
10
|
|
|
11
11
|
export default (theme: string) => {
|
|
12
|
-
const [{ code }] = useContext(StoreContext);
|
|
12
|
+
const [{ code, title }] = useContext(StoreContext);
|
|
13
13
|
|
|
14
14
|
const isThemed = theme !== '__PLAYROOM__NO_THEME__';
|
|
15
15
|
|
|
@@ -18,5 +18,6 @@ export default (theme: string) => {
|
|
|
18
18
|
code,
|
|
19
19
|
theme: isThemed ? theme : undefined,
|
|
20
20
|
paramType: playroomConfig.paramType,
|
|
21
|
+
title,
|
|
21
22
|
});
|
|
22
23
|
};
|
package/utils/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ interface CompressParamsOptions {
|
|
|
13
13
|
themes?: string[];
|
|
14
14
|
widths?: number[];
|
|
15
15
|
theme?: string;
|
|
16
|
+
title?: string;
|
|
16
17
|
}
|
|
17
18
|
export const compressParams: (options: CompressParamsOptions) => string;
|
|
18
19
|
|
|
@@ -22,6 +23,7 @@ interface CreateUrlOptions {
|
|
|
22
23
|
themes?: string[];
|
|
23
24
|
widths?: number[];
|
|
24
25
|
paramType?: ParamType;
|
|
26
|
+
title?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export const createUrl: (options: CreateUrlOptions) => string;
|
|
@@ -31,6 +33,7 @@ interface CreatePreviewUrlOptions {
|
|
|
31
33
|
code?: string;
|
|
32
34
|
theme?: string;
|
|
33
35
|
paramType?: ParamType;
|
|
36
|
+
title?: string;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
export const createPreviewUrl: (options: CreatePreviewUrlOptions) => string;
|
package/utils/index.js
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
const lzString = require('lz-string');
|
|
2
2
|
|
|
3
|
-
const compressParams = ({ code, themes, widths, theme }) => {
|
|
3
|
+
const compressParams = ({ code, themes, widths, theme, title }) => {
|
|
4
4
|
const data = JSON.stringify({
|
|
5
5
|
...(code ? { code } : {}),
|
|
6
6
|
...(themes ? { themes } : {}),
|
|
7
7
|
...(widths ? { widths } : {}),
|
|
8
8
|
...(theme ? { theme } : {}),
|
|
9
|
+
...(title ? { title } : {}),
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
return lzString.compressToEncodedURIComponent(data);
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
const createUrl = ({
|
|
15
|
+
const createUrl = ({
|
|
16
|
+
baseUrl,
|
|
17
|
+
code,
|
|
18
|
+
themes,
|
|
19
|
+
widths,
|
|
20
|
+
title,
|
|
21
|
+
paramType = 'hash',
|
|
22
|
+
}) => {
|
|
15
23
|
let path = '';
|
|
16
24
|
|
|
17
|
-
if (code || themes || widths) {
|
|
18
|
-
const compressedData = compressParams({ code, themes, widths });
|
|
25
|
+
if (code || themes || widths || title) {
|
|
26
|
+
const compressedData = compressParams({ code, themes, widths, title });
|
|
19
27
|
|
|
20
28
|
path = `${paramType === 'hash' ? '#' : ''}?code=${compressedData}`;
|
|
21
29
|
}
|
|
@@ -29,11 +37,17 @@ const createUrl = ({ baseUrl, code, themes, widths, paramType = 'hash' }) => {
|
|
|
29
37
|
return path;
|
|
30
38
|
};
|
|
31
39
|
|
|
32
|
-
const createPreviewUrl = ({
|
|
40
|
+
const createPreviewUrl = ({
|
|
41
|
+
baseUrl,
|
|
42
|
+
code,
|
|
43
|
+
theme,
|
|
44
|
+
title,
|
|
45
|
+
paramType = 'hash',
|
|
46
|
+
}) => {
|
|
33
47
|
let path = '';
|
|
34
48
|
|
|
35
|
-
if (code || theme) {
|
|
36
|
-
const compressedData = compressParams({ code, theme });
|
|
49
|
+
if (code || theme || title) {
|
|
50
|
+
const compressedData = compressParams({ code, theme, title });
|
|
37
51
|
|
|
38
52
|
path = `/preview/${paramType === 'hash' ? '#' : ''}?code=${compressedData}`;
|
|
39
53
|
}
|