-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #495 from greenpeace/wysiwyg-vars
PLANET-5978: Theme creator experiment
- Loading branch information
Showing
23 changed files
with
3,530 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
.var-picker * { | ||
font-family: Roboto, sans-serif !important; | ||
color: black !important; | ||
line-height: initial !important; | ||
} | ||
|
||
.var-picker > ul { | ||
padding: 0; | ||
max-height: 80vh; | ||
width: 300px; | ||
overflow-y: scroll; | ||
margin-bottom: 0; | ||
|
||
&::-webkit-scrollbar { | ||
width: 9px; | ||
} | ||
|
||
&::-webkit-scrollbar-track { | ||
-webkit-border-radius: 5px; | ||
border-radius: 5px; | ||
background: rgba(140, 140, 140, 0.2); | ||
} | ||
|
||
&::-webkit-scrollbar-thumb { | ||
-webkit-border-radius: 5px; | ||
border-radius: 5px; | ||
background: rgba(140, 140, 140, 0.4); | ||
} | ||
|
||
&::-webkit-scrollbar-thumb:hover { | ||
background: rgba(140, 140, 140, 0.5); | ||
} | ||
|
||
&::-webkit-scrollbar-thumb:window-inactive { | ||
background: rgba(140, 140, 140, 0.05); | ||
} | ||
} | ||
|
||
.theme-editor-highlight { | ||
border: 2px solid yellow !important; | ||
box-sizing: border-box !important; | ||
} | ||
|
||
.var-picker .var-group { | ||
background: white !important; | ||
border: 1px solid black !important; | ||
margin-bottom: 3px !important; | ||
border-radius: 7px !important; | ||
padding: 2px 7px 7px !important; | ||
|
||
h5 { | ||
margin-bottom: 2px; | ||
} | ||
} | ||
|
||
.var-picker ul > li > ul { | ||
padding-left: 8px; | ||
} | ||
|
||
.var-control { | ||
_-- { | ||
background-color: #cecece; | ||
} | ||
clear: both; | ||
border: 1px solid black !important; | ||
margin-bottom: 3px !important; | ||
border-radius: 7px !important; | ||
padding: 2px 7px 7px !important; | ||
|
||
h5 { | ||
margin-bottom: 2px; | ||
} | ||
|
||
&.var-control-in-theme _-- { | ||
background-color: #fcfafa; | ||
} | ||
} | ||
|
||
.usages-collapsed { | ||
max-height: 32px; | ||
overflow-y: hidden; | ||
overflow-x: hidden; | ||
} | ||
|
||
.usages-open { | ||
clear: both; | ||
} | ||
|
||
div[id^=font-picker] ul { | ||
position: static !important; | ||
} | ||
|
||
#theme-editor-root { | ||
position: fixed; | ||
top: 64px; | ||
left: 50px; | ||
z-index: 99999999; | ||
padding: 4px 1px 3px 4px; | ||
background: rgba(255, 255, 255, 0.1); | ||
backdrop-filter: blur(1px); | ||
|
||
&.dragging { | ||
cursor: grabbing; | ||
} | ||
} | ||
|
||
body.logged-in #theme-editor-root { | ||
top: 92px; | ||
} | ||
|
||
.usages-wrapper { | ||
position: relative; | ||
max-height: 32px; | ||
overflow: hidden; | ||
} | ||
|
||
.usages-wrapper:hover { | ||
overflow: visible; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@import "components/VarPicker"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
import { useEffect, useState } from 'react'; | ||
import { VariableControl } from './VariableControl'; | ||
import { colorToState } from './colorToState'; | ||
import { THEME_ACTIONS, useThemeEditor } from './useThemeEditor'; | ||
import { whileHoverHighlight } from './highlight'; | ||
import { exportCss, exportJson } from './export'; | ||
|
||
export const LOCAL_STORAGE_KEY = 'p4-theme'; | ||
|
||
const byName = (a, b) => a.name > b.name ? 1 : (a.name === b.name ? 0 : -1); | ||
|
||
export const VarPicker = (props) => { | ||
const { | ||
groups, | ||
selectedVars, | ||
allVars, | ||
} = props; | ||
|
||
const [activeVars, setActiveVars] = useState([]); | ||
|
||
useEffect(() => { | ||
const notAlreadyActive = cssVar => !activeVars.map(active => active.name).includes(cssVar.name); | ||
|
||
const newOnes = selectedVars.filter(notAlreadyActive); | ||
|
||
setActiveVars(([...activeVars, ...newOnes])); | ||
}, [selectedVars]); | ||
|
||
const deactivate = (cssVar) => { | ||
const isOtherVar = active => active.name !== cssVar.name; | ||
setActiveVars(activeVars.filter(isOtherVar)); | ||
}; | ||
const closeAll = () => setActiveVars([]); | ||
const CloseAllButton = () => <span | ||
style={ { | ||
float: 'right', | ||
fontSize: '14px', | ||
padding: '3px 2px', | ||
background: 'white', | ||
border: '1px solid black', | ||
borderRadius: '4px', | ||
marginBottom: '8px' | ||
} } | ||
onClick={ closeAll } | ||
> Close all </span>; | ||
|
||
const [openGroups, setOpenGroups] = useState([]); | ||
const toggleGroup = id => { | ||
const newGroups = openGroups.includes(id) | ||
? openGroups.filter(openId => openId !== id) | ||
: [...openGroups, id]; | ||
|
||
setOpenGroups(newGroups); | ||
}; | ||
useEffect(() => { | ||
setOpenGroups([groups[0]?.label]); | ||
}, [groups]); | ||
|
||
// Todo: save state somewhere. | ||
const config = { | ||
allVars, | ||
}; | ||
const [ | ||
{ | ||
theme, | ||
defaultValues, | ||
hasHistory, | ||
hasFuture, | ||
}, | ||
dispatch, | ||
] = useThemeEditor(config); | ||
|
||
const setProperty = (name, value) => { | ||
dispatch({ type: THEME_ACTIONS.SET, payload: { name, value } }); | ||
}; | ||
|
||
const unsetProperty = (name) => { | ||
dispatch({ type: THEME_ACTIONS.UNSET, payload: { name } }); | ||
}; | ||
|
||
const [collapsed, setCollapsed] = useState(false); | ||
const toggleCollapsed = () => { | ||
setCollapsed(!collapsed); | ||
}; | ||
|
||
const [shouldGroup, setShouldGroup] = useState(true); | ||
|
||
const [fileName, setFileName] = useState(''); | ||
|
||
return <div | ||
className='var-picker' | ||
> | ||
<span | ||
style={ { | ||
fontSize: '10px', | ||
border: '1px solid grey', | ||
borderRadius: '3px', | ||
margin: '0 8px', | ||
padding: '2px 4px', | ||
background: 'grey', | ||
} } | ||
onClick={ toggleCollapsed } | ||
> | ||
{ collapsed ? 'show' : 'hide' } | ||
</span> | ||
{ !collapsed && <label | ||
htmlFor="" | ||
onClick={ () => setShouldGroup(!shouldGroup) } | ||
style={ { marginBottom: '2px' } } | ||
> | ||
<input type="checkbox" readOnly checked={ shouldGroup }/> | ||
{ 'Group last clicked element' } | ||
</label> } | ||
{ !collapsed && <div | ||
title='Click and hold to drag' | ||
className="themer-controls"> | ||
<div> | ||
<button | ||
onClick={ () => exportJson(fileName) } | ||
>JSON | ||
</button> | ||
<button | ||
onClick={ () => exportCss(fileName) } | ||
>CSS | ||
</button> | ||
<label style={{fontSize: '12px'}}> | ||
<input style={ { width: '130px' } } placeholder='theme' type="text" | ||
onChange={ event => setFileName(event.target.value) }/> | ||
</label> | ||
</div> | ||
<div> | ||
<label | ||
// Only tested on Chrome | ||
style={ { | ||
display: 'inline-block', | ||
maxWidth: '33%', | ||
overflowX: 'hidden', | ||
background: 'rgba(255,255,255,.3)', | ||
cursor: 'copy' | ||
} } | ||
> | ||
<input | ||
type="file" | ||
accept={ '.json' } | ||
onChange={ event => { | ||
const reader = new FileReader(); | ||
reader.onload = event => { | ||
try { | ||
const theme = JSON.parse(event.target.result); | ||
dispatch({ type: THEME_ACTIONS.LOAD_THEME, payload: { theme } }); | ||
} catch (e) { | ||
console.log('File contents is not valid JSON.', event.target.result, event); | ||
} | ||
}; | ||
reader.readAsText(event.target.files[0]); | ||
} } | ||
style={ { cursor: 'copy' } } | ||
/> | ||
</label> | ||
</div> | ||
</div> } | ||
{ shouldGroup && !collapsed && <ul> | ||
{ groups.map(({ element, label, vars }) => ( | ||
<li className={ 'var-group' } key={ label } style={ { marginBottom: '12px' } }> | ||
<h4 | ||
style={ { fontWeight: 400, marginBottom: 0, cursor: 'pointer' } } | ||
onClick={ () => toggleGroup(label) } | ||
{ ...whileHoverHighlight(element) } | ||
> | ||
{ label } ({ vars.length }) | ||
</h4> | ||
{ openGroups.includes(label) && <ul> | ||
{ vars.map(cssVar => { | ||
const defaultValue = defaultValues[cssVar.name]; | ||
|
||
return <VariableControl | ||
{ ...{ | ||
theme, | ||
cssVar, | ||
defaultValue, | ||
dispatch, | ||
} } | ||
key={ cssVar.name } | ||
onChange={ (value) => { | ||
setProperty(cssVar.name, value); | ||
} } | ||
onUnset={ () => unsetProperty(cssVar.name) } | ||
/>; | ||
} | ||
) } | ||
</ul> } | ||
</li> | ||
)) } | ||
</ul> } | ||
|
||
{ !shouldGroup && !collapsed && <ul> | ||
<span> | ||
showing { activeVars.length } propert{ activeVars.length === 1 ? 'y' : 'ies' } | ||
</span> | ||
{ activeVars.length > 0 && ( | ||
<CloseAllButton/> | ||
) } | ||
|
||
{ activeVars.sort(byName).map(cssVar => { | ||
const defaultValue = defaultValues[cssVar.name]; | ||
|
||
return <VariableControl | ||
{ ...{ | ||
cssVar, | ||
defaultValue, | ||
dispatch, | ||
theme, | ||
} | ||
} | ||
key={ cssVar.name } | ||
onUnset={ () => unsetProperty(cssVar.name) } | ||
onCloseClick={ deactivate } | ||
onChange={ value => setProperty(cssVar.name, value) } | ||
/>; | ||
} | ||
) } | ||
</ul> } | ||
|
||
</div>; | ||
}; |
Oops, something went wrong.