diff --git a/website/src/components/components/ComponentSettings.js b/website/src/components/components/ComponentSettings.js index eb1779fa4..545fb4452 100644 --- a/website/src/components/components/ComponentSettings.js +++ b/website/src/components/components/ComponentSettings.js @@ -15,30 +15,27 @@ import media from '../../theming/mediaQueries' const Container = styled.div` background: ${({ theme }) => theme.colors.cardBackground}; color: ${({ theme }) => theme.colors.text}; - // border-top: 1px solid ${({ theme }) => theme.colors.border}; ` const Group = styled.div` - // border-top: 1px solid ${({ theme }) => theme.colors.border}; - &:first-child { border-top-width: 0; } ` const Title = styled.div` - // border-bottom: 1px solid ${({ theme }) => theme.colors.border}; padding: 16px 30px; font-weight: 700; text-transform: uppercase; font-size: 13px; line-height: 1em; - // color: ${({ theme }) => theme.colors.cardBackground}; color: white; background: ${({ theme }) => theme.colors.accent}; - background-image: linear-gradient(-90deg, ${({ theme }) => theme.colors.gradientColor0}, ${({ - theme, -}) => theme.colors.gradientColor1}); + background-image: linear-gradient( + -90deg, + ${({ theme }) => theme.colors.gradientColor0}, + ${({ theme }) => theme.colors.gradientColor1} + ); background-size: 200% 100%; background-repeat: no-repeat; background-position: top left; diff --git a/website/src/components/controls/ColorPickerControl.js b/website/src/components/controls/ColorPickerControl.js index 8b88660ce..bc298e11d 100644 --- a/website/src/components/controls/ColorPickerControl.js +++ b/website/src/components/controls/ColorPickerControl.js @@ -18,7 +18,7 @@ class ColorPickerControl extends Component { } render() { - const { id, property, flavors, currentFlavor, value } = this.props + const { id, property, flavors, currentFlavor, value, context } = this.props return ( - +
    @@ -47,6 +47,7 @@ ColorPickerControl.propTypes = { currentFlavor: PropTypes.oneOf(['svg', 'html', 'canvas', 'api']).isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, + context: PropTypes.object, } export default ColorPickerControl diff --git a/website/src/components/controls/ControlsGroup.js b/website/src/components/controls/ControlsGroup.js index 8b7e8a9bd..cb39c50be 100644 --- a/website/src/components/controls/ControlsGroup.js +++ b/website/src/components/controls/ControlsGroup.js @@ -40,7 +40,7 @@ export const shouldRenderProperty = (property, currentSettings) => { } const ControlSwitcher = memo( - ({ groupName, flavors, currentFlavor, property, settings, onChange }) => { + ({ groupName, flavors, currentFlavor, property, settings, onChange, context }) => { // generate a unique identifier for the property const id = `${snakeCase(groupName)}-${property.name}` const value = get(settings, property.name) @@ -95,6 +95,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} value={value} options={options} + context={context} onChange={handleChange} /> ) @@ -110,6 +111,8 @@ const ControlSwitcher = memo( value={value} props={options.props} defaults={options.defaults} + isOpenedByDefault={options.isOpenedByDefault} + context={context} onChange={handleChange} /> ) @@ -123,6 +126,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -136,6 +140,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -149,6 +154,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -161,6 +167,7 @@ const ControlSwitcher = memo( flavors={flavors} currentFlavor={currentFlavor} value={value} + context={context} onChange={handleChange} /> ) @@ -174,6 +181,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -187,6 +195,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -200,6 +209,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -213,6 +223,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -226,6 +237,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -239,6 +251,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -252,6 +265,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -265,6 +279,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -278,6 +293,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -291,6 +307,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -304,6 +321,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -317,6 +335,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -330,6 +349,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -343,6 +363,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -356,6 +377,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -369,6 +391,7 @@ const ControlSwitcher = memo( currentFlavor={currentFlavor} options={options} value={value} + context={context} onChange={handleChange} /> ) @@ -390,9 +413,10 @@ ControlSwitcher.propTypes = { currentFlavor: PropTypes.oneOf(['svg', 'html', 'canvas', 'api']).isRequired, settings: PropTypes.object.isRequired, onChange: PropTypes.func.isRequired, + context: PropTypes.object, } -const ControlsGroup = ({ name, flavors, currentFlavor, controls, settings, onChange }) => { +const ControlsGroup = ({ name, flavors, currentFlavor, controls, settings, onChange, context }) => { return controls.map(control => ( )) } diff --git a/website/src/components/controls/LineWidthControl.js b/website/src/components/controls/LineWidthControl.js index e7d417d77..a98dcfe19 100644 --- a/website/src/components/controls/LineWidthControl.js +++ b/website/src/components/controls/LineWidthControl.js @@ -35,41 +35,43 @@ const Marker = styled.line` fill: none; ` -const LineWidthControl = memo(({ id, property, flavors, currentFlavor, value, onChange }) => { - const handleChange = useCallback( - event => { - onChange(Number(event.target.value)) - }, - [onChange] - ) +const LineWidthControl = memo( + ({ id, property, flavors, currentFlavor, value, context, onChange }) => { + const handleChange = useCallback( + event => { + onChange(Number(event.target.value)) + }, + [onChange] + ) - return ( - - - - - - - - - - - {property.help} - - ) -}) + return ( + + + + + + + + + + + {property.help} + + ) + } +) LineWidthControl.displayName = 'LineWidthControl' diff --git a/website/src/components/controls/ObjectControl.js b/website/src/components/controls/ObjectControl.js index 2247880ae..38c5152e3 100644 --- a/website/src/components/controls/ObjectControl.js +++ b/website/src/components/controls/ObjectControl.js @@ -13,39 +13,55 @@ import ControlsGroup from './ControlsGroup' import PropertyHeader from './PropertyHeader' import { Cell, Toggle, Help } from './styled' -const ObjectControl = memo(({ property, flavors, currentFlavor, value, props, onChange }) => { - const [isOpened, setIsOpened] = useState(false) - const toggle = useCallback(() => setIsOpened(flag => !flag), [setIsOpened]) +const ObjectControl = memo( + ({ + property, + flavors, + currentFlavor, + value, + props, + onChange, + context, + isOpenedByDefault = false, + }) => { + const [isOpened, setIsOpened] = useState(isOpenedByDefault) + const toggle = useCallback(() => setIsOpened(flag => !flag), [setIsOpened]) - const subProps = useMemo(() => - props.map(prop => ({ - ...prop, - name: prop.key, - group: property.group, - })) - ) + const subProps = useMemo(() => + props.map(prop => ({ + ...prop, + name: prop.key, + group: property.group, + })) + ) - return ( - <> -
- - {property.help} - -
- {isOpened && ( - - )} - - ) -}) + const newContext = { + path: [...(context ? context.path : []), property.key || property.name], + } + + return ( + <> +
+ + {property.help} + +
+ {isOpened && ( + + )} + + ) + } +) ObjectControl.displayName = 'ObjectControl' ObjectControl.propTypes = { @@ -55,6 +71,7 @@ ObjectControl.propTypes = { onChange: PropTypes.func.isRequired, value: PropTypes.object.isRequired, props: PropTypes.array.isRequired, + isOpenedByDefault: PropTypes.bool, } export default ObjectControl diff --git a/website/src/components/controls/PropertyHeader.js b/website/src/components/controls/PropertyHeader.js index 2528dd9e9..1df8ff215 100644 --- a/website/src/components/controls/PropertyHeader.js +++ b/website/src/components/controls/PropertyHeader.js @@ -52,6 +52,11 @@ const Label = styled.label` color: ${({ theme }) => theme.colors.text}; ` +const LabelParentPath = styled.span` + font-weight: 400; + color: ${({ theme }) => theme.colors.textLight}; +` + const Type = styled.span` display: inline-block; white-space: nowrap; @@ -81,10 +86,20 @@ const Default = styled.span` color: ${({ theme }) => theme.colors.textLight}; ` -const PropertyHeader = ({ id, name, type, required, defaultValue }) => { +const PropertyHeader = ({ id, name, type, required, defaultValue, context }) => { + let label = name + if (context) { + label = ( + <> + {context.path.join('.')}. + {name} + + ) + } + return ( - + {type !== undefined && {type}} {required && required} {!required && optional} diff --git a/website/src/data/nav.js b/website/src/data/nav.js index 071aa1636..fd7617007 100644 --- a/website/src/data/nav.js +++ b/website/src/data/nav.js @@ -379,8 +379,8 @@ export const guides = [ label: 'Patterns', path: '/guides/patterns', }, - // { - // label: 'Theming', - // path: '/guides/theming', - // }, + { + label: 'Theming', + path: '/guides/theming', + }, ] diff --git a/website/src/pages/guides/theming.js b/website/src/pages/guides/theming.js index b3ee8a2bd..888dd7798 100644 --- a/website/src/pages/guides/theming.js +++ b/website/src/pages/guides/theming.js @@ -13,15 +13,11 @@ import { ResponsiveBar } from '@nivo/bar' import { ResponsiveLine } from '@nivo/line' import Layout from '../../components/Layout' import SEO from '../../components/seo' -import PageContent from '../../components/PageContent' -import ControlsGroups from '../../components/controls/ControlsGroups' -import { DescriptionBlock } from '../../components/styled' - -const Container = styled.div` - display: grid; - grid-template-columns: 2fr 1fr; - grid-column-gap: 24px; -` +import ComponentPage from '../../components/components/ComponentPage' +import ComponentHeader from '../../components/components/ComponentHeader' +import Markdown from '../../components/Markdown' +import ComponentSettings from '../../components/components/ComponentSettings' +import media from '../../theming/mediaQueries' const initialTheme = { background: '#ffffff', // defaultTheme.background, @@ -51,7 +47,8 @@ const initialTheme = { const controlGroups = [ { - name: 'Global', + name: 'Theme', + group: 'Theme', properties: [ { name: 'background', @@ -74,104 +71,145 @@ const controlGroups = [ max: 36, }, }, - ], - }, - { - name: 'Axis', - properties: [ - { - name: 'axis.domain.line.stroke', - controlType: 'colorPicker', - }, { - name: 'axis.domain.line.strokeWidth', - controlType: 'range', + name: 'axis', + controlType: 'object', controlOptions: { - unit: 'px', - min: 0, - max: 32, + isOpenedByDefault: true, + props: [ + { + key: 'ticks', + controlType: 'object', + controlOptions: { + isOpenedByDefault: true, + props: [ + { + key: 'line', + controlType: 'object', + controlOptions: { + isOpenedByDefault: true, + props: [ + { + key: 'strokeWidth', + controlType: 'lineWidth', + }, + { + key: 'stroke', + controlType: 'colorPicker', + }, + ], + }, + }, + ], + }, + }, + { + key: 'domain', + controlType: 'object', + controlOptions: { + isOpenedByDefault: true, + props: [ + { + key: 'line', + controlType: 'object', + controlOptions: { + isOpenedByDefault: true, + props: [ + { + key: 'strokeWidth', + controlType: 'lineWidth', + }, + { + key: 'stroke', + controlType: 'colorPicker', + }, + ], + }, + }, + ], + }, + }, + ], }, }, { - name: 'axis.ticks.line.stroke', - controlType: 'colorPicker', - }, - { - name: 'axis.ticks.line.strokeWidth', - controlType: 'range', + name: 'grid', + controlType: 'object', controlOptions: { - unit: 'px', - min: 0, - max: 32, - }, - }, - ], - }, - { - name: 'Grid', - properties: [ - { - name: 'grid.line.stroke', - controlType: 'colorPicker', - }, - { - name: 'grid.line.strokeWidth', - controlType: 'range', - controlOptions: { - unit: 'px', - min: 0, - max: 32, + isOpenedByDefault: true, + props: [ + { + key: 'line', + controlType: 'object', + controlOptions: { + isOpenedByDefault: true, + props: [ + { + key: 'stroke', + controlType: 'colorPicker', + }, + { + key: 'strokeWidth', + controlType: 'lineWidth', + }, + ], + }, + }, + ], }, }, ], }, ] +const description = ` +**nivo** supports theming via the \`theme\` property, this property +must contain an object which defines various styles to be applied to your +charts. If you don't provide a theme, the default theme will be used. When +you provide a theme, you don't have to provide all properties as it will get +merged with the default theme. + +There are a few things to notice while theming your components. Values for +font-size, borders… are **unitless** as nivo supports several +implementations (SVG, HTML, Canvas), however you can pass extra CSS +attributes when using a specific implementation, for example, you might add +a stroke-dasharray to the grid lines when using the SVG implementation of +the Bar component, however it will have no effect on BarCanvas as it doesn't +support it. The theme only drives the base style of the charts, for things +such as symbol colors, patterns, legends, you'll have to use the dedicated +properties. +` + const Theming = () => { const [theme, setTheme] = useState(initialTheme) + const [mode, setMode] = useState('demo') return ( - + -
-

Theming

-
- -

- nivo supports theming via the theme property, this property - must contain an object which defines various styles to be applied to your - charts. If you don't provide a theme, the default theme will be used. When - you provide a theme, you don't have to provide all properties as it will get - merged with the default theme. -

-

- There are a few things to notice while theming your components. Values for - font-size, borders… are unitless as nivo supports several - implementations (SVG, HTML, Canvas), however you can pass extra CSS - attributes when using a specific implementation, for example, you might add - a stroke-dasharray to the grid lines when using the SVG implementation of - the Bar component, however it will have no effect on BarCanvas as it doesn'r - support it. The theme only drives the base style of the charts, for things - such as symbol colors, patterns, legends, you'll have to use the dedicated - properties. -

- -
- -
-
-
+ + + + + + + {mode === 'demo' && ( + <> +
{ ]} theme={theme} animate={false} + axisBottom={{ + legend: 'X axis legend', + legendPosition: 'middle', + legendOffset: 40, + }} + axisLeft={{ + legend: 'Y axis', + legendPosition: 'middle', + legendOffset: -40, + }} />
-
+
{ enableDotLabel={true} theme={theme} animate={false} + axisBottom={{ + legend: 'X axis legend', + legendPosition: 'middle', + legendOffset: 40, + }} + axisLeft={{ + legend: 'Y axis legend', + legendPosition: 'middle', + legendOffset: -40, + }} />
-
- - - + + )} + {mode === 'code' && ( + + {`// You can pass this object to the \`theme\` property\n`} + {JSON.stringify(theme, null, ' ')} + + )} +
+ + ) } export default Theming + +const Description = styled.div` + margin: 30px 0 50px; + + ${media.desktopLarge` + & { + padding: 0 40px; + } + `} + + ${media.desktop` + & { + padding: 0 30px; + } + `} + + ${media.tablet` + & { + padding: 0 20px; + } + `} + + ${media.mobile` + & { + padding: 0 20px; + margin-bottom: 30px; + } + `} + + code { + display: inline-block; + background: ${({ theme }) => theme.colors.cardBackground}; + border-radius: 2px; + font-size: 0.9em; + padding: 5px 7px; + line-height: 1em; + } +` + +const Nav = styled.nav` + height: 46px; + background: ${({ theme }) => theme.colors.background}; + font-size: 15px; + position: relative; + display: flex; + + ${media.mobile` + & { + display: grid; + grid-template-columns: repeat(2, 1fr); + } + `} +` + +const NavItem = styled.span` + cursor: pointer; + height: 46px; + display: flex; + padding: 0 24px; + justify-content: center; + align-items: center; + background: ${({ isCurrent, theme }) => + isCurrent ? theme.colors.cardBackground : 'transparent'}; + color: ${({ isCurrent, theme }) => (isCurrent ? theme.colors.text : '#aaa')}; + + &:hover { + color: ${({ theme }) => theme.colors.text}; + } +` + +const Charts = styled.div` + position: fixed; + top: ${({ theme }) => theme.dimensions.headerHeight}px; + right: 0; + width: 60%; + --innerWidth: calc(100% - ${({ theme }) => theme.dimensions.miniNavWidth}px); + width: calc(var(--innerWidth) * 0.55); + --innerHeight: calc(100% - ${({ theme }) => theme.dimensions.headerHeight}px); + height: var(--innerHeight); + z-index: 10; + overflow: hidden; + background: ${({ theme }) => theme.colors.cardBackground}; + display: flex; + flex-direction: column; + + & > div:nth-child(2), + & > div:nth-child(3) { + height: calc(var(--innerHeight) / 2); + } + + ${media.tablet` + & { + top: ${({ theme }) => theme.dimensions.headerHeight}px; + right: 0; + width: 55%; + height: calc(100% - ${({ theme }) => theme.dimensions.headerHeight}px); + } + `} + + ${media.mobile` + & { + position: relative; + top: auto; + right: auto; + width: auto; + height: 520px; + z-index: 0; + border-top: 1px solid ${({ theme }) => theme.colors.border}; + } + `} +` + +const Code = styled.pre` + height: calc(100% - 46px); + margin: 0; + background-color: ${({ theme }) => theme.highlight.plain.backgroundColor}; + color: ${({ theme }) => theme.highlight.plain.color}; + font-size: 0.8rem; + line-height: 1.7; + padding: 12px 20px; +`