Commit 4adc389c authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Consolidate clean-theme class naming and organization

Apply clean-theme class to "root" elements of sidebar
and annotator. Remove clean-theme logic from sidebar components, and
instead use selector in SASS modules to apply relevant styles.

Fixes https://github.com/hypothesis/client/issues/2617
parent da5286ba
...@@ -116,7 +116,7 @@ export default class Sidebar extends Guest { ...@@ -116,7 +116,7 @@ export default class Sidebar extends Guest {
frame.className = 'annotator-frame annotator-outer'; frame.className = 'annotator-frame annotator-outer';
if (config.theme === 'clean') { if (config.theme === 'clean') {
frame.classList.add('annotator-frame--drop-shadow-enabled'); frame.classList.add('annotator-frame--theme-clean');
} }
element.appendChild(frame); element.appendChild(frame);
......
...@@ -103,10 +103,10 @@ describe('Sidebar', () => { ...@@ -103,10 +103,10 @@ describe('Sidebar', () => {
assert.equal(sidebar.frame.style.display, 'none'); assert.equal(sidebar.frame.style.display, 'none');
}); });
it('has a shadow if the clean theme is enabled', () => { it('applies a style if theme is configured as "clean"', () => {
const sidebar = createSidebar({ theme: 'clean' }); const sidebar = createSidebar({ theme: 'clean' });
assert.isTrue( assert.isTrue(
sidebar.frame.classList.contains('annotator-frame--drop-shadow-enabled') sidebar.frame.classList.contains('annotator-frame--theme-clean')
); );
}); });
......
import classnames from 'classnames';
import { createElement } from 'preact'; import { createElement } from 'preact';
import { useEffect, useMemo } from 'preact/hooks'; import { useEffect, useMemo } from 'preact/hooks';
import propTypes from 'prop-types'; import propTypes from 'prop-types';
...@@ -98,6 +99,7 @@ function HypothesisApp({ ...@@ -98,6 +99,7 @@ function HypothesisApp({
() => applyTheme(['appBackgroundColor'], settings), () => applyTheme(['appBackgroundColor'], settings),
[settings] [settings]
); );
const isThemeClean = settings.theme === 'clean';
const isSidebar = route === 'sidebar'; const isSidebar = route === 'sidebar';
...@@ -170,7 +172,9 @@ function HypothesisApp({ ...@@ -170,7 +172,9 @@ function HypothesisApp({
return ( return (
<div <div
className="hypothesis-app js-thread-list-scroll-root" className={classnames('hypothesis-app', 'js-thread-list-scroll-root', {
'theme-clean': isThemeClean,
})}
style={backgroundStyle} style={backgroundStyle}
> >
<TopBar <TopBar
......
...@@ -104,8 +104,6 @@ function SelectionTabs({ isLoading, settings }) { ...@@ -104,8 +104,6 @@ function SelectionTabs({ isLoading, settings }) {
selectTab: store.selectTab, selectTab: store.selectTab,
})); }));
const isThemeClean = settings.theme === 'clean';
const selectTab = tabId => { const selectTab = tabId => {
store.clearSelection(); store.clearSelection();
store.selectTab(tabId); store.selectTab(tabId);
...@@ -121,12 +119,7 @@ function SelectionTabs({ isLoading, settings }) { ...@@ -121,12 +119,7 @@ function SelectionTabs({ isLoading, settings }) {
return ( return (
<div className="selection-tabs-container"> <div className="selection-tabs-container">
<div <div className="selection-tabs" role="tablist">
className={classnames('selection-tabs', {
'selection-tabs--theme-clean': isThemeClean,
})}
role="tablist"
>
<Tab <Tab
count={annotationCount} count={annotationCount}
isWaitingToAnchor={isWaitingToAnchorAnnotations} isWaitingToAnchor={isWaitingToAnchorAnnotations}
......
...@@ -429,14 +429,34 @@ describe('HypothesisApp', () => { ...@@ -429,14 +429,34 @@ describe('HypothesisApp', () => {
}); });
}); });
it('applies theme config', () => { describe('theming', () => {
const style = { backgroundColor: 'red' }; it('applies theme config', () => {
fakeApplyTheme.returns({ backgroundColor: 'red' }); const style = { backgroundColor: 'red' };
fakeApplyTheme.returns({ backgroundColor: 'red' });
const wrapper = createComponent();
const background = wrapper.find('.hypothesis-app');
assert.calledWith(fakeApplyTheme, ['appBackgroundColor'], fakeSettings);
assert.deepEqual(background.prop('style'), style);
});
});
it('applies a clean-theme style when config sets theme to "clean"', () => {
fakeSettings.theme = 'clean';
const wrapper = createComponent();
const container = wrapper.find('.hypothesis-app');
assert.isTrue(container.hasClass('theme-clean'));
});
it('does not apply clean-theme style when config does not assert `clean` theme', () => {
fakeSettings.theme = '';
const wrapper = createComponent(); const wrapper = createComponent();
const background = wrapper.find('.hypothesis-app'); const container = wrapper.find('.hypothesis-app');
assert.calledWith(fakeApplyTheme, ['appBackgroundColor'], fakeSettings); assert.isFalse(container.hasClass('hypothesis-app--theme-clean'));
assert.deepEqual(background.prop('style'), style);
}); });
}); });
...@@ -134,17 +134,6 @@ describe('SelectionTabs', function () { ...@@ -134,17 +134,6 @@ describe('SelectionTabs', function () {
assert.equal(tabs.at(1).prop('title'), 'Page notes'); assert.equal(tabs.at(1).prop('title'), 'Page notes');
}); });
it('should show the clean theme when settings contains the clean theme option', function () {
fakeSettings.theme = 'clean';
const wrapper = createComponent();
assert.isTrue(wrapper.exists('.selection-tabs--theme-clean'));
});
it('should not show the clean theme when settings does not contain the clean theme option', function () {
const wrapper = createComponent();
assert.isFalse(wrapper.exists('.selection-tabs--theme-clean'));
});
it('should not display the new-note-btn when the annotations tab is active', function () { it('should not display the new-note-btn when the annotations tab is active', function () {
const wrapper = createComponent(); const wrapper = createComponent();
assert.equal(wrapper.find('NewNoteButton').length, 0); assert.equal(wrapper.find('NewNoteButton').length, 0);
......
...@@ -15,12 +15,7 @@ describe('ThreadCard', () => { ...@@ -15,12 +15,7 @@ describe('ThreadCard', () => {
function createComponent(props) { function createComponent(props) {
return mount( return mount(
<ThreadCard <ThreadCard frameSync={fakeFrameSync} thread={fakeThread} {...props} />
frameSync={fakeFrameSync}
settings={{}}
thread={fakeThread}
{...props}
/>
); );
} }
...@@ -64,12 +59,6 @@ describe('ThreadCard', () => { ...@@ -64,12 +59,6 @@ describe('ThreadCard', () => {
assert(wrapper.find('.thread-card').hasClass('is-focused')); assert(wrapper.find('.thread-card').hasClass('is-focused'));
}); });
it('applies a CSS class if settings indicate a `clean` theme', () => {
const wrapper = createComponent({ settings: { theme: 'clean' } });
assert(wrapper.find('.thread-card').hasClass('thread-card--theme-clean'));
});
it('shows document info if current route is not sidebar', () => { it('shows document info if current route is not sidebar', () => {
fakeStore.route.returns('whatever'); fakeStore.route.returns('whatever');
......
...@@ -249,12 +249,6 @@ describe('TopBar', () => { ...@@ -249,12 +249,6 @@ describe('TopBar', () => {
assert.ok(searchInput.exists()); assert.ok(searchInput.exists());
}); });
it('shows the clean theme when settings contains the clean theme option', () => {
fakeSettings.theme = 'clean';
const wrapper = createTopBar();
assert.isTrue(wrapper.exists('.top-bar--theme-clean'));
});
context('in the stream and single annotation pages', () => { context('in the stream and single annotation pages', () => {
it('does not render the group list, sort menu or share menu', () => { it('does not render the group list, sort menu or share menu', () => {
const wrapper = createTopBar({ isSidebar: false }); const wrapper = createTopBar({ isSidebar: false });
......
...@@ -17,7 +17,6 @@ import Thread from './thread'; ...@@ -17,7 +17,6 @@ import Thread from './thread';
* @typedef ThreadCardProps * @typedef ThreadCardProps
* @prop {Thread} thread * @prop {Thread} thread
* @prop {Object} frameSync - Injected service * @prop {Object} frameSync - Injected service
* @prop {MergedConfig} settings - Injected service
*/ */
/** /**
...@@ -26,7 +25,7 @@ import Thread from './thread'; ...@@ -26,7 +25,7 @@ import Thread from './thread';
* *
* @param {ThreadCardProps} props * @param {ThreadCardProps} props
*/ */
function ThreadCard({ frameSync, settings, thread }) { function ThreadCard({ frameSync, thread }) {
const threadTag = thread.annotation && thread.annotation.$tag; const threadTag = thread.annotation && thread.annotation.$tag;
const isFocused = useStore(store => store.isAnnotationFocused(threadTag)); const isFocused = useStore(store => store.isAnnotationFocused(threadTag));
const showDocumentInfo = useStore(store => store.route() !== 'sidebar'); const showDocumentInfo = useStore(store => store.route() !== 'sidebar');
...@@ -71,7 +70,6 @@ function ThreadCard({ frameSync, settings, thread }) { ...@@ -71,7 +70,6 @@ function ThreadCard({ frameSync, settings, thread }) {
key={thread.id} key={thread.id}
className={classnames('thread-card', { className={classnames('thread-card', {
'is-focused': isFocused, 'is-focused': isFocused,
'thread-card--theme-clean': settings.theme === 'clean',
})} })}
> >
<Thread thread={thread} showDocumentInfo={showDocumentInfo} /> <Thread thread={thread} showDocumentInfo={showDocumentInfo} />
...@@ -82,9 +80,8 @@ function ThreadCard({ frameSync, settings, thread }) { ...@@ -82,9 +80,8 @@ function ThreadCard({ frameSync, settings, thread }) {
ThreadCard.propTypes = { ThreadCard.propTypes = {
thread: propTypes.object.isRequired, thread: propTypes.object.isRequired,
frameSync: propTypes.object.isRequired, frameSync: propTypes.object.isRequired,
settings: propTypes.object,
}; };
ThreadCard.injectedProps = ['frameSync', 'settings']; ThreadCard.injectedProps = ['frameSync'];
export default withServices(ThreadCard); export default withServices(ThreadCard);
import classnames from 'classnames';
import { Fragment, createElement } from 'preact'; import { Fragment, createElement } from 'preact';
import propTypes from 'prop-types'; import propTypes from 'prop-types';
...@@ -49,7 +48,6 @@ function TopBar({ ...@@ -49,7 +48,6 @@ function TopBar({
settings, settings,
streamer, streamer,
}) { }) {
const useCleanTheme = settings.theme === 'clean';
const showSharePageButton = !isThirdPartyService(settings); const showSharePageButton = !isThirdPartyService(settings);
const loginLinkStyle = applyTheme(['accentColor'], settings); const loginLinkStyle = applyTheme(['accentColor'], settings);
...@@ -112,9 +110,7 @@ function TopBar({ ...@@ -112,9 +110,7 @@ function TopBar({
); );
return ( return (
<div <div className="top-bar">
className={classnames('top-bar', useCleanTheme && 'top-bar--theme-clean')}
>
{/* Single-annotation and stream views. */} {/* Single-annotation and stream views. */}
{!isSidebar && ( {!isSidebar && (
<div className="top-bar__inner content"> <div className="top-bar__inner content">
......
...@@ -66,11 +66,6 @@ ...@@ -66,11 +66,6 @@
} }
} }
/** Visible with clean theme */
.annotator-frame--drop-shadow-enabled {
box-shadow: var.$annotator-shadow--sidebar;
}
.annotator-placeholder { .annotator-placeholder {
opacity: 0; opacity: 0;
position: absolute; position: absolute;
...@@ -98,6 +93,11 @@ ...@@ -98,6 +93,11 @@
z-index: 10000; z-index: 10000;
} }
/** Affordances for clean theme */
#{var.$annotator--theme-clean} {
box-shadow: var.$annotator-shadow--sidebar;
}
/* /*
Mobile layout Mobile layout
240-479 px 240-479 px
......
...@@ -30,7 +30,8 @@ ...@@ -30,7 +30,8 @@
@include utils.shadow--active; @include utils.shadow--active;
} }
&--theme-clean { /** Clean theme affordances */
#{var.$sidebar--theme-clean} & {
// Give a little more space so that the border appears centered // Give a little more space so that the border appears centered
// between cards // between cards
padding-bottom: 1.5em; padding-bottom: 1.5em;
......
...@@ -24,10 +24,6 @@ ...@@ -24,10 +24,6 @@
margin: 0 var.$layout-space--xxsmall; margin: 0 var.$layout-space--xxsmall;
} }
.selection-tabs--theme-clean {
margin-left: 15px;
}
.selection-tabs__type { .selection-tabs__type {
@include buttons.reset-native-btn-styles; @include buttons.reset-native-btn-styles;
@include focus.outline-on-keyboard-focus; @include focus.outline-on-keyboard-focus;
...@@ -69,3 +65,8 @@ ...@@ -69,3 +65,8 @@
padding: 2em; padding: 2em;
text-align: center; text-align: center;
} }
/** Clean theme affordances */
#{var.$sidebar--theme-clean} .selection-tabs {
margin-left: 15px;
}
...@@ -56,10 +56,6 @@ ...@@ -56,10 +56,6 @@
} }
} }
.top-bar--theme-clean {
border-bottom: none;
}
.top-bar__login-button { .top-bar__login-button {
@include buttons.button; @include buttons.button;
@include utils.font--large; @include utils.font--large;
...@@ -105,3 +101,8 @@ ...@@ -105,3 +101,8 @@
margin-right: auto; margin-right: auto;
white-space: nowrap; white-space: nowrap;
} }
/** Clean theme affordances */
#{var.$sidebar--theme-clean} .top-bar {
border-bottom: none;
}
...@@ -110,6 +110,10 @@ $icon-size--small: 12px; ...@@ -110,6 +110,10 @@ $icon-size--small: 12px;
$icon-size--medium: 16px; $icon-size--medium: 16px;
$icon-size--large: 24px; $icon-size--large: 24px;
// Selectors for applying clean-theme styling
$annotator--theme-clean: '.annotator-frame--theme-clean';
$sidebar--theme-clean: '.theme-clean';
// Annotator-specific values // Annotator-specific values
// ----------------------------------- // -----------------------------------
$annotator-bucket-bar-width: 22px; $annotator-bucket-bar-width: 22px;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment