playroom 0.34.2 → 0.36.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.
Files changed (38) hide show
  1. package/.github/workflows/preview-site.yml +3 -3
  2. package/.github/workflows/release.yml +3 -3
  3. package/.github/workflows/snapshot.yml +3 -3
  4. package/.github/workflows/validate.yml +5 -5
  5. package/.nvmrc +1 -1
  6. package/CHANGELOG.md +55 -0
  7. package/README.md +6 -0
  8. package/bin/cli.cjs +2 -1
  9. package/cypress/e2e/editor.cy.js +1 -1
  10. package/cypress/e2e/keymaps.cy.js +1507 -11
  11. package/cypress/e2e/scope.cy.js +1 -1
  12. package/cypress/e2e/smoke.cy.js +2 -2
  13. package/cypress/e2e/toolbar.cy.js +1 -2
  14. package/cypress/e2e/urlHandling.cy.js +4 -5
  15. package/cypress/support/utils.js +62 -54
  16. package/lib/makeWebpackConfig.js +0 -3
  17. package/lib/provideDefaultConfig.js +13 -2
  18. package/package.json +18 -17
  19. package/src/Playroom/CatchErrors/CatchErrors.tsx +5 -6
  20. package/src/Playroom/CodeEditor/CodeEditor.tsx +11 -0
  21. package/src/Playroom/CodeEditor/keymaps/comment.ts +326 -0
  22. package/src/Playroom/CodeEditor/keymaps/wrap.ts +4 -1
  23. package/src/Playroom/Frame.tsx +9 -5
  24. package/src/Playroom/FramesPanel/FramesPanel.css.ts +19 -0
  25. package/src/Playroom/FramesPanel/FramesPanel.tsx +89 -46
  26. package/src/Playroom/Preview.tsx +12 -3
  27. package/src/Playroom/PreviewPanel/PreviewPanel.tsx +1 -1
  28. package/src/Playroom/SettingsPanel/SettingsPanel.tsx +11 -7
  29. package/src/Playroom/Stack/Stack.css.ts +4 -35
  30. package/src/Playroom/Stack/Stack.tsx +2 -9
  31. package/src/Playroom/Toolbar/Toolbar.tsx +2 -2
  32. package/src/Playroom/sprinkles.css.ts +1 -0
  33. package/src/StoreContext/StoreContext.tsx +31 -6
  34. package/src/index.d.ts +2 -0
  35. package/src/utils/params.ts +5 -8
  36. package/src/utils/usePreviewUrl.ts +2 -1
  37. package/utils/index.d.ts +3 -0
  38. package/utils/index.js +21 -7
