Unverified Commit 47630349 authored by Lyza Gardner's avatar Lyza Gardner Committed by GitHub

Merge pull request #1293 from hypothesis/user-focus-ui

Add first iteration of UI to user-focused mode
parents 4527e0c0 3e0bbe14
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false" class="Icon Icon--cancel"><g fill-rule="evenodd"><rect fill="none" stroke="none" x="0" y="0" width="16" height="16"></rect><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 8l3.536-3.536L8 8 4.464 4.464 8 8zm0 0l-3.536 3.536L8 8l3.536 3.536L8 8z"></path></g></svg>
...@@ -4,42 +4,59 @@ const { createElement } = require('preact'); ...@@ -4,42 +4,59 @@ const { createElement } = require('preact');
const useStore = require('../store/use-store'); const useStore = require('../store/use-store');
const SvgIcon = require('./svg-icon');
/** /**
* Render a control to interact with any focused "mode" in the sidebar. * Render a control to interact with any focused "mode" in the sidebar.
* Currently only a user-focus mode is supported but this could be broadened * Currently only a user-focus mode is supported but this could be broadened
* and abstracted if needed. Allow user to toggle in and out of the focus "mode." * and abstracted if needed. Allow user to toggle in and out of the focus "mode."
*/ */
function FocusedModeHeader() { function FocusedModeHeader() {
const store = useStore(store => ({ const actions = useStore(store => ({
actions: {
setFocusModeFocused: store.setFocusModeFocused, setFocusModeFocused: store.setFocusModeFocused,
},
selectors: {
focusModeFocused: store.focusModeFocused,
focusModeUserPrettyName: store.focusModeUserPrettyName,
},
})); }));
const selectors = useStore(store => ({
focusModeFocused: store.focusModeFocused(),
focusModeHasUser: store.focusModeHasUser(),
focusModeUserPrettyName: store.focusModeUserPrettyName(),
}));
// Nothing to do here for now if we're not focused on a user
if (!selectors.focusModeHasUser) {
return null;
}
const toggleFocusedMode = () => { const toggleFocusedMode = () => {
store.actions.setFocusModeFocused(!store.selectors.focusModeFocused()); actions.setFocusModeFocused(!selectors.focusModeFocused);
}; };
const buttonText = () => { const filterStatus = (
if (store.selectors.focusModeFocused()) { <div className="focused-mode-header__filter-status">
return `Annotations by ${store.selectors.focusModeUserPrettyName()}`; {selectors.focusModeFocused ? (
<span>
Annotations by <strong>{selectors.focusModeUserPrettyName}</strong>{' '}
only
</span>
) : (
<span>Everybody&rsquo;s annotations</span>
)}
</div>
);
const buttonText = (() => {
if (selectors.focusModeFocused) {
return 'Show all';
} else { } else {
return 'All annotations'; return `Only ${selectors.focusModeUserPrettyName}`;
} }
}; })();
return ( return (
<div className="focused-mode-header"> <div className="focused-mode-header sheet">
<button {filterStatus}
onClick={toggleFocusedMode} <button onClick={toggleFocusedMode} className="focused-mode-header__btn">
className="primary-action-btn primary-action-btn--short" <SvgIcon name="cancel" className="focused-mode-header__btn-icon" />
title={`Toggle to show annotations only by ${store.selectors.focusModeUserPrettyName()}`} {buttonText}
>
{buttonText()}
</button> </button>
</div> </div>
); );
......
...@@ -7,6 +7,7 @@ const propTypes = require('prop-types'); ...@@ -7,6 +7,7 @@ const propTypes = require('prop-types');
// The list of supported icons // The list of supported icons
const icons = { const icons = {
add: require('../../images/icons/add.svg'), add: require('../../images/icons/add.svg'),
cancel: require('../../images/icons/cancel.svg'),
'collapse-menu': require('../../images/icons/collapse-menu.svg'), 'collapse-menu': require('../../images/icons/collapse-menu.svg'),
'expand-menu': require('../../images/icons/expand-menu.svg'), 'expand-menu': require('../../images/icons/expand-menu.svg'),
copy: require('../../images/icons/copy.svg'), copy: require('../../images/icons/copy.svg'),
......
...@@ -19,8 +19,9 @@ describe('FocusedModeHeader', function() { ...@@ -19,8 +19,9 @@ describe('FocusedModeHeader', function() {
focused: true, focused: true,
}, },
}, },
focusModeFocused: sinon.stub().returns(false), focusModeFocused: sinon.stub().returns(true),
focusModeUserPrettyName: sinon.stub().returns('Fake User'), focusModeUserPrettyName: sinon.stub().returns('Fake User'),
focusModeHasUser: sinon.stub().returns(true),
setFocusModeFocused: sinon.stub(), setFocusModeFocused: sinon.stub(),
}; };
FocusedModeHeader.$imports.$mock({ FocusedModeHeader.$imports.$mock({
...@@ -32,29 +33,71 @@ describe('FocusedModeHeader', function() { ...@@ -32,29 +33,71 @@ describe('FocusedModeHeader', function() {
FocusedModeHeader.$imports.$restore(); FocusedModeHeader.$imports.$restore();
}); });
it('creates the component', () => { context('not in user-focused mode', () => {
it('should not render anything if not in user-focused mode', () => {
fakeStore.focusModeHasUser = sinon.stub().returns(false);
const wrapper = createComponent(); const wrapper = createComponent();
assert.include(wrapper.text(), 'All annotations');
assert.isFalse(wrapper.exists('.focused-mode-header'));
});
}); });
it("sets the button's text to the user's name when focused", () => { context('user-focused mode', () => {
fakeStore.focusModeFocused = sinon.stub().returns(true); context('focus is applied (focused/on)', () => {
it("should render status text indicating only that user's annotations are visible", () => {
const wrapper = createComponent(); const wrapper = createComponent();
assert.include(wrapper.text(), 'Annotations by Fake User');
assert.match(wrapper.text(), /Annotations by.+Fake User/);
}); });
describe('clicking the button shall toggle the focused mode', function() { it('should render a button allowing the user to view all annotations', () => {
it('when focused is false, toggle to true', () => {
const wrapper = createComponent(); const wrapper = createComponent();
wrapper.find('button').simulate('click');
assert.calledWith(fakeStore.setFocusModeFocused, true); const button = wrapper.find('button');
assert.include(button.text(), 'Show all');
});
});
context('focus is not applied (unfocused/off)', () => {
beforeEach(() => {
fakeStore.focusModeFocused = sinon.stub().returns(false);
});
it("should render status text indicating that all user's annotations are visible", () => {
const wrapper = createComponent();
assert.match(wrapper.text(), /Everybody.*s annotations/);
}); });
it('when focused is true, toggle to false', () => { it("should render a button allowing the user to view only focus user's annotations", () => {
const wrapper = createComponent();
const button = wrapper.find('button');
assert.include(button.text(), 'Only Fake User');
});
});
describe('toggle button', () => {
it('should toggle focus mode to false if clicked when focused', () => {
fakeStore.focusModeFocused = sinon.stub().returns(true); fakeStore.focusModeFocused = sinon.stub().returns(true);
const wrapper = createComponent(); const wrapper = createComponent();
wrapper.find('button').simulate('click'); wrapper.find('button').simulate('click');
assert.calledWith(fakeStore.setFocusModeFocused, false); assert.calledWith(fakeStore.setFocusModeFocused, false);
}); });
it('should toggle focus mode to true if clicked when not focused', () => {
fakeStore.focusModeFocused = sinon.stub().returns(false);
const wrapper = createComponent();
wrapper.find('button').simulate('click');
assert.calledWith(fakeStore.setFocusModeFocused, true);
});
});
}); });
}); });
...@@ -55,7 +55,8 @@ function RootThread($rootScope, store, searchFilter, viewFilter) { ...@@ -55,7 +55,8 @@ function RootThread($rootScope, store, searchFilter, viewFilter) {
let filterFn; let filterFn;
if (shouldFilterThread()) { if (shouldFilterThread()) {
const filters = searchFilter.generateFacetedFilter(state.filterQuery, { const filters = searchFilter.generateFacetedFilter(state.filterQuery, {
user: store.focusModeUsername(), // `null` if no focused user // if a focus mode is applied (focused) and we're focusing on a user
user: store.focusModeFocused() && store.focusModeUsername(),
}); });
filterFn = function(annot) { filterFn = function(annot) {
......
// A dark grey button used for the primary action
// in a form
.focused-mode-header { .focused-mode-header {
margin-bottom: 10px; display: flex;
button { align-items: center;
width: 100%;
height: 24px; &__btn {
margin-right: 10px; @include primary-action-btn;
&:focus { @include outline-on-keyboard-focus;
outline: none; display: flex;
margin-left: auto;
height: 30px;
padding-left: 6px;
padding-right: 10px;
background-color: $grey-2;
color: $grey-5;
&:hover:enabled {
background-color: $grey-3;
} }
} }
&__btn-icon {
margin-right: 3px;
}
&__filter-status {
font-weight: 400;
}
} }
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