Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested overrides playground #3940

Merged
merged 5 commits into from Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 34 additions & 13 deletions documentation-site/components/yard/__tests__/ast.test.ts
Expand Up @@ -4,24 +4,45 @@ import {toggleOverrideSharedProps} from '../ast';
describe('parseOverrides', () => {
test('get overrides active state and value', () => {
const overrides = `{
Root: {
style: ({ $theme }) => {
return {
Root: {
style: ({ $theme }) => ({
outline: \`\${$theme.colors.warning200} solid\`,
backgroundColor: $theme.colors.warning200
};
})
},
Tag: {
props: {
overrides: {
Action: {
style: ({ $theme }) => ({
outline: \`\${$theme.colors.warning200} dashed\`,
backgroundColor:
$theme.colors.warning200
})
}
}
}
}
}
}`;
expect(parseOverrides(overrides, ['Root'])).toEqual({
}`;
expect(parseOverrides(overrides)).toEqual({
Root: {
active: true,
style: `({ $theme }) => {
return {
outline: \`\${\$theme.colors.warning200} solid\`,
backgroundColor: $theme.colors.warning200
};
}`,
style: `({ $theme }) => ({
outline: \`\${\$theme.colors.warning200} solid\`,
backgroundColor: $theme.colors.warning200
})`,
},
Tag: {
active: true,
nestedValue: {
Action: {
active: true,
style: `({ $theme }) => ({
outline: \`\${\$theme.colors.warning200} dashed\`,
backgroundColor: $theme.colors.warning200
})`,
},
},
},
});
});
Expand Down
3 changes: 2 additions & 1 deletion documentation-site/components/yard/config/select.ts
Expand Up @@ -3,6 +3,7 @@ import {Select, SIZE, TYPE} from 'baseui/select';
import {PropTypes} from 'react-view';
import {changeHandlers} from './common/common';
import {TConfig} from '../types';
import TagConfig from './tag';

const selectProps = require('!!extract-react-types-loader!../../../../src/select/select.js');

Expand Down Expand Up @@ -350,7 +351,7 @@ const SelectConfig: TConfig = {
'Placeholder',
'ValueContainer',
'SingleValue',
'Tag',
TagConfig,
'InputContainer',
'Input',
'IconsContainer',
Expand Down
134 changes: 85 additions & 49 deletions documentation-site/components/yard/custom-props.ts
@@ -1,6 +1,5 @@
import * as t from '@babel/types';
import template from '@babel/template';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import {formatCode, parse} from 'react-view';

Expand All @@ -9,57 +8,94 @@ export type TCustomPropFields = {
sharedProps: {[key: string]: string | {type: string; description: string}};
};

export function parseOverrides(code: string, names: string[]) {
const resultOverrides: any = {};
try {
// to make the AST root valid, let's add a const definition
const ast = parse(`const foo = ${code};`);
traverse(ast, {
ObjectProperty(path) {
const propertyName = path.node.key.name;
if (names.includes(propertyName)) {
//@ts-ignore
const overrideProps = path.node.value.properties;
overrideProps.forEach((prop: any) => {
if (prop.key.name === 'style') {
resultOverrides[propertyName] = {
style: formatCode(generate(prop.value).code),
active: true,
};
}
});
}
},
const parseOverridesInner = (overrides: any, acc: any) => {
overrides.forEach((override: any) => {
const overrideName = override.key.name;
const overrideProps = override.value.properties;
overrideProps.forEach((prop: any) => {
if (prop.key.name === 'style') {
acc[overrideName] = {
style: formatCode(generate(prop.value).code),
active: true,
};
}
// looking for 'props' key
if (prop.key.name === 'props') {
// looking for 'overrides' key
prop.value.properties.forEach((subprop: any) => {
if (subprop.key.name === 'overrides') {
acc[overrideName] = {
nestedValue: {},
active: true,
};
parseOverridesInner(
subprop.value.properties,
acc[overrideName].nestedValue,
);
}
});
}
});
} catch (e) {
throw new Error("Overrides code is not valid and can't be parsed.");
}
return resultOverrides;
}
});
};

export const parseOverrides = (code: string) => {
const ast: any = parse(`const foo = ${code};`);
const topOverrides = ast.program.body[0].declarations[0].init.properties;
const returnValue = {};
parseOverridesInner(topOverrides, returnValue);
return returnValue;
};

type TGenerateValue = {
[key: string]: {
active: boolean;
style?: string;
nestedValue?: TGenerateValue;
};
};

const generateOverrides = (value: TGenerateValue) => {
const activeValues = Object.entries(value).filter(
([, val]) => val.active && (val.style || val.nestedValue),
);
if (activeValues.length === 0) return null;
const keys: any = activeValues.map(([key, val]) => {
if (val.nestedValue) {
const nestedOverride = generateOverrides(val.nestedValue);
if (!nestedOverride) {
return null;
}
return t.objectProperty(
t.identifier(key),
t.objectExpression([
t.objectProperty(
t.identifier('props'),
t.objectExpression([
t.objectProperty(
t.identifier('overrides'),
nestedOverride as any,
),
]),
),
]),
);
}
return t.objectProperty(
t.identifier(key),
t.objectExpression([
t.objectProperty(t.identifier('style'), template.expression(
val.style as string,
)({}) as any),
]),
);
});
return t.objectExpression(keys);
};

export const customProps = {
overrides: {
generate: (value: {[key: string]: {active: boolean; style: string}}) => {
const activeValues = Object.entries(value).filter(
([, val]: any) => val.active,
);
if (activeValues.length === 0) return null;
const keys = activeValues.map(([key, val]: [string, any]) =>
t.objectProperty(
t.identifier(key),
t.objectExpression([
t.objectProperty(t.identifier('style'), template.expression(
val.style,
)({}) as any),
]),
),
);
return t.objectExpression(keys);
},
parse: (code: string, knobProps: any) => {
const names =
knobProps && knobProps.overrides ? knobProps.overrides.names || [] : [];
return parseOverrides(code, names);
},
generate: generateOverrides,
parse: parseOverrides,
},
};
2 changes: 2 additions & 0 deletions documentation-site/components/yard/index.tsx
Expand Up @@ -25,6 +25,7 @@ import {getProvider, getThemeFromContext, TProviderValue} from './provider';
import {customProps, TCustomPropFields} from './custom-props';
import ThemeEditor from './theme-editor';
import Overrides from './overrides';
import OverridesDescription from './overrides-description';
import Editor from './editor';
import ActionButtons from './action-buttons';
import Knobs from './knobs';
Expand Down Expand Up @@ -123,6 +124,7 @@ const Yard: React.FC<TYardProps> = ({
activeOverrides > 0 ? ` (${activeOverrides})` : ''
}`}
>
<OverridesDescription componentName={componentName} />
<Overrides
componentName={componentName}
componentConfig={props}
Expand Down
59 changes: 59 additions & 0 deletions documentation-site/components/yard/nested-tooltip.tsx
@@ -0,0 +1,59 @@
import * as React from 'react';
import {StatefulTooltip} from 'baseui/tooltip';
import {useStyletron} from 'baseui';
import Link from 'next/link';

const NestedTooltip: React.FC<{name: string; nestedName: string}> = ({
name,
nestedName,
}) => {
const [css, theme] = useStyletron();
return (
<StatefulTooltip
content={() => (
<div
className={css({
padding: theme.sizing.scale600,
maxWidth: '400px',
})}
>
<p>
<b>{nestedName}</b> is a nested override of <b>{name}</b>. It means
that {name} component is using another base web component{' '}
{nestedName} as its sub-component.
</p>
<p>
Since {nestedName} has its own set of overrides, you have to target
nested sub-component to change relevant styles. You can utilize this
interactive playground and see the resulting code bellow.
</p>
<p>
<Link href="/guides/understanding-overrides#override-nested-components">
<a
className={css({color: theme.colors.primaryB})}
href="/guides/understanding-overrides#override-nested-components"
>
Learn more about nested overrides.
</a>
</Link>
</p>
</div>
)}
returnFocus
showArrow
>
<span
className={css({
marginLeft: theme.sizing.scale400,
color: theme.colors.accent,
borderBottom: `1px ${theme.colors.accent} dashed`,
...theme.typography.LabelXSmall,
})}
>
nested
</span>
</StatefulTooltip>
);
};

export default NestedTooltip;
15 changes: 8 additions & 7 deletions documentation-site/components/yard/override.tsx
Expand Up @@ -12,18 +12,18 @@ export const getHighlightStyles = (
isLightTheme: boolean,
sharedProps: string[],
) =>
formatCode(`({ $theme, ${sharedProps.join(',')} }) => { return ({
formatCode(`({ $theme, ${sharedProps.join(',')} }) => ({
outline: \`\${${
isLightTheme ? '$theme.colors.warning200' : '$theme.colors.warning600'
}} solid\`,
backgroundColor: ${
isLightTheme ? '$theme.colors.warning200' : '$theme.colors.warning600'
},
})}
})
`);

const getEmptyStyles = (sharedProps: string[]) =>
formatCode(`({ $theme, ${sharedProps.join(',')} }) => { return ({})}
formatCode(`({ $theme, ${sharedProps.join(',')} }) => ({})
`);

type TProps = {
Expand Down Expand Up @@ -97,11 +97,11 @@ const Override: React.FC<TProps> = ({
componentConfig,
set,
}) => {
const [, theme] = useStyletron();
const [css, theme] = useStyletron();
const isLightTheme = theme.name.startsWith('light-theme');
const code = overridesObj[overrideKey] ? overridesObj[overrideKey].style : '';
return (
<React.Fragment>
<div className={css({paddingRight: '10px', paddingBottom: '16px'})}>
<Editor
onChange={newCode => {
set({
Expand All @@ -110,9 +110,10 @@ const Override: React.FC<TProps> = ({
});
}}
code={code}
small
/>
<ButtonGroup
size={SIZE.compact}
size={SIZE.mini}
overrides={{
Root: {
style: ({$theme}) => ({
Expand Down Expand Up @@ -189,7 +190,7 @@ const Override: React.FC<TProps> = ({
Reset
</Button>
</ButtonGroup>
</React.Fragment>
</div>
);
};

Expand Down
23 changes: 23 additions & 0 deletions documentation-site/components/yard/overrides-description.tsx
@@ -0,0 +1,23 @@
import * as React from 'react';
import {Caption1} from 'baseui/typography';
import Link from 'next/link';
import {StyledLink} from 'baseui/link';

const OverridesDescription: React.FC<{componentName: string}> = ({
componentName,
}) => (
<Caption1
marginLeft="scale200"
marginRight="scale200"
marginBottom="scale400"
>
Additionally, you can fully customize any part of the {componentName}{' '}
component through the overrides prop (
<Link href="/guides/understanding-overrides">
<StyledLink href="/guides/understanding-overrides">learn more</StyledLink>
</Link>
). Try to update different <b>style overrides</b> in the explorer bellow:
</Caption1>
);

export default OverridesDescription;