Skip to content

Commit

Permalink
feat: share control values in url
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Oct 18, 2020
1 parent 923141f commit b668105
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 17 deletions.
7 changes: 6 additions & 1 deletion core/store/src/state/context/StateRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export interface StateRootProps {
* active page tab
*/
activeTab?: string;
/**
* initial control values. usually passed from the url
*/
values?: any;

/**
* global options passed from container
Expand All @@ -42,6 +46,7 @@ export const StateRoot: FC<StateRootProps> = ({
docId,
store,
options = {},
values,
activeTab,
}) => {
return (
Expand All @@ -51,7 +56,7 @@ export const StateRoot: FC<StateRootProps> = ({
<OptionsContextProvider options={options}>
<DocumentContextProvider docId={docId}>
<DocsSortContextProvider>
<StoryContextProvider storyId={storyId}>
<StoryContextProvider storyId={storyId} values={values}>
<ControlsContextStoryProvider>
{children}
</ControlsContextStoryProvider>
Expand Down
21 changes: 17 additions & 4 deletions core/store/src/state/context/story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
docStoryToId,
getComponentName,
Component,
mergeControlValues,
} from '@component-controls/core';
import { useStore, StoreContext, useActiveTab } from './store';

Expand All @@ -24,10 +25,10 @@ export const StoryContext = createContext<StoryContextProps>({
updateStory: () => {},
});

export const StoryContextProvider: FC<{ storyId: string | undefined }> = ({
storyId,
children,
}) => {
export const StoryContextProvider: FC<{
storyId?: string;
values?: any;
}> = ({ storyId, values, children }) => {
const { store } = useContext(StoreContext);
const [, setStory] = useState<Story | undefined>(
storyId ? store.stories[storyId] : undefined,
Expand All @@ -41,6 +42,18 @@ export const StoryContextProvider: FC<{ storyId: string | undefined }> = ({
store.addObserver(onObserver);
return () => store.removeObserver(onObserver);
}, [store, storyId]);
useEffect(() => {
if (values && storyId) {
const story = store.stories[storyId];
const storyControls = story.controls || {};
const newValue = {
...story,
controls: mergeControlValues(storyControls, undefined, values),
};
store.updateStory(newValue);
setStory(newValue);
}
}, [values, storyId, store]);
return (
<StoryContext.Provider
value={{
Expand Down
8 changes: 7 additions & 1 deletion core/store/src/state/recoil/StateRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Store } from '@component-controls/core';
import { storeState, activeTabState, optionsState } from './store';
import { documentIdState } from './document';
import { storyIdState } from './story';
import { controlsValuesState } from './controls';

export interface StateRootProps {
/**
Expand All @@ -23,7 +24,10 @@ export interface StateRootProps {
* active page tab
*/
activeTab?: string;

/**
* initial control values. usually passed from the url
*/
values?: any;
/**
* global options passed from container
* those are global parameters as well as decorators
Expand All @@ -38,6 +42,7 @@ export const StateRoot: FC<StateRootProps> = ({
store,
options = {},
activeTab,
values,
}) => {
return (
<RecoilRoot
Expand All @@ -47,6 +52,7 @@ export const StateRoot: FC<StateRootProps> = ({
set(storyIdState, storyId);
set(activeTabState, activeTab);
set(optionsState, options || {});
set(controlsValuesState, values);
}}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion examples/stories/src/stories/controls-editors.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const kitchenSink = ({
{new Date(birthday).toLocaleTimeString()}
</p>
<p>I live in NY for {years} years.</p>
<p>My wallet contains: ${dollars.toFixed(2)}</p>
<p>My wallet contains: ${parseFloat(dollars).toFixed(2)}</p>
<p>In my backpack, I have:</p>
<ul>
{items && items.map(item => <li key={item.name}>{item.name}</li>)}
Expand Down
2 changes: 2 additions & 0 deletions ui/blocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"fuse.js": "^6.4.1",
"global": "^4.3.2",
"js-string-escape": "^1.0.1",
"query-string": "^6.13.5",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-frame-component": "^4.1.2",
Expand All @@ -55,6 +56,7 @@
"@component-controls/ts-markdown-docs": "^1.21.0",
"@theme-ui/presets": "^0.3.0",
"@types/mdx-js__react": "^1.5.1",
"@types/query-string": "^6.3.0",
"@types/react": "^16.9.34",
"@types/react-frame-component": "^4.1.1",
"@types/react-resize-detector": "^4.2.0",
Expand Down
10 changes: 2 additions & 8 deletions ui/blocks/src/PageContainer/PageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { get } from '@theme-ui/css';
import { useTheme } from '@component-controls/components';
import { useCurrentDocument } from '@component-controls/store';
import { Container } from '../Container/Container';
import { getURL } from '../utils/url';

export interface PageContainerOwnProps {
/**
Expand Down Expand Up @@ -45,14 +46,7 @@ export const PageContainer: FC<PageContainerProps> = forwardRef(
useEffect(() => {
const parseURLHash = () => {
try {
const pageURL =
(typeof window !== 'undefined' &&
window.location !== window.parent.location &&
window.parent.location
? window.parent.location.href
: document.location.href) || '';
const url = new URL(pageURL);

const url = getURL();
const scrollId = url.hash ? url.hash.substring(1) : undefined;
if (scrollId) {
const element = document.getElementById(scrollId);
Expand Down
26 changes: 26 additions & 0 deletions ui/blocks/src/PropsTable/controlsActions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useMemo, MouseEvent } from 'react';
import { window } from 'global';
import copy from 'copy-to-clipboard';
import queryString from 'query-string';
import {
resetControlValues,
getControlValues,
Expand All @@ -10,6 +11,7 @@ import {
canRandomizeControl,
canResetControl,
} from '@component-controls/core';
import { getURL } from '../utils/url';

export interface UseControlsActionsProps {
controls?: ComponentControls;
Expand All @@ -19,6 +21,7 @@ export interface UseControlsActionsProps {
export const useControlsActions = (props: UseControlsActionsProps) => {
const { controls, setControlValue, storyId } = props;
const [copied, setCopied] = React.useState(false);
const [urlCopied, setURLCopied] = React.useState(false);
const { hasControls, canReset, canRandomize } = useMemo(() => {
const keys = controls ? Object.keys(controls) : [];
const canRandomize =
Expand Down Expand Up @@ -77,6 +80,29 @@ export const useControlsActions = (props: UseControlsActionsProps) => {
id: 'randomize',
'aria-label': 'generate random values for the component controls',
});
actions.push({
node: urlCopied ? 'url copied' : 'share',
group: 'controls',
onClick: () => {
if (controls) {
const url = getURL();
const parsedParams = queryString.parse(url.search);
const values = getControlValues(controls);
parsedParams.controls = JSON.stringify(values);
const strValues = queryString.stringify(parsedParams);
const copyURL = `${url.protocol}//${url.host}${url.pathname}${
strValues ? `?${strValues}` : ''
}${url.hash ? `#${url.hash}` : ''}`;
copy(copyURL);
if (typeof window !== 'undefined') {
setURLCopied(true);
window.setTimeout(() => setURLCopied(false), 1500);
}
}
},
id: 'share_controls',
'aria-label': 'copy the control values as url search params',
});
}
if (canReset) {
actions.push({
Expand Down
16 changes: 14 additions & 2 deletions ui/blocks/src/context/BlockContext.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import React from 'react';
import React, { useMemo } from 'react';
import queryString from 'query-string';
import { deepMerge } from '@component-controls/core';
import { StateRoot, StateRootProps, useStore } from '@component-controls/store';
import { ErrorBoundary } from './ErrorBoundary';
import { ThemeProvider, ThemeProviderProps } from '../ThemeProvider';
import { getURL } from '../utils/url';

export const BlockContextProvider: React.FC<StateRootProps &
Pick<ThemeProviderProps, 'components'>> = ({
children,
components,
...props
}) => {
const values = useMemo(() => {
const url = getURL();
const parsedParams = queryString.parse(url.search);
if (typeof parsedParams.controls === 'string') {
return typeof parsedParams.controls === 'string'
? JSON.parse(parsedParams.controls)
: parsedParams.controls;
}
return undefined;
}, []);
return (
<StateRoot {...props}>
<StateRoot values={values} {...props}>
<ErrorBoundary>
<ThemeProvider components={components}>{children}</ThemeProvider>
</ErrorBoundary>
Expand Down
9 changes: 9 additions & 0 deletions ui/blocks/src/utils/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const getURL = () => {
const pageURL =
(typeof window !== 'undefined' &&
window.location !== window.parent.location &&
window.parent.location
? window.parent.location.href
: document.location.href) || '';
return new URL(pageURL);
};

0 comments on commit b668105

Please sign in to comment.