@@ -1,5 +1,4 @@
1
- import { calc } from '@vanilla-extract/css-utils';
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 xxsmall = style({
14
+ export const spaceScale = styleVariants(vars.space, (space) => ({
16
15
  vars: {
17
- [size]: vars.grid,
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, space !== 'none' && styles[space])}
27
+ className={classnames(styles.gap, styles.spaceScale[space])}
35
28
  >
36
29
  {dividers && index > 0 ? (
37
30
  <div className={styles.gap}>
@@ -81,7 +81,7 @@ export default ({ themes: allThemes, widths: allWidths, snippets }: Props) => {
81
81
  {hasSnippets && (
82
82
  <ToolbarItem
83
83
  active={isSnippetsOpen}
84
- title={`Insert snippet (${isMac() ? '\u2318' : 'Ctrl + '}K)`}
84
+ title={`Insert snippet (${isMac() ? '⌘K' : 'Ctrl+K'})`}
85
85
  disabled={!validCursorPosition}
86
86
  data-testid="toggleSnippets"
87
87
  onClick={() => {
@@ -127,7 +127,7 @@ export default ({ themes: allThemes, widths: allWidths, snippets }: Props) => {
127
127
 
128
128
  <div>
129
129
  <ToolbarItem
130
- title="Copy link to clipboard"
130
+ title={`Copy Playroom link (${isMac() ? '⌘⇧C' : 'Ctrl+Shift+C'})`}
131
131
  success={copying}
132
132
  onClick={copyHandler}
133
133
  data-testid="copyToClipboard"
@@ -42,6 +42,7 @@ export const vars = createGlobalTheme(':root', {
42
42
  large: '12px',
43
43
  xlarge: '16px',
44
44
  xxlarge: '20px',
45
+ xxxlarge: '28px',
45
46
  gutter: '40px',
46
47
  },
47
48
  });
@@ -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,
@@ -159,7 +162,7 @@ const createReducer =
159
162
  statusMessage:
160
163
  trigger === 'toolbarItem'
161
164
  ? {
162
- message: 'Copied to clipboard',
165
+ message: 'Copied Playroom link to clipboard',
163
166
  tone: 'positive',
164
167
  }
165
168
  : undefined,
@@ -383,6 +386,15 @@ const createReducer =
383
386
  return restState;
384
387
  }
385
388
 
389
+ case 'updateTitle': {
390
+ const { title } = action.payload;
391
+
392
+ return {
393
+ ...state,
394
+ title,
395
+ };
396
+ }
397
+
386
398
  default:
387
399
  return state;
388
400
  }
@@ -439,19 +451,23 @@ export const StoreProvider = ({
439
451
  let codeFromQuery: State['code'];
440
452
  let themesFromQuery: State['visibleThemes'];
441
453
  let widthsFromQuery: State['visibleWidths'];
454
+ let titleFromQuery: State['title'];
442
455
 
443
- if (params.code) {
456
+ const paramsCode = params.get('code');
457
+ if (paramsCode) {
444
458
  const {
445
459
  code: parsedCode,
446
460
  themes: parsedThemes,
447
461
  widths: parsedWidths,
462
+ title: parsedTitle,
448
463
  } = JSON.parse(
449
- lzString.decompressFromEncodedURIComponent(String(params.code)) ?? ''
464
+ lzString.decompressFromEncodedURIComponent(String(paramsCode)) ?? ''
450
465
  );
451
466
 
452
467
  codeFromQuery = parsedCode;
453
468
  themesFromQuery = parsedThemes;
454
469
  widthsFromQuery = parsedWidths;
470
+ titleFromQuery = parsedTitle;
455
471
  }
456
472
 
457
473
  Promise.all([
@@ -476,9 +492,15 @@ export const StoreProvider = ({
476
492
  const editorPosition = storedPosition;
477
493
  const editorHeight = storedHeight;
478
494
  const editorWidth = storedWidth;
479
- const visibleWidths = widthsFromQuery || storedVisibleWidths;
495
+ const visibleWidths =
496
+ widthsFromQuery ||
497
+ storedVisibleWidths ||
498
+ playroomConfig?.defaultVisibleWidths;
480
499
  const visibleThemes =
481
- hasThemesConfigured && (themesFromQuery || storedVisibleThemes);
500
+ hasThemesConfigured &&
501
+ (themesFromQuery ||
502
+ storedVisibleThemes ||
503
+ playroomConfig?.defaultVisibleThemes);
482
504
  const colorScheme = storedColorScheme;
483
505
 
484
506
  dispatch({
@@ -491,6 +513,7 @@ export const StoreProvider = ({
491
513
  ...(visibleThemes ? { visibleThemes } : {}),
492
514
  ...(visibleWidths ? { visibleWidths } : {}),
493
515
  ...(colorScheme ? { colorScheme } : {}),
516
+ title: titleFromQuery,
494
517
  ready: true,
495
518
  },
496
519
  });
@@ -521,11 +544,13 @@ export const StoreProvider = ({
521
544
  code: state.code,
522
545
  themes: state.visibleThemes,
523
546
  widths: state.visibleWidths,
547
+ title: state.title,
524
548
  });
525
549
  }, [
526
550
  state.code,
527
551
  state.visibleThemes,
528
552
  state.visibleWidths,
553
+ state.title,
529
554
  debouncedCodeUpdate,
530
555
  ]);
531
556
 
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 {
@@ -1,6 +1,5 @@
1
1
  import { createBrowserHistory } from 'history';
2
2
  import { useState, useEffect } from 'react';
3
- import queryString, { type ParsedQuery } from 'query-string';
4
3
 
5
4
  import playroomConfig from '../config';
6
5
 
@@ -11,10 +10,8 @@ export function updateUrlCode(code: string) {
11
10
 
12
11
  const existingQuery = getParamsFromQuery();
13
12
 
14
- const newQuery = queryString.stringify({
15
- ...existingQuery,
16
- code,
17
- });
13
+ const newQuery = new URLSearchParams(existingQuery);
14
+ newQuery.set('code', code);
18
15
 
19
16
  const params =
20
17
  playroomConfig.paramType === 'hash' ? `#?${newQuery}` : `?${newQuery}`;
@@ -24,18 +21,18 @@ export function updateUrlCode(code: string) {
24
21
 
25
22
  export function getParamsFromQuery(location = history.location) {
26
23
  try {
27
- return queryString.parse(
24
+ return new URLSearchParams(
28
25
  playroomConfig.paramType === 'hash'
29
26
  ? location.hash.replace(/^#/, '')
30
27
  : location.search
31
28
  );
32
29
  } catch (err) {
33
- return {};
30
+ return new URLSearchParams();
34
31
  }
35
32
  }
36
33
 
37
34
  export function useParams<ReturnType>(
38
- selector: (rawParams: ParsedQuery) => ReturnType
35
+ selector: (rawParams: URLSearchParams) => ReturnType
39
36
  ): ReturnType {
40
37
  const [params, setParams] = useState(getParamsFromQuery);
41
38
 
@@ -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 = ({ baseUrl, code, themes, widths, paramType = 'hash' }) => {
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 = ({ baseUrl, code, theme, paramType = 'hash' }) => {
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
  }