Skip to content

Commit 30ed20b

Browse files
committed
chore: refactor ControlsTable
1 parent bbc1d9c commit 30ed20b

14 files changed

+217
-237
lines changed

integrations/storybook/src/blocks/ControlsTable.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React, { FC } from 'react';
22
import {
3-
BlockControlsTable,
4-
BlockControlsTableProps,
3+
ControlsTable as BaseControlsTable,
4+
ControlsTableProps,
55
useStoryContext,
66
} from '@component-controls/blocks';
77

88
import { ThemeProvider } from '../context/ThemeProvider';
9-
export type ControlsTableProps = BlockControlsTableProps;
109

1110
export const ControlsTable: FC<ControlsTableProps> = ({
1211
id: propId,
@@ -25,7 +24,7 @@ export const ControlsTable: FC<ControlsTableProps> = ({
2524

2625
return id ? (
2726
<ThemeProvider>
28-
<BlockControlsTable id={id} {...rest} />
27+
<BaseControlsTable id={id} {...rest} />
2928
</ThemeProvider>
3029
) : null;
3130
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { StoryContextConsumer } from '../context/story/StoryContext';
3+
import { ControlsTable } from './ControlsTable';
4+
import { MockContext } from '../test/MockContext';
5+
6+
export default {
7+
title: 'Blocks/ControlsTable',
8+
component: ControlsTable,
9+
};
10+
11+
const MockStory = () => (
12+
<StoryContextConsumer id="blocks-core-story-plain--controls">
13+
{({ story: { controls } = {} }) => (
14+
<h2>{`Hello, my name is ${controls?.name.value}, and I am ${controls?.age.value} years old.`}</h2>
15+
)}
16+
</StoryContextConsumer>
17+
);
18+
export const overview = () => (
19+
<MockContext storyId="blocks-core-story-plain--controls">
20+
<MockStory />
21+
<ControlsTable id="." />
22+
</MockContext>
23+
);
24+
25+
export const title = () => (
26+
<MockContext storyId="blocks-core-story-plain--controls">
27+
<MockStory />
28+
<ControlsTable title="." id="." />
29+
</MockContext>
30+
);
31+
32+
export const customTitle = () => (
33+
<MockContext storyId="blocks-core-story-plain--controls">
34+
<MockStory />
35+
<ControlsTable title="Custom title" id="." />
36+
</MockContext>
37+
);
38+
export const notCollapsible = () => (
39+
<MockContext storyId="blocks-core-story-plain--controls">
40+
<MockStory />
41+
<ControlsTable title="." collapsible={false} />
42+
</MockContext>
43+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/** @jsx jsx */
2+
import { jsx, Box } from 'theme-ui';
3+
import React, { FC, MouseEvent } from 'react';
4+
import { window, document } from 'global';
5+
import qs from 'qs';
6+
import copy from 'copy-to-clipboard';
7+
import {
8+
resetControlValues,
9+
getControlValues,
10+
LoadedComponentControls,
11+
LoadedComponentControl,
12+
randomizeData,
13+
} from '@component-controls/core';
14+
import {
15+
ActionContainer,
16+
Tab,
17+
Tabs,
18+
TabList,
19+
TabPanel,
20+
} from '@component-controls/components';
21+
import {
22+
StoryBlockContainer,
23+
StoryBlockContainerProps,
24+
} from '../BlockContainer/story';
25+
26+
import { useBlockContext } from '../context';
27+
import { SingleControlsTable } from './SingleControlsTable';
28+
29+
export type ControlsTableProps = Omit<StoryBlockContainerProps, 'children'>;
30+
31+
const DEFAULT_GROUP_ID = 'Other';
32+
33+
interface GroupedControlsType {
34+
[key: string]: LoadedComponentControls;
35+
}
36+
37+
/**
38+
* Table component to display a story's controls and their editors.
39+
* Can adapt to multiple groups of controls, displaying them in their own tabs.
40+
*/
41+
export const ControlsTable: FC<ControlsTableProps> = (
42+
props: ControlsTableProps,
43+
) => {
44+
const [copied, setCopied] = React.useState(false);
45+
const { setControlValue, clickControl } = useBlockContext();
46+
return (
47+
<StoryBlockContainer {...props}>
48+
{(context, rest) => {
49+
const { story, id: storyId } = context;
50+
const { controls } = story || {};
51+
if (controls && Object.keys(controls).length) {
52+
const onReset = (e: MouseEvent<HTMLButtonElement>) => {
53+
e.preventDefault();
54+
if (setControlValue && storyId) {
55+
const values = resetControlValues(controls);
56+
setControlValue(storyId, undefined, values);
57+
}
58+
};
59+
const onCopy = (e: MouseEvent<HTMLButtonElement>) => {
60+
e.preventDefault();
61+
setCopied(true);
62+
const { location } = document;
63+
const query = qs.parse(location.search, {
64+
ignoreQueryPrefix: true,
65+
});
66+
const values = getControlValues(controls);
67+
Object.keys(values).forEach(key => {
68+
query[`controls-${key}`] = values[key];
69+
});
70+
71+
copy(
72+
`${location.origin + location.pathname}?${qs.stringify(query, {
73+
encode: false,
74+
})}`,
75+
);
76+
window.setTimeout(() => setCopied(false), 1500);
77+
};
78+
const groupped: GroupedControlsType = Object.keys(controls)
79+
.filter(k => {
80+
const p: LoadedComponentControl = controls[k];
81+
return p.type && !p.hidden;
82+
})
83+
.reduce((acc: GroupedControlsType, k: string) => {
84+
const groupId = controls[k].groupId || DEFAULT_GROUP_ID;
85+
return {
86+
...acc,
87+
[groupId]: { ...acc[groupId], [k]: controls[k] },
88+
};
89+
}, {});
90+
const groupedItems = Object.keys(groupped)
91+
.sort()
92+
.map(key => {
93+
return {
94+
label: key,
95+
controls: groupped[key],
96+
};
97+
});
98+
if (groupedItems.length === 0) {
99+
return null;
100+
}
101+
const actionItems = [
102+
{
103+
title: copied ? 'copied' : 'copy',
104+
onClick: onCopy,
105+
id: 'copy',
106+
'aria-label': 'copy control values',
107+
},
108+
{
109+
title: 'reset',
110+
onClick: onReset,
111+
id: 'reset',
112+
'aria-label': 'reset control values to their initial value',
113+
},
114+
{
115+
title: 'randomize',
116+
onClick: () => {
117+
if (setControlValue && controls && storyId) {
118+
setControlValue(storyId, undefined, randomizeData(controls));
119+
}
120+
},
121+
id: 'randomize',
122+
'aria-label': 'generate random values for the component controls',
123+
},
124+
];
125+
return (
126+
<ActionContainer actions={actionItems}>
127+
<Box
128+
sx={{
129+
pt: 4,
130+
}}
131+
>
132+
{groupedItems.length === 1 ? (
133+
<SingleControlsTable
134+
{...rest}
135+
setControlValue={setControlValue}
136+
clickControl={clickControl}
137+
storyId={storyId}
138+
controls={groupedItems[0].controls}
139+
/>
140+
) : (
141+
<Tabs>
142+
<TabList>
143+
{groupedItems.map(item => (
144+
<Tab key={`tab_${item.label}`}>{item.label}</Tab>
145+
))}
146+
</TabList>
147+
{groupedItems.map(item => (
148+
<TabPanel key={`tab_panel_${item.label}`}>
149+
<SingleControlsTable
150+
{...rest}
151+
setControlValue={setControlValue}
152+
clickControl={clickControl}
153+
storyId={storyId}
154+
controls={item.controls}
155+
/>
156+
</TabPanel>
157+
))}
158+
</Tabs>
159+
)}
160+
</Box>
161+
</ActionContainer>
162+
);
163+
}
164+
return null;
165+
}}
166+
</StoryBlockContainer>
167+
);
168+
};

ui/blocks/src/ControlsTable/plain/SingleControlsTable.tsx renamed to ui/blocks/src/ControlsTable/SingleControlsTable.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { getPropertyEditor, PropertyEditor } from '@component-controls/editors';
99
import { Table } from '@component-controls/components';
1010
import { Flex } from 'theme-ui';
11-
import { InvalidType } from '../../notifications/InvalidType';
11+
import { InvalidType } from '../notifications/InvalidType';
1212

1313
export interface SingleControlsTableProps {
1414
/**

ui/blocks/src/ControlsTable/block/BlockControlsTable.stories.tsx

-22
This file was deleted.

ui/blocks/src/ControlsTable/block/BlockControlsTable.tsx

-22
This file was deleted.

ui/blocks/src/ControlsTable/block/index.ts

-1
This file was deleted.

ui/blocks/src/ControlsTable/index.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export * from './block';
2-
export * from './plain';
1+
export * from './ControlsTable';

ui/blocks/src/ControlsTable/plain/ControlsTable.stories.tsx

-22
This file was deleted.

0 commit comments

Comments
 (0)